diff options
300 files changed, 29656 insertions, 191 deletions
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn index f4432cd..66e69af 100644 --- a/mojo/BUILD.gn +++ b/mojo/BUILD.gn @@ -17,6 +17,10 @@ group("mojo") { if (is_android) { deps += [ "//mojo/android" ] } + + if (!is_component_build) { + deps += [ "//mojo/shell" ] + } } group("tests") { diff --git a/mojo/application/BUILD.gn b/mojo/application/BUILD.gn index dda877e..0087ebc 100644 --- a/mojo/application/BUILD.gn +++ b/mojo/application/BUILD.gn @@ -19,6 +19,22 @@ source_set("application") { ] } +source_set("content_handler") { + sources = [ + "content_handler_factory.cc", + "content_handler_factory.h", + ] + deps = [ + ":application", + "//base", + "//mojo/common", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/content_handler/public/interfaces", + "//mojo/services/network/public/interfaces", + ] +} + source_set("test_support") { testonly = true sources = [ diff --git a/mojo/application/DEPS b/mojo/application/DEPS new file mode 100644 index 0000000..dfd226d --- /dev/null +++ b/mojo/application/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/mojo_services/src/content_handler", +] diff --git a/mojo/application/content_handler_factory.cc b/mojo/application/content_handler_factory.cc new file mode 100644 index 0000000..8518f4c --- /dev/null +++ b/mojo/application/content_handler_factory.cc @@ -0,0 +1,130 @@ +// 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 "mojo/application/content_handler_factory.h" + +#include <set> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/platform_thread.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mojo { + +namespace { + +class ApplicationThread : public base::PlatformThread::Delegate { + public: + ApplicationThread( + scoped_refptr<base::MessageLoopProxy> handler_thread, + const base::Callback<void(ApplicationThread*)>& termination_callback, + ContentHandlerFactory::Delegate* handler_delegate, + InterfaceRequest<Application> application_request, + URLResponsePtr response) + : handler_thread_(handler_thread), + termination_callback_(termination_callback), + handler_delegate_(handler_delegate), + application_request_(application_request.Pass()), + response_(response.Pass()) {} + + private: + void ThreadMain() override { + handler_delegate_->RunApplication(application_request_.Pass(), + response_.Pass()); + handler_thread_->PostTask(FROM_HERE, + base::Bind(termination_callback_, this)); + } + + scoped_refptr<base::MessageLoopProxy> handler_thread_; + base::Callback<void(ApplicationThread*)> termination_callback_; + ContentHandlerFactory::Delegate* handler_delegate_; + InterfaceRequest<Application> application_request_; + URLResponsePtr response_; + + DISALLOW_COPY_AND_ASSIGN(ApplicationThread); +}; + +class ContentHandlerImpl : public ContentHandler { + public: + ContentHandlerImpl(ContentHandlerFactory::Delegate* delegate, + InterfaceRequest<ContentHandler> request) + : delegate_(delegate), + binding_(this, request.Pass()), + weak_factory_(this) {} + ~ContentHandlerImpl() override { + // We're shutting down and doing cleanup. Cleanup may trigger calls back to + // OnThreadEnd(). As we're doing the cleanup here we don't want to do it in + // OnThreadEnd() as well. InvalidateWeakPtrs() ensures we don't get any + // calls to OnThreadEnd(). + weak_factory_.InvalidateWeakPtrs(); + for (auto thread : active_threads_) { + base::PlatformThread::Join(thread.second); + delete thread.first; + } + } + + private: + // Overridden from ContentHandler: + void StartApplication(InterfaceRequest<Application> application_request, + URLResponsePtr response) override { + ApplicationThread* thread = new ApplicationThread( + base::MessageLoopProxy::current(), + base::Bind(&ContentHandlerImpl::OnThreadEnd, + weak_factory_.GetWeakPtr()), + delegate_, application_request.Pass(), response.Pass()); + base::PlatformThreadHandle handle; + bool launched = base::PlatformThread::Create(0, thread, &handle); + DCHECK(launched); + active_threads_[thread] = handle; + } + + void OnThreadEnd(ApplicationThread* thread) { + DCHECK(active_threads_.find(thread) != active_threads_.end()); + base::PlatformThreadHandle handle = active_threads_[thread]; + active_threads_.erase(thread); + base::PlatformThread::Join(handle); + delete thread; + } + + ContentHandlerFactory::Delegate* delegate_; + std::map<ApplicationThread*, base::PlatformThreadHandle> active_threads_; + StrongBinding<ContentHandler> binding_; + base::WeakPtrFactory<ContentHandlerImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl); +}; + +} // namespace + +ContentHandlerFactory::ContentHandlerFactory(Delegate* delegate) + : delegate_(delegate) { +} + +ContentHandlerFactory::~ContentHandlerFactory() { +} + +void ContentHandlerFactory::ManagedDelegate::RunApplication( + InterfaceRequest<Application> application_request, + URLResponsePtr response) { + base::MessageLoop loop(common::MessagePumpMojo::Create()); + auto application = + this->CreateApplication(application_request.Pass(), response.Pass()); + if (application) + loop.Run(); +} + +void ContentHandlerFactory::Create(ApplicationConnection* connection, + InterfaceRequest<ContentHandler> request) { + new ContentHandlerImpl(delegate_, request.Pass()); +} + +} // namespace mojo diff --git a/mojo/application/content_handler_factory.h b/mojo/application/content_handler_factory.h new file mode 100644 index 0000000..aabba6f --- /dev/null +++ b/mojo/application/content_handler_factory.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_ +#define MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/interfaces/application/shell.mojom.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" + +namespace mojo { + +class ContentHandlerFactory : public InterfaceFactory<ContentHandler> { + public: + class HandledApplicationHolder { + public: + virtual ~HandledApplicationHolder() {} + }; + + class Delegate { + public: + virtual ~Delegate() {} + // Implement this method to create the Application. This method will be + // called on a new thread. Leaving this method will quit the application. + virtual void RunApplication( + InterfaceRequest<Application> application_request, + URLResponsePtr response) = 0; + }; + + class ManagedDelegate : public Delegate { + public: + ~ManagedDelegate() override {} + // Implement this method to create the Application for the given content. + // This method will be called on a new thread. The application will be run + // on this new thread, and the returned value will be kept alive until the + // application ends. + virtual scoped_ptr<HandledApplicationHolder> CreateApplication( + InterfaceRequest<Application> application_request, + URLResponsePtr response) = 0; + + private: + void RunApplication(InterfaceRequest<Application> application_request, + URLResponsePtr response) override; + }; + + explicit ContentHandlerFactory(Delegate* delegate); + ~ContentHandlerFactory() override; + + private: + // From InterfaceFactory: + void Create(ApplicationConnection* connection, + InterfaceRequest<ContentHandler> request) override; + + Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(ContentHandlerFactory); +}; + +template <class A> +class HandledApplicationHolderImpl + : public ContentHandlerFactory::HandledApplicationHolder { + public: + explicit HandledApplicationHolderImpl(A* value) : value_(value) {} + + private: + scoped_ptr<A> value_; +}; + +template <class A> +scoped_ptr<ContentHandlerFactory::HandledApplicationHolder> +make_handled_factory_holder(A* value) { + return make_scoped_ptr(new HandledApplicationHolderImpl<A>(value)); +} + +} // namespace mojo + +#endif // MOJO_APPLICATION_CONTENT_HANDLER_FACTORY_H_ diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn index 8044698..8f5dfaa 100644 --- a/mojo/common/BUILD.gn +++ b/mojo/common/BUILD.gn @@ -18,6 +18,7 @@ component("common_base") { sources = [ "common_type_converters.cc", "common_type_converters.h", + "data_pipe_file_utils.cc", "data_pipe_utils.cc", "data_pipe_utils.h", "handle_watcher.cc", @@ -27,6 +28,8 @@ component("common_base") { "message_pump_mojo_handler.h", "time_helper.cc", "time_helper.h", + "weak_binding_set.h", + "weak_interface_ptr_set.h", ] defines = [ "MOJO_COMMON_IMPLEMENTATION" ] @@ -76,3 +79,21 @@ test("mojo_common_unittests") { "message_pump_mojo_unittest.cc", ] } + +if (!is_component_build) { + source_set("tracing_impl") { + sources = [ + "trace_controller_impl.cc", + "trace_controller_impl.h", + "tracing_impl.cc", + "tracing_impl.h", + ] + + deps = [ + "//base", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//mojo/services/tracing:bindings", + ] + } +} diff --git a/mojo/common/DEPS b/mojo/common/DEPS index f3ee1c8..81bd86c 100644 --- a/mojo/common/DEPS +++ b/mojo/common/DEPS @@ -4,3 +4,12 @@ include_rules = [ "+mojo/common", "+third_party/mojo/src/mojo/public", ] + +specific_include_rules = {
+ "trace_controller_impl\.h": [
+ "+mojo/services/tracing/tracing.mojom.h"
+ ],
+ "tracing_impl\.h": [
+ "+mojo/services/tracing/tracing.mojom.h"
+ ],
+}
diff --git a/mojo/common/data_pipe_file_utils.cc b/mojo/common/data_pipe_file_utils.cc new file mode 100644 index 0000000..4bc2686 --- /dev/null +++ b/mojo/common/data_pipe_file_utils.cc @@ -0,0 +1,79 @@ +// 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 "mojo/common/data_pipe_utils.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/location.h" +#include "base/task_runner_util.h" + +namespace mojo { +namespace common { +namespace { + +bool BlockingCopyFromFile(const base::FilePath& source, + ScopedDataPipeProducerHandle destination, + uint32_t skip) { + base::File file(source, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) + return false; + if (file.Seek(base::File::FROM_BEGIN, skip) != skip) { + LOG(ERROR) << "Seek of " << skip << " in " << source.value() << " failed"; + return false; + } + for (;;) { + void* buffer = nullptr; + uint32_t buffer_num_bytes = 0; + MojoResult result = + BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + int bytes_read = + file.ReadAtCurrentPos(static_cast<char*>(buffer), buffer_num_bytes); + if (bytes_read >= 0) { + EndWriteDataRaw(destination.get(), bytes_read); + if (bytes_read == 0) { + // eof + return true; + } + } else { + // error + EndWriteDataRaw(destination.get(), 0); + return false; + } + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_DEADLINE_INDEFINITE, nullptr); + if (result != MOJO_RESULT_OK) { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } +#if !defined(OS_WIN) + NOTREACHED(); + return false; +#endif +} + +} // namespace + +void CopyFromFile(const base::FilePath& source, + ScopedDataPipeProducerHandle destination, + uint32_t skip, + base::TaskRunner* task_runner, + const base::Callback<void(bool)>& callback) { + base::PostTaskAndReplyWithResult(task_runner, FROM_HERE, + base::Bind(&BlockingCopyFromFile, source, + base::Passed(&destination), skip), + callback); +} + +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc index c99c666..a280dad 100644 --- a/mojo/common/data_pipe_utils.cc +++ b/mojo/common/data_pipe_utils.cc @@ -71,6 +71,37 @@ bool BlockingCopyToString(ScopedDataPipeConsumerHandle source, source.Pass(), base::Bind(&CopyToStringHelper, result)); } +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination) { + auto it = source.begin(); + for (;;) { + void* buffer = nullptr; + uint32_t buffer_num_bytes = 0; + MojoResult result = + BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + char* char_buffer = static_cast<char*>(buffer); + uint32_t byte_index = 0; + while (it != source.end() && byte_index < buffer_num_bytes) { + char_buffer[byte_index++] = *it++; + } + EndWriteDataRaw(destination.get(), byte_index); + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE, + MOJO_DEADLINE_INDEFINITE, nullptr); + if (result != MOJO_RESULT_OK) { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } +} + bool BlockingCopyToFile(ScopedDataPipeConsumerHandle source, const base::FilePath& destination) { base::ScopedFILE fp(base::OpenFile(destination, "wb")); diff --git a/mojo/common/data_pipe_utils.h b/mojo/common/data_pipe_utils.h index 65a0b8b..5b32cfd 100644 --- a/mojo/common/data_pipe_utils.h +++ b/mojo/common/data_pipe_utils.h @@ -28,6 +28,13 @@ void MOJO_COMMON_EXPORT CopyToFile( base::TaskRunner* task_runner, const base::Callback<void(bool /*success*/)>& callback); +void MOJO_COMMON_EXPORT +CopyFromFile(const base::FilePath& source, + ScopedDataPipeProducerHandle destination, + uint32_t skip, + base::TaskRunner* task_runner, + const base::Callback<void(bool /*success*/)>& callback); + // Copies the data from |source| into |contents| and returns true on success and // false on error. In case of I/O error, |contents| holds the data that could // be read from source before the error occurred. @@ -35,6 +42,16 @@ bool MOJO_COMMON_EXPORT BlockingCopyToString( ScopedDataPipeConsumerHandle source, std::string* contents); +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination); + +// Synchronously copies data from source to the destination file returning true +// on success and false on error. In case of an error, |destination| holds the +// data that could be read from the source before the error occured. +bool MOJO_COMMON_EXPORT BlockingCopyToFile(ScopedDataPipeConsumerHandle source, + const base::FilePath& destination); + } // namespace common } // namespace mojo diff --git a/mojo/common/trace_controller_impl.cc b/mojo/common/trace_controller_impl.cc new file mode 100644 index 0000000..178f4cd --- /dev/null +++ b/mojo/common/trace_controller_impl.cc @@ -0,0 +1,49 @@ +// Copyright 2015 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 "mojo/common/trace_controller_impl.h" + +#include "base/trace_event/trace_event.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" + +namespace mojo { + +TraceControllerImpl::TraceControllerImpl( + InterfaceRequest<tracing::TraceController> request) + : binding_(this, request.Pass()) { +} + +TraceControllerImpl::~TraceControllerImpl() { +} + +void TraceControllerImpl::StartTracing( + const String& categories, + tracing::TraceDataCollectorPtr collector) { + DCHECK(!collector_.get()); + collector_ = collector.Pass(); + std::string categories_str = categories.To<std::string>(); + base::trace_event::TraceLog::GetInstance()->SetEnabled( + base::trace_event::CategoryFilter(categories_str), + base::trace_event::TraceLog::RECORDING_MODE, + base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL)); +} + +void TraceControllerImpl::StopTracing() { + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + + base::trace_event::TraceLog::GetInstance()->Flush( + base::Bind(&TraceControllerImpl::SendChunk, base::Unretained(this))); +} + +void TraceControllerImpl::SendChunk( + const scoped_refptr<base::RefCountedString>& events_str, + bool has_more_events) { + collector_->DataCollected(mojo::String(events_str->data())); + if (!has_more_events) { + collector_.reset(); + } +} + +} // namespace mojo diff --git a/mojo/common/trace_controller_impl.h b/mojo/common/trace_controller_impl.h new file mode 100644 index 0000000..3f9225a --- /dev/null +++ b/mojo/common/trace_controller_impl.h @@ -0,0 +1,39 @@ +// Copyright 2015 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. + +#ifndef MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_ +#define MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_ + +#include "base/memory/ref_counted_memory.h" +#include "mojo/services/tracing/tracing.mojom.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h" + +namespace mojo { + +class TraceControllerImpl : public tracing::TraceController { + public: + explicit TraceControllerImpl( + InterfaceRequest<tracing::TraceController> request); + + ~TraceControllerImpl() override; + + private: + // tracing::TraceController implementation: + void StartTracing(const String& categories, + tracing::TraceDataCollectorPtr collector) override; + void StopTracing() override; + + void SendChunk(const scoped_refptr<base::RefCountedString>& events_str, + bool has_more_events); + + tracing::TraceDataCollectorPtr collector_; + StrongBinding<tracing::TraceController> binding_; + + DISALLOW_COPY_AND_ASSIGN(TraceControllerImpl); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_TRACING_CONTROLLER_IMPL_H_ diff --git a/mojo/common/tracing_impl.cc b/mojo/common/tracing_impl.cc new file mode 100644 index 0000000..420a475 --- /dev/null +++ b/mojo/common/tracing_impl.cc @@ -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. + +#include "mojo/common/tracing_impl.h" + +#include "base/trace_event/trace_event.h" +#include "mojo/common/trace_controller_impl.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" + +namespace mojo { + +TracingImpl::TracingImpl() { +} + +TracingImpl::~TracingImpl() { +} + +void TracingImpl::Initialize(ApplicationImpl* app) { + ApplicationConnection* connection = app->ConnectToApplication("mojo:tracing"); + connection->AddService(this); +} + +void TracingImpl::Create(ApplicationConnection* connection, + InterfaceRequest<tracing::TraceController> request) { + new TraceControllerImpl(request.Pass()); +} + +} // namespace mojo diff --git a/mojo/common/tracing_impl.h b/mojo/common/tracing_impl.h new file mode 100644 index 0000000..c9118e2 --- /dev/null +++ b/mojo/common/tracing_impl.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef MOJO_COMMON_TRACING_IMPL_H_ +#define MOJO_COMMON_TRACING_IMPL_H_ + +#include "base/macros.h" +#include "mojo/services/tracing/tracing.mojom.h" +#include "third_party/mojo/src/mojo/public/cpp/application/interface_factory.h" + +namespace mojo { + +class ApplicationImpl; + +class TracingImpl : public InterfaceFactory<tracing::TraceController> { + public: + TracingImpl(); + ~TracingImpl() override; + + // This connects to the tracing service and registers ourselves to provide + // tracing data on demand. + void Initialize(ApplicationImpl* app); + + private: + // InterfaceFactory<tracing::TraceController> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<tracing::TraceController> request) override; + + DISALLOW_COPY_AND_ASSIGN(TracingImpl); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_TRACING_IMPL_H_ diff --git a/mojo/common/weak_binding_set.h b/mojo/common/weak_binding_set.h new file mode 100644 index 0000000..8738388 --- /dev/null +++ b/mojo/common/weak_binding_set.h @@ -0,0 +1,106 @@ +// 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. + +#ifndef MOJO_COMMON_WEAK_BINDING_SET_H_ +#define MOJO_COMMON_WEAK_BINDING_SET_H_ + +#include <algorithm> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h" + +namespace mojo { + +template <typename Interface> +class WeakBinding; + +// Use this class to manage a set of weak pointers to bindings each of which is +// owned by the pipe they are bound to. +template <typename Interface> +class WeakBindingSet : public ErrorHandler { + public: + WeakBindingSet() : error_handler_(nullptr) {} + ~WeakBindingSet() { CloseAllBindings(); } + + void set_error_handler(ErrorHandler* error_handler) { + error_handler_ = error_handler; + } + + void AddBinding(Interface* impl, InterfaceRequest<Interface> request) { + auto binding = new WeakBinding<Interface>(impl, request.Pass()); + binding->set_error_handler(this); + bindings_.push_back(binding->GetWeakPtr()); + } + + void CloseAllBindings() { + for (const auto& it : bindings_) { + if (it) + it->Close(); + } + bindings_.clear(); + } + + private: + // ErrorHandler implementation. + void OnConnectionError() override { + // Clear any deleted bindings. + bindings_.erase( + std::remove_if(bindings_.begin(), bindings_.end(), + [](const base::WeakPtr<WeakBinding<Interface>>& p) { + return p.get() == nullptr; + }), + bindings_.end()); + + if (error_handler_) + error_handler_->OnConnectionError(); + } + + ErrorHandler* error_handler_; + std::vector<base::WeakPtr<WeakBinding<Interface>>> bindings_; + + DISALLOW_COPY_AND_ASSIGN(WeakBindingSet); +}; + +template <typename Interface> +class WeakBinding : public ErrorHandler { + public: + WeakBinding(Interface* impl, InterfaceRequest<Interface> request) + : binding_(impl, request.Pass()), + error_handler_(nullptr), + weak_ptr_factory_(this) { + binding_.set_error_handler(this); + } + + ~WeakBinding() override {} + + void set_error_handler(ErrorHandler* error_handler) { + error_handler_ = error_handler; + } + + base::WeakPtr<WeakBinding> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + void Close() { binding_.Close(); } + + // ErrorHandler implementation. + void OnConnectionError() override { + ErrorHandler* error_handler = error_handler_; + delete this; + if (error_handler) + error_handler->OnConnectionError(); + } + + private: + mojo::Binding<Interface> binding_; + ErrorHandler* error_handler_; + base::WeakPtrFactory<WeakBinding> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(WeakBinding); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_WEAK_BINDING_SET_H_ diff --git a/mojo/common/weak_interface_ptr_set.h b/mojo/common/weak_interface_ptr_set.h new file mode 100644 index 0000000..e2e88a5 --- /dev/null +++ b/mojo/common/weak_interface_ptr_set.h @@ -0,0 +1,88 @@ +// 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. + +#ifndef MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_ +#define MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_ + +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_ptr.h" + +namespace mojo { + +template <typename Interface> +class WeakInterfacePtr; + +template <typename Interface> +class WeakInterfacePtrSet { + public: + WeakInterfacePtrSet() {} + ~WeakInterfacePtrSet() { CloseAll(); } + + void AddInterfacePtr(InterfacePtr<Interface> ptr) { + auto weak_interface_ptr = new WeakInterfacePtr<Interface>(ptr.Pass()); + ptrs_.push_back(weak_interface_ptr->GetWeakPtr()); + ClearNullInterfacePtrs(); + } + + template <typename FunctionType> + void ForAllPtrs(FunctionType function) { + for (const auto& it : ptrs_) { + if (it) + function(it->get()); + } + ClearNullInterfacePtrs(); + } + + void CloseAll() { + for (const auto& it : ptrs_) { + if (it) + it->Close(); + } + ptrs_.clear(); + } + + private: + using WPWIPI = base::WeakPtr<WeakInterfacePtr<Interface>>; + + void ClearNullInterfacePtrs() { + ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(), [](const WPWIPI& p) { + return p.get() == nullptr; + }), ptrs_.end()); + } + + std::vector<WPWIPI> ptrs_; +}; + +template <typename Interface> +class WeakInterfacePtr : public ErrorHandler { + public: + explicit WeakInterfacePtr(InterfacePtr<Interface> ptr) + : ptr_(ptr.Pass()), weak_ptr_factory_(this) { + ptr_.set_error_handler(this); + } + ~WeakInterfacePtr() override {} + + void Close() { ptr_.reset(); } + + Interface* get() { return ptr_.get(); } + + base::WeakPtr<WeakInterfacePtr> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + // ErrorHandler implementation + void OnConnectionError() override { delete this; } + + InterfacePtr<Interface> ptr_; + base::WeakPtrFactory<WeakInterfacePtr> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(WeakInterfacePtr); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_WEAK_INTERFACE_PTR_SET_H_ diff --git a/mojo/converters/geometry/geometry_type_converters.cc b/mojo/converters/geometry/geometry_type_converters.cc index ea9166d..fb17e03 100644 --- a/mojo/converters/geometry/geometry_type_converters.cc +++ b/mojo/converters/geometry/geometry_type_converters.cc @@ -87,4 +87,19 @@ gfx::RectF TypeConverter<gfx::RectF, RectFPtr>::Convert(const RectFPtr& input) { return gfx::RectF(input->x, input->y, input->width, input->height); } +// static +Rect TypeConverter<Rect, gfx::Rect>::Convert(const gfx::Rect& input) { + Rect rect; + rect.x = input.x(); + rect.y = input.y(); + rect.width = input.width(); + rect.height = input.height(); + return rect; +} + +// static +gfx::Rect TypeConverter<gfx::Rect, Rect>::Convert(const Rect& input) { + return gfx::Rect(input.x, input.y, input.width, input.height); +} + } // namespace mojo diff --git a/mojo/converters/geometry/geometry_type_converters.h b/mojo/converters/geometry/geometry_type_converters.h index 2d906c1..cb2a6b8 100644 --- a/mojo/converters/geometry/geometry_type_converters.h +++ b/mojo/converters/geometry/geometry_type_converters.h @@ -59,6 +59,15 @@ struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::RectF, RectFPtr> { static gfx::RectF Convert(const RectFPtr& input); }; +template <> +struct MOJO_GEOMETRY_EXPORT TypeConverter<Rect, gfx::Rect> { + static Rect Convert(const gfx::Rect& input); +}; +template <> +struct MOJO_GEOMETRY_EXPORT TypeConverter<gfx::Rect, Rect> { + static gfx::Rect Convert(const Rect& input); +}; + } // namespace mojo #endif // MOJO_CONVERTERS_GEOMETRY_GEOMETRY_TYPE_CONVERTERS_H_ diff --git a/mojo/converters/input_events/input_event_names.h b/mojo/converters/input_events/input_event_names.h deleted file mode 100644 index 3fd6ef6..0000000 --- a/mojo/converters/input_events/input_event_names.h +++ /dev/null @@ -1,46 +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. - -MOJO_INPUT_EVENT_NAME(UNKNOWN); -MOJO_INPUT_EVENT_NAME(MOUSE_PRESSED); -MOJO_INPUT_EVENT_NAME(MOUSE_DRAGGED); -MOJO_INPUT_EVENT_NAME(MOUSE_RELEASED); -MOJO_INPUT_EVENT_NAME(MOUSE_MOVED); -MOJO_INPUT_EVENT_NAME(MOUSE_ENTERED); -MOJO_INPUT_EVENT_NAME(MOUSE_EXITED); -MOJO_INPUT_EVENT_NAME(KEY_PRESSED); -MOJO_INPUT_EVENT_NAME(KEY_RELEASED); -MOJO_INPUT_EVENT_NAME(MOUSEWHEEL); -MOJO_INPUT_EVENT_NAME(MOUSE_CAPTURE_CHANGED); -MOJO_INPUT_EVENT_NAME(TOUCH_RELEASED); -MOJO_INPUT_EVENT_NAME(TOUCH_PRESSED); -MOJO_INPUT_EVENT_NAME(TOUCH_MOVED); -MOJO_INPUT_EVENT_NAME(TOUCH_CANCELLED); -MOJO_INPUT_EVENT_NAME(DROP_TARGET_EVENT); -MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_PRESS); -MOJO_INPUT_EVENT_NAME(TRANSLATED_KEY_RELEASE); -MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_BEGIN); -MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_END); -MOJO_INPUT_EVENT_NAME(GESTURE_SCROLL_UPDATE); -MOJO_INPUT_EVENT_NAME(GESTURE_TAP); -MOJO_INPUT_EVENT_NAME(GESTURE_TAP_DOWN); -MOJO_INPUT_EVENT_NAME(GESTURE_TAP_CANCEL); -MOJO_INPUT_EVENT_NAME(GESTURE_TAP_UNCONFIRMED); -MOJO_INPUT_EVENT_NAME(GESTURE_DOUBLE_TAP); -MOJO_INPUT_EVENT_NAME(GESTURE_BEGIN); -MOJO_INPUT_EVENT_NAME(GESTURE_END); -MOJO_INPUT_EVENT_NAME(GESTURE_TWO_FINGER_TAP); -MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_BEGIN); -MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_END); -MOJO_INPUT_EVENT_NAME(GESTURE_PINCH_UPDATE); -MOJO_INPUT_EVENT_NAME(GESTURE_LONG_PRESS); -MOJO_INPUT_EVENT_NAME(GESTURE_LONG_TAP); -MOJO_INPUT_EVENT_NAME(GESTURE_SWIPE); -MOJO_INPUT_EVENT_NAME(GESTURE_SHOW_PRESS); -MOJO_INPUT_EVENT_NAME(GESTURE_WIN8_EDGE_SWIPE); -MOJO_INPUT_EVENT_NAME(SCROLL); -MOJO_INPUT_EVENT_NAME(SCROLL_FLING_START); -MOJO_INPUT_EVENT_NAME(SCROLL_FLING_CANCEL); -MOJO_INPUT_EVENT_NAME(CANCEL_MODE); -MOJO_INPUT_EVENT_NAME(UMA_DATA); diff --git a/mojo/converters/input_events/input_events_type_converters.cc b/mojo/converters/input_events/input_events_type_converters.cc index f6be2f1..300ec18 100644 --- a/mojo/converters/input_events/input_events_type_converters.cc +++ b/mojo/converters/input_events/input_events_type_converters.cc @@ -16,6 +16,70 @@ #include "ui/events/keycodes/keyboard_codes.h" namespace mojo { +namespace { + +ui::EventType MojoMouseEventTypeToUIEvent(const EventPtr& event) { + DCHECK(!event->pointer_data.is_null()); + DCHECK_EQ(POINTER_KIND_MOUSE, event->pointer_data->kind); + switch (event->action) { + case EVENT_TYPE_POINTER_DOWN: + return ui::ET_MOUSE_PRESSED; + + case EVENT_TYPE_POINTER_UP: + return ui::ET_MOUSE_RELEASED; + + case EVENT_TYPE_POINTER_MOVE: + DCHECK(event->pointer_data); + if (event->pointer_data->horizontal_wheel != 0 || + event->pointer_data->vertical_wheel != 0) { + return ui::ET_MOUSEWHEEL; + } + if (event->flags & + (EVENT_FLAGS_LEFT_MOUSE_BUTTON | EVENT_FLAGS_MIDDLE_MOUSE_BUTTON | + EVENT_FLAGS_RIGHT_MOUSE_BUTTON)) { + return ui::ET_MOUSE_DRAGGED; + } + return ui::ET_MOUSE_MOVED; + + default: + NOTREACHED(); + } + + return ui::ET_MOUSE_RELEASED; +} + +ui::EventType MojoTouchEventTypeToUIEvent(const EventPtr& event) { + DCHECK(!event->pointer_data.is_null()); + DCHECK_EQ(POINTER_KIND_TOUCH, event->pointer_data->kind); + switch (event->action) { + case EVENT_TYPE_POINTER_DOWN: + return ui::ET_TOUCH_PRESSED; + + case EVENT_TYPE_POINTER_UP: + return ui::ET_TOUCH_RELEASED; + + case EVENT_TYPE_POINTER_MOVE: + return ui::ET_TOUCH_MOVED; + + case EVENT_TYPE_POINTER_CANCEL: + return ui::ET_TOUCH_CANCELLED; + + default: + NOTREACHED(); + } + + return ui::ET_TOUCH_CANCELLED; +} + +void SetPointerDataLocationFromEvent(const ui::LocatedEvent& located_event, + PointerData* pointer_data) { + pointer_data->x = located_event.location_f().x(); + pointer_data->y = located_event.location_f().y(); + pointer_data->screen_x = located_event.root_location_f().x(); + pointer_data->screen_y = located_event.root_location_f().y(); +} + +} // namespace COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_NONE) == static_cast<int32>(ui::EF_NONE), @@ -60,62 +124,80 @@ COMPILE_ASSERT(static_cast<int32>(EVENT_FLAGS_MOD3_DOWN) == // static EventType TypeConverter<EventType, ui::EventType>::Convert(ui::EventType type) { -#define MOJO_INPUT_EVENT_NAME(name) case ui::ET_##name: return EVENT_TYPE_##name - switch (type) { -#include "mojo/converters/input_events/input_event_names.h" - case ui::ET_LAST: - NOTREACHED(); - break; - } + case ui::ET_MOUSE_PRESSED: + case ui::ET_TOUCH_PRESSED: + return EVENT_TYPE_POINTER_DOWN; -#undef MOJO_INPUT_EVENT_NAME + case ui::ET_MOUSE_DRAGGED: + case ui::ET_MOUSE_MOVED: + case ui::ET_MOUSE_ENTERED: + case ui::ET_MOUSE_EXITED: + case ui::ET_TOUCH_MOVED: + case ui::ET_MOUSEWHEEL: + return EVENT_TYPE_POINTER_MOVE; - NOTREACHED(); - return EVENT_TYPE_UNKNOWN; -} + case ui::ET_MOUSE_RELEASED: + case ui::ET_TOUCH_RELEASED: + return EVENT_TYPE_POINTER_UP; -// static -ui::EventType TypeConverter<ui::EventType, EventType>::Convert(EventType type) { -#define MOJO_INPUT_EVENT_NAME(name) case EVENT_TYPE_##name: return ui::ET_##name + case ui::ET_TOUCH_CANCELLED: + return EVENT_TYPE_POINTER_CANCEL; - switch (type) { -#include "mojo/converters/input_events/input_event_names.h" - } + case ui::ET_KEY_PRESSED: + return EVENT_TYPE_KEY_PRESSED; -#undef MOJO_INPUT_EVENT_NAME + case ui::ET_KEY_RELEASED: + return EVENT_TYPE_KEY_RELEASED; - NOTREACHED(); - return ui::ET_UNKNOWN; + default: + break; + } + return EVENT_TYPE_UNKNOWN; } -// static EventPtr TypeConverter<EventPtr, ui::Event>::Convert(const ui::Event& input) { - EventPtr event(Event::New()); - event->action = ConvertTo<EventType>(input.type()); + const EventType type = ConvertTo<EventType>(input.type()); + if (type == EVENT_TYPE_UNKNOWN) + return nullptr; + + EventPtr event = Event::New(); + event->action = type; event->flags = EventFlags(input.flags()); event->time_stamp = input.time_stamp().ToInternalValue(); - if (input.IsMouseEvent() || input.IsTouchEvent()) { + PointerData pointer_data; + if (input.IsMouseEvent()) { const ui::LocatedEvent* located_event = static_cast<const ui::LocatedEvent*>(&input); - - LocationDataPtr location_data(LocationData::New()); - location_data->in_view_location = Point::From(located_event->location()); - if (input.HasNativeEvent()) { - location_data->screen_location = - Point::From(ui::EventSystemLocationFromNative(input.native_event())); + PointerDataPtr pointer_data(PointerData::New()); + // TODO(sky): come up with a better way to handle this. + pointer_data->pointer_id = std::numeric_limits<int32>::max(); + pointer_data->kind = POINTER_KIND_MOUSE; + SetPointerDataLocationFromEvent(*located_event, pointer_data.get()); + if (input.IsMouseWheelEvent()) { + const ui::MouseWheelEvent* wheel_event = + static_cast<const ui::MouseWheelEvent*>(&input); + // This conversion assumes we're using the mojo meaning of these values: + // [-1 1]. + pointer_data->horizontal_wheel = + static_cast<float>(wheel_event->x_offset()) / 100.0f; + pointer_data->vertical_wheel = + static_cast<float>(wheel_event->y_offset()) / 100.0f; } - - event->location_data = location_data.Pass(); - } - - if (input.IsTouchEvent()) { + event->pointer_data = pointer_data.Pass(); + } else if (input.IsTouchEvent()) { const ui::TouchEvent* touch_event = static_cast<const ui::TouchEvent*>(&input); - TouchDataPtr touch_data(TouchData::New()); - touch_data->pointer_id = touch_event->touch_id(); - event->touch_data = touch_data.Pass(); + PointerDataPtr pointer_data(PointerData::New()); + pointer_data->pointer_id = touch_event->touch_id(); + pointer_data->kind = POINTER_KIND_TOUCH; + SetPointerDataLocationFromEvent(*touch_event, pointer_data.get()); + pointer_data->radius_major = touch_event->radius_x(); + pointer_data->radius_minor = touch_event->radius_y(); + pointer_data->pressure = touch_event->force(); + pointer_data->orientation = touch_event->rotation_angle(); + event->pointer_data = pointer_data.Pass(); } else if (input.IsKeyEvent()) { const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(&input); KeyDataPtr key_data(KeyData::New()); @@ -138,15 +220,7 @@ EventPtr TypeConverter<EventPtr, ui::Event>::Convert(const ui::Event& input) { key_data->text = key_event->GetText(); key_data->unmodified_text = key_event->GetUnmodifiedText(); } - event->key_data = key_data.Pass(); - } else if (input.IsMouseWheelEvent()) { - const ui::MouseWheelEvent* wheel_event = - static_cast<const ui::MouseWheelEvent*>(&input); - MouseWheelDataPtr wheel_data(MouseWheelData::New()); - wheel_data->x_offset = wheel_event->x_offset(); - wheel_data->y_offset = wheel_event->y_offset(); - event->wheel_data = wheel_data.Pass(); } return event.Pass(); } @@ -160,18 +234,17 @@ EventPtr TypeConverter<EventPtr, ui::KeyEvent>::Convert( // static scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert( const EventPtr& input) { - scoped_ptr<ui::Event> ui_event; - ui::EventType ui_event_type = ConvertTo<ui::EventType>(input->action); - - gfx::Point location; - if (!input->location_data.is_null() && - !input->location_data->in_view_location.is_null()) { - location = input->location_data->in_view_location.To<gfx::Point>(); + gfx::PointF location; + gfx::PointF screen_location; + if (!input->pointer_data.is_null()) { + location.SetPoint(input->pointer_data->x, input->pointer_data->y); + screen_location.SetPoint(input->pointer_data->screen_x, + input->pointer_data->screen_y); } switch (input->action) { - case ui::ET_KEY_PRESSED: - case ui::ET_KEY_RELEASED: { + case EVENT_TYPE_KEY_PRESSED: + case EVENT_TYPE_KEY_RELEASED: { scoped_ptr<ui::KeyEvent> key_event; if (input->key_data->is_char) { key_event.reset(new ui::KeyEvent( @@ -181,9 +254,10 @@ scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert( input->flags)); } else { key_event.reset(new ui::KeyEvent( - ui_event_type, - static_cast<ui::KeyboardCode>( - input->key_data->key_code), + input->action == EVENT_TYPE_KEY_PRESSED ? ui::ET_KEY_PRESSED + : ui::ET_KEY_RELEASED, + + static_cast<ui::KeyboardCode>(input->key_data->key_code), input->flags)); } key_event->SetExtendedKeyEventData(scoped_ptr<ui::ExtendedKeyEventData>( @@ -192,54 +266,43 @@ scoped_ptr<ui::Event> TypeConverter<scoped_ptr<ui::Event>, EventPtr>::Convert( input->key_data->text, input->key_data->unmodified_text))); key_event->set_platform_keycode(input->key_data->native_key_code); - ui_event = key_event.Pass(); - break; - } - case EVENT_TYPE_MOUSE_PRESSED: - case EVENT_TYPE_MOUSE_DRAGGED: - case EVENT_TYPE_MOUSE_RELEASED: - case EVENT_TYPE_MOUSE_MOVED: - case EVENT_TYPE_MOUSE_ENTERED: - case EVENT_TYPE_MOUSE_EXITED: { - // TODO: last flags isn't right. Need to send changed_flags. - ui_event.reset(new ui::MouseEvent( - ui_event_type, - location, - location, - ui::EventFlags(input->flags), - ui::EventFlags(input->flags))); - break; + return key_event.Pass(); } - case EVENT_TYPE_MOUSEWHEEL: { - const gfx::Vector2d offset(input->wheel_data->x_offset, - input->wheel_data->y_offset); - ui_event.reset(new ui::MouseWheelEvent(offset, - location, - location, - ui::EventFlags(input->flags), - ui::EventFlags(input->flags))); - break; - } - case EVENT_TYPE_TOUCH_MOVED: - case EVENT_TYPE_TOUCH_PRESSED: - case EVENT_TYPE_TOUCH_CANCELLED: - case EVENT_TYPE_TOUCH_RELEASED: { - ui_event.reset(new ui::TouchEvent( - ui_event_type, - location, - ui::EventFlags(input->flags), - input->touch_data->pointer_id, - base::TimeDelta::FromInternalValue(input->time_stamp), - 0.f, 0.f, 0.f, 0.f)); - break; + case EVENT_TYPE_POINTER_DOWN: + case EVENT_TYPE_POINTER_UP: + case EVENT_TYPE_POINTER_MOVE: + case EVENT_TYPE_POINTER_CANCEL: { + if (input->pointer_data->kind == POINTER_KIND_MOUSE) { + // TODO: last flags isn't right. Need to send changed_flags. + scoped_ptr<ui::MouseEvent> event(new ui::MouseEvent( + MojoMouseEventTypeToUIEvent(input), location, screen_location, + ui::EventTimeForNow(), ui::EventFlags(input->flags), + ui::EventFlags(input->flags))); + if (event->IsMouseWheelEvent()) { + // This conversion assumes we're using the mojo meaning of these + // values: [-1 1]. + scoped_ptr<ui::MouseEvent> wheel_event(new ui::MouseWheelEvent( + *event, + static_cast<int>(input->pointer_data->horizontal_wheel * 100), + static_cast<int>(input->pointer_data->vertical_wheel * 100))); + event = wheel_event.Pass(); + } + return event.Pass(); + } + scoped_ptr<ui::TouchEvent> touch_event(new ui::TouchEvent( + MojoTouchEventTypeToUIEvent(input), location, + ui::EventFlags(input->flags), input->pointer_data->pointer_id, + base::TimeDelta::FromInternalValue(input->time_stamp), + input->pointer_data->radius_major, input->pointer_data->radius_minor, + input->pointer_data->orientation, input->pointer_data->pressure)); + touch_event->set_root_location(screen_location); + return touch_event.Pass(); } default: - // TODO: support other types. - // NOTIMPLEMENTED(); - ; + NOTIMPLEMENTED(); } // TODO: need to support time_stamp. - return ui_event.Pass(); + return nullptr; } } // namespace mojo diff --git a/mojo/converters/input_events/input_events_type_converters.h b/mojo/converters/input_events/input_events_type_converters.h index d520006..b9e152f 100644 --- a/mojo/converters/input_events/input_events_type_converters.h +++ b/mojo/converters/input_events/input_events_type_converters.h @@ -12,17 +12,14 @@ namespace mojo { +// NOTE: the mojo input events do not necessarily provide a 1-1 mapping with +// ui::Event types. Be careful in using them! template <> struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventType, ui::EventType> { static EventType Convert(ui::EventType type); }; template <> -struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<ui::EventType, EventType> { - static ui::EventType Convert(EventType type); -}; - -template <> struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::Event> { static EventPtr Convert(const ui::Event& input); }; @@ -33,6 +30,11 @@ struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::KeyEvent> { }; template <> +struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<EventPtr, ui::GestureEvent> { + static EventPtr Convert(const ui::GestureEvent& input); +}; + +template <> struct MOJO_INPUT_EVENTS_EXPORT TypeConverter<scoped_ptr<ui::Event>, EventPtr> { static scoped_ptr<ui::Event> Convert(const EventPtr& input); }; diff --git a/mojo/gles2/BUILD.gn b/mojo/gles2/BUILD.gn new file mode 100644 index 0000000..3c27743 --- /dev/null +++ b/mojo/gles2/BUILD.gn @@ -0,0 +1,54 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") + +config("mojo_use_gles2") { + defines = [ "MOJO_USE_GLES2_IMPL" ] +} + +config("gles2_use_mojo") { + defines = [ "GLES2_USE_MOJO" ] +} + +source_set("gles2") { + sources = [ + "command_buffer_client_impl.cc", + "command_buffer_client_impl.h", + "gles2_impl.cc", + "gles2_context.cc", + "gles2_context.h", + ] + + defines = [ + "GL_GLEXT_PROTOTYPES", + "MOJO_GLES2_IMPLEMENTATION", + ] + + configs += [ + ":gles2_use_mojo", + ":mojo_use_gles2", + ] + public_configs = [ ":gles2_use_mojo" ] + all_dependent_configs = [ ":mojo_use_gles2" ] + + deps = [ + "//base", + "//base/third_party/dynamic_annotations", + "//gpu/command_buffer/client", + "//gpu/command_buffer/client:gles2_cmd_helper", + "//gpu/command_buffer/client:gles2_implementation", + "//gpu/command_buffer/client:gles2_interface", + "//gpu/command_buffer/common", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/c/gles2:headers", + "//third_party/mojo/src/mojo/public/c/system", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/mojo_services/src/gpu/public/interfaces", + "//mojo/services/gles2:lib", + ] + + include_dirs = [ ".." ] +} diff --git a/mojo/gles2/DEPS b/mojo/gles2/DEPS new file mode 100644 index 0000000..aa72943 --- /dev/null +++ b/mojo/gles2/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+gpu", + "+third_party/mojo_services/src/gpu", +]
\ No newline at end of file diff --git a/mojo/gles2/README.md b/mojo/gles2/README.md new file mode 100644 index 0000000..94b3997a5 --- /dev/null +++ b/mojo/gles2/README.md @@ -0,0 +1,5 @@ +Mojo GLES2 +========== + +We export this dynamically linked library via mojo/public/gles2 in order to +hide the gpu/command_buffer/client dependency from clients of the Mojo API. diff --git a/mojo/gles2/command_buffer_client_impl.cc b/mojo/gles2/command_buffer_client_impl.cc new file mode 100644 index 0000000..37dc30c --- /dev/null +++ b/mojo/gles2/command_buffer_client_impl.cc @@ -0,0 +1,336 @@ +// 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 "mojo/gles2/command_buffer_client_impl.h" + +#include <limits> + +#include "base/logging.h" +#include "base/process/process_handle.h" +#include "mojo/services/gles2/command_buffer_type_conversions.h" +#include "mojo/services/gles2/mojo_buffer_backing.h" + +namespace gles2 { + +namespace { + +bool CreateMapAndDupSharedBuffer(size_t size, + void** memory, + mojo::ScopedSharedBufferHandle* handle, + mojo::ScopedSharedBufferHandle* duped) { + MojoResult result = mojo::CreateSharedBuffer(NULL, size, handle); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(handle->is_valid()); + + result = mojo::DuplicateBuffer(handle->get(), NULL, duped); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(duped->is_valid()); + + result = mojo::MapBuffer( + handle->get(), 0, size, memory, MOJO_MAP_BUFFER_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return false; + DCHECK(*memory); + + return true; +} + +} // namespace + +CommandBufferDelegate::~CommandBufferDelegate() {} + +void CommandBufferDelegate::ContextLost() {} + +class CommandBufferClientImpl::SyncClientImpl + : public mojo::CommandBufferSyncClient { + public: + SyncClientImpl(mojo::CommandBufferSyncClientPtr* ptr, + const MojoAsyncWaiter* async_waiter) + : initialized_successfully_(false), binding_(this, ptr, async_waiter) {} + + bool WaitForInitialization() { + if (!binding_.WaitForIncomingMethodCall()) + return false; + return initialized_successfully_; + } + + mojo::CommandBufferStatePtr WaitForProgress() { + if (!binding_.WaitForIncomingMethodCall()) + return mojo::CommandBufferStatePtr(); + return command_buffer_state_.Pass(); + } + + gpu::Capabilities GetCapabilities() { + return capabilities_.To<gpu::Capabilities>(); + } + + private: + // CommandBufferSyncClient methods: + void DidInitialize(bool success, + mojo::GpuCapabilitiesPtr capabilities) override { + initialized_successfully_ = success; + capabilities_ = capabilities.Pass(); + } + void DidMakeProgress(mojo::CommandBufferStatePtr state) override { + command_buffer_state_ = state.Pass(); + } + + bool initialized_successfully_; + mojo::GpuCapabilitiesPtr capabilities_; + mojo::CommandBufferStatePtr command_buffer_state_; + mojo::Binding<mojo::CommandBufferSyncClient> binding_; + + DISALLOW_COPY_AND_ASSIGN(SyncClientImpl); +}; + +class CommandBufferClientImpl::SyncPointClientImpl + : public mojo::CommandBufferSyncPointClient { + public: + SyncPointClientImpl(mojo::CommandBufferSyncPointClientPtr* ptr, + const MojoAsyncWaiter* async_waiter) + : sync_point_(0u), binding_(this, ptr, async_waiter) {} + + uint32_t WaitForInsertSyncPoint() { + if (!binding_.WaitForIncomingMethodCall()) + return 0u; + uint32_t result = sync_point_; + sync_point_ = 0u; + return result; + } + + private: + void DidInsertSyncPoint(uint32_t sync_point) override { + sync_point_ = sync_point; + } + + uint32_t sync_point_; + + mojo::Binding<mojo::CommandBufferSyncPointClient> binding_; +}; + +CommandBufferClientImpl::CommandBufferClientImpl( + CommandBufferDelegate* delegate, + const MojoAsyncWaiter* async_waiter, + mojo::ScopedMessagePipeHandle command_buffer_handle) + : delegate_(delegate), + observer_binding_(this), + shared_state_(NULL), + last_put_offset_(-1), + next_transfer_buffer_id_(0), + async_waiter_(async_waiter) { + command_buffer_.Bind(command_buffer_handle.Pass(), async_waiter); + command_buffer_.set_error_handler(this); +} + +CommandBufferClientImpl::~CommandBufferClientImpl() {} + +bool CommandBufferClientImpl::Initialize() { + const size_t kSharedStateSize = sizeof(gpu::CommandBufferSharedState); + void* memory = NULL; + mojo::ScopedSharedBufferHandle duped; + bool result = CreateMapAndDupSharedBuffer( + kSharedStateSize, &memory, &shared_state_handle_, &duped); + if (!result) + return false; + + shared_state_ = static_cast<gpu::CommandBufferSharedState*>(memory); + + shared_state()->Initialize(); + + mojo::CommandBufferSyncClientPtr sync_client; + sync_client_impl_.reset(new SyncClientImpl(&sync_client, async_waiter_)); + + mojo::CommandBufferSyncPointClientPtr sync_point_client; + sync_point_client_impl_.reset( + new SyncPointClientImpl(&sync_point_client, async_waiter_)); + + mojo::CommandBufferLostContextObserverPtr observer_ptr; + observer_binding_.Bind(GetProxy(&observer_ptr), async_waiter_); + command_buffer_->Initialize(sync_client.Pass(), + sync_point_client.Pass(), + observer_ptr.Pass(), + duped.Pass()); + + // Wait for DidInitialize to come on the sync client pipe. + if (!sync_client_impl_->WaitForInitialization()) { + VLOG(1) << "Channel encountered error while creating command buffer"; + return false; + } + capabilities_ = sync_client_impl_->GetCapabilities(); + return true; +} + +gpu::CommandBuffer::State CommandBufferClientImpl::GetLastState() { + return last_state_; +} + +int32 CommandBufferClientImpl::GetLastToken() { + TryUpdateState(); + return last_state_.token; +} + +void CommandBufferClientImpl::Flush(int32 put_offset) { + if (last_put_offset_ == put_offset) + return; + + last_put_offset_ = put_offset; + command_buffer_->Flush(put_offset); +} + +void CommandBufferClientImpl::OrderingBarrier(int32_t put_offset) { + // TODO(jamesr): Implement this more efficiently. + Flush(put_offset); +} + +void CommandBufferClientImpl::WaitForTokenInRange(int32 start, int32 end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.token) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + TryUpdateState(); + } +} + +void CommandBufferClientImpl::WaitForGetOffsetInRange(int32 start, int32 end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.get_offset) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + TryUpdateState(); + } +} + +void CommandBufferClientImpl::SetGetBuffer(int32 shm_id) { + command_buffer_->SetGetBuffer(shm_id); + last_put_offset_ = -1; +} + +scoped_refptr<gpu::Buffer> CommandBufferClientImpl::CreateTransferBuffer( + size_t size, + int32* id) { + if (size >= std::numeric_limits<uint32_t>::max()) + return NULL; + + void* memory = NULL; + mojo::ScopedSharedBufferHandle handle; + mojo::ScopedSharedBufferHandle duped; + if (!CreateMapAndDupSharedBuffer(size, &memory, &handle, &duped)) + return NULL; + + *id = ++next_transfer_buffer_id_; + + command_buffer_->RegisterTransferBuffer( + *id, duped.Pass(), static_cast<uint32_t>(size)); + + scoped_ptr<gpu::BufferBacking> backing( + new MojoBufferBacking(handle.Pass(), memory, size)); + scoped_refptr<gpu::Buffer> buffer(new gpu::Buffer(backing.Pass())); + return buffer; +} + +void CommandBufferClientImpl::DestroyTransferBuffer(int32 id) { + command_buffer_->DestroyTransferBuffer(id); +} + +gpu::Capabilities CommandBufferClientImpl::GetCapabilities() { + return capabilities_; +} + +int32_t CommandBufferClientImpl::CreateImage(ClientBuffer buffer, + size_t width, + size_t height, + unsigned internalformat) { + // TODO(piman) + NOTIMPLEMENTED(); + return -1; +} + +void CommandBufferClientImpl::DestroyImage(int32 id) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +int32_t CommandBufferClientImpl::CreateGpuMemoryBufferImage( + size_t width, + size_t height, + unsigned internalformat, + unsigned usage) { + // TODO(piman) + NOTIMPLEMENTED(); + return -1; +} + +uint32_t CommandBufferClientImpl::InsertSyncPoint() { + command_buffer_->InsertSyncPoint(true); + return sync_point_client_impl_->WaitForInsertSyncPoint(); +} + +uint32_t CommandBufferClientImpl::InsertFutureSyncPoint() { + command_buffer_->InsertSyncPoint(false); + return sync_point_client_impl_->WaitForInsertSyncPoint(); +} + +void CommandBufferClientImpl::RetireSyncPoint(uint32_t sync_point) { + command_buffer_->RetireSyncPoint(sync_point); +} + +void CommandBufferClientImpl::SignalSyncPoint(uint32_t sync_point, + const base::Closure& callback) { + // TODO(piman) +} + +void CommandBufferClientImpl::SignalQuery(uint32_t query, + const base::Closure& callback) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +void CommandBufferClientImpl::SetSurfaceVisible(bool visible) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +uint32_t CommandBufferClientImpl::CreateStreamTexture(uint32_t texture_id) { + // TODO(piman) + NOTIMPLEMENTED(); + return 0; +} + +void CommandBufferClientImpl::DidLoseContext(int32_t lost_reason) { + last_state_.error = gpu::error::kLostContext; + last_state_.context_lost_reason = + static_cast<gpu::error::ContextLostReason>(lost_reason); + delegate_->ContextLost(); +} + +void CommandBufferClientImpl::OnConnectionError() { + DidLoseContext(gpu::error::kUnknown); +} + +void CommandBufferClientImpl::TryUpdateState() { + if (last_state_.error == gpu::error::kNoError) + shared_state()->Read(&last_state_); +} + +void CommandBufferClientImpl::MakeProgressAndUpdateState() { + command_buffer_->MakeProgress(last_state_.get_offset); + + mojo::CommandBufferStatePtr state = sync_client_impl_->WaitForProgress(); + if (!state) { + VLOG(1) << "Channel encountered error while waiting for command buffer"; + // TODO(piman): is it ok for this to re-enter? + DidLoseContext(gpu::error::kUnknown); + return; + } + + if (state->generation - last_state_.generation < 0x80000000U) + last_state_ = state.To<State>(); +} + +void CommandBufferClientImpl::SetLock(base::Lock* lock) { +} + +} // namespace gles2 diff --git a/mojo/gles2/command_buffer_client_impl.h b/mojo/gles2/command_buffer_client_impl.h new file mode 100644 index 0000000..811bb07 --- /dev/null +++ b/mojo/gles2/command_buffer_client_impl.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ +#define MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/client/gpu_control.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/command_buffer_shared.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" + +namespace base { +class RunLoop; +} + +namespace gles2 { +class CommandBufferClientImpl; + +class CommandBufferDelegate { + public: + virtual ~CommandBufferDelegate(); + virtual void ContextLost(); +}; + +class CommandBufferClientImpl : public mojo::CommandBufferLostContextObserver, + public mojo::ErrorHandler, + public gpu::CommandBuffer, + public gpu::GpuControl { + public: + explicit CommandBufferClientImpl( + CommandBufferDelegate* delegate, + const MojoAsyncWaiter* async_waiter, + mojo::ScopedMessagePipeHandle command_buffer_handle); + ~CommandBufferClientImpl() override; + + // CommandBuffer implementation: + bool Initialize() override; + State GetLastState() override; + int32_t GetLastToken() override; + void Flush(int32_t put_offset) override; + void OrderingBarrier(int32_t put_offset) override; + void WaitForTokenInRange(int32_t start, int32_t end) override; + void WaitForGetOffsetInRange(int32_t start, int32_t end) override; + void SetGetBuffer(int32_t shm_id) override; + scoped_refptr<gpu::Buffer> CreateTransferBuffer(size_t size, + int32_t* id) override; + void DestroyTransferBuffer(int32_t id) override; + + // gpu::GpuControl implementation: + gpu::Capabilities GetCapabilities() override; + int32_t CreateImage(ClientBuffer buffer, + size_t width, + size_t height, + unsigned internalformat) override; + void DestroyImage(int32_t id) override; + int32_t CreateGpuMemoryBufferImage(size_t width, + size_t height, + unsigned internalformat, + unsigned usage) override; + uint32 InsertSyncPoint() override; + uint32 InsertFutureSyncPoint() override; + void RetireSyncPoint(uint32 sync_point) override; + void SignalSyncPoint(uint32 sync_point, + const base::Closure& callback) override; + void SignalQuery(uint32 query, const base::Closure& callback) override; + void SetSurfaceVisible(bool visible) override; + uint32 CreateStreamTexture(uint32 texture_id) override; + void SetLock(base::Lock*) override; + + private: + class SyncClientImpl; + class SyncPointClientImpl; + + // mojo::CommandBufferLostContextObserver implementation: + void DidLoseContext(int32_t lost_reason) override; + + // mojo::ErrorHandler implementation: + void OnConnectionError() override; + + void TryUpdateState(); + void MakeProgressAndUpdateState(); + + gpu::CommandBufferSharedState* shared_state() const { return shared_state_; } + + CommandBufferDelegate* delegate_; + mojo::Binding<mojo::CommandBufferLostContextObserver> observer_binding_; + mojo::CommandBufferPtr command_buffer_; + scoped_ptr<SyncClientImpl> sync_client_impl_; + scoped_ptr<SyncPointClientImpl> sync_point_client_impl_; + + gpu::Capabilities capabilities_; + State last_state_; + mojo::ScopedSharedBufferHandle shared_state_handle_; + gpu::CommandBufferSharedState* shared_state_; + int32_t last_put_offset_; + int32_t next_transfer_buffer_id_; + + const MojoAsyncWaiter* async_waiter_; +}; + +} // gles2 + +#endif // MOJO_GLES2_COMMAND_BUFFER_CLIENT_IMPL_H_ diff --git a/mojo/gles2/gles2_context.cc b/mojo/gles2/gles2_context.cc new file mode 100644 index 0000000..7334c64 --- /dev/null +++ b/mojo/gles2/gles2_context.cc @@ -0,0 +1,64 @@ +// 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 "mojo/gles2/gles2_context.h" + +#include "gpu/command_buffer/client/gles2_cmd_helper.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "gpu/command_buffer/client/transfer_buffer.h" +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" + +namespace gles2 { + +namespace { +const size_t kDefaultCommandBufferSize = 1024 * 1024; +const size_t kDefaultStartTransferBufferSize = 1 * 1024 * 1024; +const size_t kDefaultMinTransferBufferSize = 1 * 256 * 1024; +const size_t kDefaultMaxTransferBufferSize = 16 * 1024 * 1024; +} + +GLES2Context::GLES2Context(const MojoAsyncWaiter* async_waiter, + mojo::ScopedMessagePipeHandle command_buffer_handle, + MojoGLES2ContextLost lost_callback, + void* closure) + : command_buffer_(this, async_waiter, command_buffer_handle.Pass()), + lost_callback_(lost_callback), + closure_(closure) { +} + +GLES2Context::~GLES2Context() {} + +bool GLES2Context::Initialize() { + if (!command_buffer_.Initialize()) + return false; + gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(&command_buffer_)); + if (!gles2_helper_->Initialize(kDefaultCommandBufferSize)) + return false; + gles2_helper_->SetAutomaticFlushes(false); + transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get())); + gpu::Capabilities capabilities = command_buffer_.GetCapabilities(); + bool bind_generates_resource = + !!capabilities.bind_generates_resource_chromium; + // TODO(piman): Some contexts (such as compositor) want this to be true, so + // this needs to be a public parameter. + bool lose_context_when_out_of_memory = false; + bool support_client_side_arrays = false; + implementation_.reset( + new gpu::gles2::GLES2Implementation(gles2_helper_.get(), + NULL, + transfer_buffer_.get(), + bind_generates_resource, + lose_context_when_out_of_memory, + support_client_side_arrays, + &command_buffer_)); + return implementation_->Initialize(kDefaultStartTransferBufferSize, + kDefaultMinTransferBufferSize, + kDefaultMaxTransferBufferSize, + gpu::gles2::GLES2Implementation::kNoLimit); +} + +void GLES2Context::ContextLost() { lost_callback_(closure_); } + +} // namespace gles2 diff --git a/mojo/gles2/gles2_context.h b/mojo/gles2/gles2_context.h new file mode 100644 index 0000000..092ed74 --- /dev/null +++ b/mojo/gles2/gles2_context.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef MOJO_GLES2_GLES2_CONTEXT_H_ +#define MOJO_GLES2_GLES2_CONTEXT_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "mojo/gles2/command_buffer_client_impl.h" +#include "mojo/public/c/gles2/gles2.h" + +struct MojoGLES2ContextPrivate {}; + +namespace gpu { +class TransferBuffer; +namespace gles2 { +class GLES2CmdHelper; +class GLES2Implementation; +} +} + +namespace gles2 { + +class GLES2Context : public CommandBufferDelegate, + public MojoGLES2ContextPrivate { + public: + explicit GLES2Context(const MojoAsyncWaiter* async_waiter, + mojo::ScopedMessagePipeHandle command_buffer_handle, + MojoGLES2ContextLost lost_callback, + void* closure); + ~GLES2Context() override; + bool Initialize(); + + gpu::gles2::GLES2Interface* interface() const { + return implementation_.get(); + } + gpu::ContextSupport* context_support() const { return implementation_.get(); } + + private: + void ContextLost() override; + + CommandBufferClientImpl command_buffer_; + scoped_ptr<gpu::gles2::GLES2CmdHelper> gles2_helper_; + scoped_ptr<gpu::TransferBuffer> transfer_buffer_; + scoped_ptr<gpu::gles2::GLES2Implementation> implementation_; + MojoGLES2ContextLost lost_callback_; + void* closure_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(GLES2Context); +}; + +} // namespace gles2 + +#endif // MOJO_GLES2_GLES2_CONTEXT_H_ diff --git a/mojo/gles2/gles2_impl.cc b/mojo/gles2/gles2_impl.cc new file mode 100644 index 0000000..9a65a6d --- /dev/null +++ b/mojo/gles2/gles2_impl.cc @@ -0,0 +1,76 @@ +// 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 "mojo/public/c/gles2/gles2.h" + +#include "base/lazy_instance.h" +#include "base/threading/thread_local.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "mojo/gles2/gles2_context.h" + +using gles2::GLES2Context; + +namespace { + +base::LazyInstance<base::ThreadLocalPointer<gpu::gles2::GLES2Interface> >::Leaky + g_gpu_interface; + +} // namespace + +extern "C" { +MojoGLES2Context MojoGLES2CreateContext(MojoHandle handle, + MojoGLES2ContextLost lost_callback, + void* closure, + const MojoAsyncWaiter* async_waiter) { + mojo::MessagePipeHandle mph(handle); + mojo::ScopedMessagePipeHandle scoped_handle(mph); + scoped_ptr<GLES2Context> client(new GLES2Context( + async_waiter, scoped_handle.Pass(), lost_callback, closure)); + if (!client->Initialize()) + client.reset(); + return client.release(); +} + +void MojoGLES2DestroyContext(MojoGLES2Context context) { + delete static_cast<GLES2Context*>(context); +} + +void MojoGLES2MakeCurrent(MojoGLES2Context context) { + gpu::gles2::GLES2Interface* interface = NULL; + if (context) { + GLES2Context* client = static_cast<GLES2Context*>(context); + interface = client->interface(); + DCHECK(interface); + } + g_gpu_interface.Get().Set(interface); +} + +void MojoGLES2SwapBuffers() { + DCHECK(g_gpu_interface.Get().Get()); + g_gpu_interface.Get().Get()->SwapBuffers(); +} + +void* MojoGLES2GetGLES2Interface(MojoGLES2Context context) { + return static_cast<GLES2Context*>(context)->interface(); +} + +void* MojoGLES2GetContextSupport(MojoGLES2Context context) { + return static_cast<GLES2Context*>(context)->context_support(); +} + +#define VISIT_GL_CALL(Function, ReturnType, PARAMETERS, ARGUMENTS) \ + ReturnType gl##Function PARAMETERS { \ + DCHECK(g_gpu_interface.Get().Get()); \ + return g_gpu_interface.Get().Get()->Function ARGUMENTS; \ + } +#include "mojo/public/c/gles2/gles2_call_visitor_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_chromium_miscellaneous_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sub_image_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_chromium_sync_point_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_chromium_texture_mailbox_autogen.h" +#include "mojo/public/c/gles2/gles2_call_visitor_occlusion_query_ext_autogen.h" +#undef VISIT_GL_CALL + +} // extern "C" diff --git a/mojo/public/mojo.gni b/mojo/public/mojo.gni new file mode 100644 index 0000000..f2631a0 --- /dev/null +++ b/mojo/public/mojo.gni @@ -0,0 +1,34 @@ +# 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. + +import("//build/module_args/mojo.gni") + +# If using the prebuilt shell, gate its usage by the platforms for which it is +# published. +mojo_use_prebuilt_mojo_shell = false +if (!defined(mojo_build_mojo_shell_from_source) || + !mojo_build_mojo_shell_from_source) { + mojo_use_prebuilt_mojo_shell = is_linux || is_android +} + +# If using the prebuilt network service, gate its usage by the platforms for +# which it is published. +mojo_use_prebuilt_network_service = false +if (!defined(mojo_build_network_service_from_source) || + !mojo_build_network_service_from_source) { + mojo_use_prebuilt_network_service = is_linux || is_android +} + +# Enable Dart apptest framework by default. +mojo_use_dart_apptest_framework = true +if (defined(mojo_disable_dart_apptest_framework) && + mojo_disable_dart_apptest_framework) { + mojo_use_dart_apptest_framework = false +} + +# The absolute path to the directory containing the mojo public SDK (i.e., the +# directory containing mojo/public). The build files within the Mojo public +# SDK use this variable to allow themselves to be parameterized by the location +# of the public SDK within a client repo. +mojo_root = get_path_info("../..", "abspath") diff --git a/mojo/public/mojo_application.gni b/mojo/public/mojo_application.gni new file mode 100644 index 0000000..2c7ee4a --- /dev/null +++ b/mojo/public/mojo_application.gni @@ -0,0 +1,354 @@ +# 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. + +import("//build/module_args/mojo.gni") +import("mojo.gni") +import("mojo_sdk.gni") + +# Generate a binary mojo application.The parameters of this template are those +# of a shared library. +template("mojo_native_application") { + if (defined(invoker.output_name)) { + base_target_name = invoker.output_name + } else { + base_target_name = target_name + } + + final_target_name = target_name + + if (!is_nacl) { + output = base_target_name + ".mojo" + library_target_name = base_target_name + "_library" + + if (is_linux || is_android) { + library_name = "lib${library_target_name}.so" + } else if (is_win) { + library_name = "${library_target_name}.dll" + } else if (is_mac) { + library_name = "lib${library_target_name}.dylib" + } else { + assert(false, "Platform not supported.") + } + + if (is_android) { + # On android, use the stripped version of the library, because applications + # are always fetched over the network. + library_dir = "${root_out_dir}/lib.stripped" + } else { + library_dir = root_out_dir + } + + shared_library(library_target_name) { + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.cflags_c)) { + cflags_c = invoker.cflags_c + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.cflags_objc)) { + cflags_objc = invoker.cflags_objc + } + if (defined(invoker.cflags_objcc)) { + cflags_objcc = invoker.cflags_objcc + } + if (defined(invoker.defines)) { + defines = invoker.defines + } + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + if (defined(invoker.ldflags)) { + ldflags = invoker.ldflags + } + if (defined(invoker.lib_dirs)) { + lib_dirs = invoker.lib_dirs + } + if (defined(invoker.libs)) { + libs = invoker.libs + } + + data_deps = [] + if (defined(invoker.data_deps)) { + data_deps = invoker.data_deps + } + + # Copy any necessary prebuilt artifacts. + if (mojo_use_prebuilt_mojo_shell) { + data_deps += + [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ] + } + if (mojo_use_prebuilt_network_service) { + data_deps += [ rebase_path("mojo/public/tools:copy_network_service", + ".", + mojo_root) ] + } + + deps = rebase_path([ + "mojo/public/c/system", + "mojo/public/platform/native:system", + ], + ".", + mojo_root) + if (defined(invoker.deps)) { + deps += invoker.deps + } + if (defined(invoker.forward_dependent_configs_from)) { + forward_dependent_configs_from = invoker.forward_dependent_configs_from + } + if (defined(invoker.public_deps)) { + public_deps = invoker.public_deps + } + if (defined(invoker.all_dependent_configs)) { + all_dependent_configs = invoker.all_dependent_configs + } + if (defined(invoker.public_configs)) { + public_configs = invoker.public_configs + } + if (defined(invoker.check_includes)) { + check_includes = invoker.check_includes + } + if (defined(invoker.configs)) { + configs += invoker.configs + } + if (defined(invoker.data)) { + data = invoker.data + } + if (defined(invoker.inputs)) { + inputs = invoker.inputs + } + if (defined(invoker.public)) { + public = invoker.public + } + if (defined(invoker.sources)) { + sources = invoker.sources + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + + visibility = [ ":${final_target_name}" ] + } + + copy(final_target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + deps = [ + ":${library_target_name}", + ] + + sources = [ + "${library_dir}/${library_name}", + ] + outputs = [ + "${root_out_dir}/${output}", + ] + } + } else { + nexe_target_name = base_target_name + "_nexe" + nexe_name = base_target_name + ".nexe" + + output = "${base_target_name}_${target_cpu}.nexe.mojo" + + executable(nexe_target_name) { + output_name = base_target_name + + if (defined(invoker.cflags)) { + cflags = invoker.cflags + } + if (defined(invoker.cflags_c)) { + cflags_c = invoker.cflags_c + } + if (defined(invoker.cflags_cc)) { + cflags_cc = invoker.cflags_cc + } + if (defined(invoker.cflags_objc)) { + cflags_objc = invoker.cflags_objc + } + if (defined(invoker.cflags_objcc)) { + cflags_objcc = invoker.cflags_objcc + } + if (defined(invoker.defines)) { + defines = invoker.defines + } + if (defined(invoker.include_dirs)) { + include_dirs = invoker.include_dirs + } + if (defined(invoker.ldflags)) { + ldflags = invoker.ldflags + } + if (defined(invoker.lib_dirs)) { + lib_dirs = invoker.lib_dirs + } + if (defined(invoker.libs)) { + libs = invoker.libs + } + + data_deps = [] + if (defined(invoker.data_deps)) { + data_deps = invoker.data_deps + } + + # Copy any necessary prebuilt artifacts. + if (mojo_use_prebuilt_mojo_shell) { + data_deps += + [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ] + } + if (mojo_use_prebuilt_network_service) { + data_deps += [ rebase_path("mojo/public/tools:copy_network_service", + ".", + mojo_root) ] + } + + deps = rebase_path([ + "mojo/public/c/system", + "mojo/public/platform/nacl:system", + ], + ".", + mojo_root) + if (defined(invoker.deps)) { + deps += invoker.deps + } + if (defined(invoker.forward_dependent_configs_from)) { + forward_dependent_configs_from = invoker.forward_dependent_configs_from + } + if (defined(invoker.public_deps)) { + public_deps = invoker.public_deps + } + if (defined(invoker.all_dependent_configs)) { + all_dependent_configs = invoker.all_dependent_configs + } + if (defined(invoker.public_configs)) { + public_configs = invoker.public_configs + } + if (defined(invoker.check_includes)) { + check_includes = invoker.check_includes + } + if (defined(invoker.configs)) { + configs += invoker.configs + } + if (defined(invoker.data)) { + data = invoker.data + } + if (defined(invoker.inputs)) { + inputs = invoker.inputs + } + if (defined(invoker.public)) { + public = invoker.public + } + if (defined(invoker.sources)) { + sources = invoker.sources + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + + visibility = [ ":${final_target_name}" ] + } + + action(target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + + script = rebase_path("mojo/public/tools/prepend.py", ".", mojo_root) + + input_path = "${root_out_dir}/${nexe_name}" + inputs = [ + input_path, + ] + + output_path = "${root_build_dir}/${output}" + outputs = [ + output_path, + ] + + deps = [ + ":${nexe_target_name}", + ] + + rebase_input = rebase_path(input_path, root_build_dir) + rebase_output = rebase_path(output_path, root_build_dir) + args = [ + "--input=$rebase_input", + "--output=$rebase_output", + "--line=#!mojo mojo:nacl_content_handler", + ] + } + } +} + +if (is_android) { + # Declares an Android Mojo application consisting of an .so file and a + # corresponding .dex.jar file. + # + # Variables: + # input_so: the .so file to bundle + # input_dex_jar: the .dex.jar file to bundle + # output_name (optional): override for the output file name + template("mojo_android_application") { + assert(defined(invoker.input_so)) + assert(defined(invoker.input_dex_jar)) + + zip_action_name = "${target_name}_zip" + zip_action_output = "$target_gen_dir/${target_name}.zip" + action(zip_action_name) { + script = "//build/android/gn/zip.py" + + inputs = [ + invoker.input_so, + invoker.input_dex_jar, + ] + + output = zip_action_output + outputs = [ + output, + ] + + rebase_inputs = rebase_path(inputs, root_build_dir) + rebase_output = rebase_path(output, root_build_dir) + args = [ + "--inputs=$rebase_inputs", + "--output=$rebase_output", + ] + } + + if (defined(invoker.output_name)) { + mojo_output = "$root_out_dir/" + invoker.output_name + ".mojo" + } else { + mojo_output = "$root_out_dir/" + target_name + ".mojo" + } + + action(target_name) { + script = rebase_path("mojo/public/tools/prepend.py", ".", mojo_root) + + input = zip_action_output + inputs = [ + input, + ] + + output = mojo_output + outputs = [ + output, + ] + + rebase_input = rebase_path(input, root_build_dir) + rebase_output = rebase_path(output, root_build_dir) + args = [ + "--input=$rebase_input", + "--output=$rebase_output", + "--line=#!mojo mojo:android_handler", + ] + } + } +} diff --git a/mojo/public/mojo_sdk.gni b/mojo/public/mojo_sdk.gni new file mode 100644 index 0000000..dbed84c --- /dev/null +++ b/mojo/public/mojo_sdk.gni @@ -0,0 +1,139 @@ +# 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. + +# The absolute path to the directory containing the mojo public SDK (i.e., the +# directory containing mojo/public). The build files within the Mojo public +# SDK use this variable to allow themselves to be parameterized by the location +# of the public SDK within a client repo. +mojo_root = get_path_info("../..", "abspath") + +# Takes as input a "source_set" that includes dependencies that are relative to +# the parent directory of the Mojo public SDK (given in |mojo_sdk_deps|). +# Generates a source_set that has the mojo_sdk_deps added as ordinary deps +# rebased to the current directory. +# By default, restricts the entries that are given in invoker.deps and +# invoker.public_deps to be only within the same file and on a small set of +# whitelisted external dependencies. This check can be elided by setting +# restrict_external_deps to false in the invoker. DO NOT DO THIS in +# //mojo/public. +# +# Example of a mojo_sdk_source_set: +# +# mojo_sdk_source_set("foo") { +# sources = [ +# "foo.h", +# "foo.cc", +# ] +# +# # Same-file deps are specified in the ordinary way. Any external +# dependencies are specified the same way (although in general there should +# be very few of these). +# deps = [ +# ":bar", +# ] +# +# # Mojo SDK deps are specified relative to the containing directory of the +# SDK via mojo_sdk_deps. +# mojo_sdk_deps = [ +# "mojo/public/cpp/bindings", +# "mojo/public/cpp/environment", +# "mojo/public/cpp/system", +# ] +# } +# +template("mojo_sdk_source_set") { + source_set(target_name) { + if (defined(invoker.visibility)) { + visibility = invoker.visibility + } else { + visibility = [ "*" ] + } + if (defined(invoker.mojo_sdk_visibility)) { + foreach(sdk_target, invoker.mojo_sdk_visibility) { + # Check that the SDK target was not mistakenly given as an absolute + # path. + assert(get_path_info(sdk_target, "abspath") != sdk_target) + visibility += [ rebase_path(sdk_target, ".", mojo_root) ] + } + } + + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + + if (defined(invoker.sources)) { + sources = invoker.sources + } + + if (defined(invoker.defines)) { + defines = invoker.defines + } + + if (defined(invoker.libs)) { + libs = invoker.libs + } + + public_configs = + [ rebase_path("mojo/public/build/config:mojo_sdk", ".", mojo_root) ] + if (defined(invoker.public_configs)) { + public_configs += invoker.public_configs + } + + if (defined(invoker.configs)) { + configs += invoker.configs + } + + if (defined(invoker.allow_circular_includes_from)) { + allow_circular_includes_from = invoker.allow_circular_includes_from + } + + if (defined(invoker.public_deps) || defined(invoker.deps)) { + restrict_external_deps = true + if (defined(invoker.restrict_external_deps)) { + restrict_external_deps = invoker.restrict_external_deps + } + } + + public_deps = [] + if (defined(invoker.public_deps)) { + foreach(dep, invoker.public_deps) { + if (restrict_external_deps) { + # The only deps that are not specified relative to the location of + # the Mojo SDK should be on targets within the same file or on a + # whitelisted set of external dependencies. + assert(get_path_info(dep, "dir") == ".") + } + public_deps += [ dep ] + } + } + if (defined(invoker.mojo_sdk_public_deps)) { + foreach(sdk_dep, invoker.mojo_sdk_public_deps) { + # Check that the SDK dep was not mistakenly given as an absolute path. + assert(get_path_info(sdk_dep, "abspath") != sdk_dep) + public_deps += [ rebase_path(sdk_dep, ".", mojo_root) ] + } + } + + deps = [] + if (defined(invoker.deps)) { + foreach(dep, invoker.deps) { + if (restrict_external_deps) { + # The only deps that are not specified relative to the location of + # the Mojo SDK should be on targets within the same file or on a + # whitelisted set of external dependencies. + dep_dir = get_path_info(dep, "dir") + assert(dep_dir == "." || dep == "//testing/gtest") + } + deps += [ dep ] + } + } + if (defined(invoker.mojo_sdk_deps)) { + foreach(sdk_dep, invoker.mojo_sdk_deps) { + # Check that the SDK dep was not mistakenly given as an absolute path. + assert(get_path_info(sdk_dep, "abspath") != sdk_dep) + deps += [ rebase_path(sdk_dep, ".", mojo_root) ] + } + } + } +} diff --git a/mojo/services/BUILD.gn b/mojo/services/BUILD.gn index eea5522..459288f 100644 --- a/mojo/services/BUILD.gn +++ b/mojo/services/BUILD.gn @@ -19,8 +19,14 @@ group("services") { if (!is_component_build) { deps += [ + "//mojo/services/clipboard", "//mojo/services/html_viewer", + "//mojo/services/kiosk_wm", + "//mojo/services/native_viewport", "//mojo/services/network", + "//mojo/services/surfaces", + "//mojo/services/tracing", + "//mojo/services/view_manager", ] # TODO(GYP): Make this work. diff --git a/mojo/services/clipboard/BUILD.gn b/mojo/services/clipboard/BUILD.gn new file mode 100644 index 0000000..cfdcb25 --- /dev/null +++ b/mojo/services/clipboard/BUILD.gn @@ -0,0 +1,44 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/mojo_application.gni") + +mojo_native_application("clipboard") { + sources = [ + "clipboard_standalone_impl.cc", + "clipboard_standalone_impl.h", + "main.cc", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/common", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/bindings:callback", + "//third_party/mojo_services/src/clipboard/public/interfaces", + ] +} + +mojo_native_application("apptests") { + output_name = "clipboard_apptests" + + testonly = true + + sources = [ + "clipboard_apptest.cc", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo_services/src/clipboard/public/interfaces", + ] + + data_deps = [ ":clipboard($default_toolchain)" ] +} diff --git a/mojo/services/clipboard/DEPS b/mojo/services/clipboard/DEPS new file mode 100644 index 0000000..e6505c5 --- /dev/null +++ b/mojo/services/clipboard/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+mojo/application", + "+third_party/mojo_services/src/clipboard", +] diff --git a/mojo/services/clipboard/clipboard_apptest.cc b/mojo/services/clipboard/clipboard_apptest.cc new file mode 100644 index 0000000..b6a2fad --- /dev/null +++ b/mojo/services/clipboard/clipboard_apptest.cc @@ -0,0 +1,156 @@ +// 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 "base/bind.h" +#include "base/run_loop.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "third_party/mojo_services/src/clipboard/public/interfaces/clipboard.mojom.h" + +using mojo::Array; +using mojo::Clipboard; +using mojo::Map; +using mojo::String; + +namespace { + +void CopyUint64AndEndRunloop(uint64_t* output, + base::RunLoop* run_loop, + uint64_t input) { + *output = input; + run_loop->Quit(); +} + +void CopyStringAndEndRunloop(std::string* output, + bool* string_is_null, + base::RunLoop* run_loop, + const Array<uint8_t>& input) { + *string_is_null = input.is_null(); + *output = input.is_null() ? "" : input.To<std::string>(); + run_loop->Quit(); +} + +void CopyVectorStringAndEndRunloop(std::vector<std::string>* output, + base::RunLoop* run_loop, + const Array<String>& input) { + *output = input.To<std::vector<std::string> >(); + run_loop->Quit(); +} + +const char* kUninitialized = "Uninitialized data"; +const char* kPlainTextData = "Some plain data"; +const char* kHtmlData = "<html>data</html>"; + +} // namespace + +namespace clipboard { + +class ClipboardAppTest : public mojo::test::ApplicationTestBase { + public: + ClipboardAppTest() : ApplicationTestBase() {} + ~ClipboardAppTest() override {} + + void SetUp() override { + mojo::test::ApplicationTestBase::SetUp(); + application_impl()->ConnectToService("mojo:clipboard", &clipboard_); + } + + uint64_t GetSequenceNumber() { + base::RunLoop run_loop; + uint64_t sequence_num = 999999; + clipboard_->GetSequenceNumber( + Clipboard::TYPE_COPY_PASTE, + base::Bind(&CopyUint64AndEndRunloop, &sequence_num, &run_loop)); + run_loop.Run(); + return sequence_num; + } + + std::vector<std::string> GetAvailableFormatMimeTypes() { + base::RunLoop run_loop; + std::vector<std::string> types; + types.push_back(kUninitialized); + clipboard_->GetAvailableMimeTypes( + Clipboard::TYPE_COPY_PASTE, + base::Bind(&CopyVectorStringAndEndRunloop, &types, &run_loop)); + run_loop.Run(); + return types; + } + + bool GetDataOfType(const std::string& mime_type, std::string* data) { + base::RunLoop run_loop; + bool is_null = false; + clipboard_->ReadMimeType( + Clipboard::TYPE_COPY_PASTE, mime_type, + base::Bind(&CopyStringAndEndRunloop, data, &is_null, &run_loop)); + run_loop.Run(); + return !is_null; + } + + void SetStringText(const std::string& data) { + Map<String, Array<uint8_t>> mime_data; + mime_data[Clipboard::MIME_TYPE_TEXT] = Array<uint8_t>::From(data); + clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE, + mime_data.Pass()); + } + + protected: + mojo::ClipboardPtr clipboard_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardAppTest); +}; + +TEST_F(ClipboardAppTest, EmptyClipboardOK) { + EXPECT_EQ(0ul, GetSequenceNumber()); + EXPECT_TRUE(GetAvailableFormatMimeTypes().empty()); + std::string data; + EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); +} + +TEST_F(ClipboardAppTest, CanReadBackText) { + std::string data; + EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); + EXPECT_EQ(0ul, GetSequenceNumber()); + + SetStringText(kPlainTextData); + EXPECT_EQ(1ul, GetSequenceNumber()); + + EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); + EXPECT_EQ(kPlainTextData, data); +} + +TEST_F(ClipboardAppTest, CanSetMultipleDataTypesAtOnce) { + Map<String, Array<uint8_t>> mime_data; + mime_data[Clipboard::MIME_TYPE_TEXT] = + Array<uint8_t>::From(std::string(kPlainTextData)); + mime_data[Clipboard::MIME_TYPE_HTML] = + Array<uint8_t>::From(std::string(kHtmlData)); + + clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE, mime_data.Pass()); + + EXPECT_EQ(1ul, GetSequenceNumber()); + + std::string data; + EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); + EXPECT_EQ(kPlainTextData, data); + EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_HTML, &data)); + EXPECT_EQ(kHtmlData, data); +} + +TEST_F(ClipboardAppTest, CanClearClipboardWithZeroArray) { + std::string data; + SetStringText(kPlainTextData); + EXPECT_EQ(1ul, GetSequenceNumber()); + + EXPECT_TRUE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); + EXPECT_EQ(kPlainTextData, data); + + Map<String, Array<uint8_t>> mime_data; + clipboard_->WriteClipboardData(Clipboard::TYPE_COPY_PASTE, mime_data.Pass()); + + EXPECT_EQ(2ul, GetSequenceNumber()); + EXPECT_FALSE(GetDataOfType(Clipboard::MIME_TYPE_TEXT, &data)); +} + +} // namespace clipboard diff --git a/mojo/services/clipboard/clipboard_standalone_impl.cc b/mojo/services/clipboard/clipboard_standalone_impl.cc new file mode 100644 index 0000000..e783af7 --- /dev/null +++ b/mojo/services/clipboard/clipboard_standalone_impl.cc @@ -0,0 +1,89 @@ +// 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 "mojo/services/clipboard/clipboard_standalone_impl.h" + +#include <string.h> + +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/string.h" + +using mojo::Array; +using mojo::Map; +using mojo::String; + +namespace clipboard { + +// ClipboardData contains data copied to the Clipboard for a variety of formats. +// It mostly just provides APIs to cleanly access and manipulate this data. +class ClipboardStandaloneImpl::ClipboardData { + public: + ClipboardData() {} + ~ClipboardData() {} + + Array<String> GetMimeTypes() const { + Array<String> types(data_types_.size()); + int i = 0; + for (auto it = data_types_.begin(); it != data_types_.end(); ++it, ++i) + types[i] = it.GetKey(); + + return types.Pass(); + } + + void SetData(Map<String, Array<uint8_t>> data) { data_types_ = data.Pass(); } + + void GetData(const String& mime_type, Array<uint8_t>* data) const { + auto it = data_types_.find(mime_type); + if (it != data_types_.end()) + *data = it.GetValue().Clone(); + } + + private: + Map<String, Array<uint8_t>> data_types_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardData); +}; + +ClipboardStandaloneImpl::ClipboardStandaloneImpl( + mojo::InterfaceRequest<mojo::Clipboard> request) + : binding_(this, request.Pass()) { + for (int i = 0; i < kNumClipboards; ++i) { + sequence_number_[i] = 0; + clipboard_state_[i].reset(new ClipboardData); + } +} + +ClipboardStandaloneImpl::~ClipboardStandaloneImpl() { +} + +void ClipboardStandaloneImpl::GetSequenceNumber( + Clipboard::Type clipboard_type, + const mojo::Callback<void(uint64_t)>& callback) { + callback.Run(sequence_number_[clipboard_type]); +} + +void ClipboardStandaloneImpl::GetAvailableMimeTypes( + Clipboard::Type clipboard_type, + const mojo::Callback<void(Array<String>)>& callback) { + callback.Run(clipboard_state_[clipboard_type]->GetMimeTypes().Pass()); +} + +void ClipboardStandaloneImpl::ReadMimeType( + Clipboard::Type clipboard_type, + const String& mime_type, + const mojo::Callback<void(Array<uint8_t>)>& callback) { + Array<uint8_t> mime_data; + clipboard_state_[clipboard_type]->GetData(mime_type, &mime_data); + callback.Run(mime_data.Pass()); +} + +void ClipboardStandaloneImpl::WriteClipboardData( + Clipboard::Type clipboard_type, + Map<String, Array<uint8_t>> data) { + sequence_number_[clipboard_type]++; + clipboard_state_[clipboard_type]->SetData(data.Pass()); +} + +} // namespace clipboard diff --git a/mojo/services/clipboard/clipboard_standalone_impl.h b/mojo/services/clipboard/clipboard_standalone_impl.h new file mode 100644 index 0000000..6c5d828 --- /dev/null +++ b/mojo/services/clipboard/clipboard_standalone_impl.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_ +#define SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_ + +#include <base/memory/scoped_ptr.h> +#include <string> + +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/clipboard/public/interfaces/clipboard.mojom.h" + +namespace clipboard { + +// Stub clipboard implementation. +// +// Eventually, we'll actually want to interact with the system clipboard, but +// that's hard today because the system clipboard is asynchronous (on X11), the +// ui::Clipboard interface is synchronous (which is what we'd use), mojo is +// asynchronous across processes, and the WebClipboard interface is synchronous +// (which is at least tractable). +class ClipboardStandaloneImpl : public mojo::Clipboard { + public: + // mojo::Clipboard exposes three possible clipboards. + static const int kNumClipboards = 3; + + explicit ClipboardStandaloneImpl( + mojo::InterfaceRequest<mojo::Clipboard> request); + ~ClipboardStandaloneImpl() override; + + // mojo::Clipboard implementation. + void GetSequenceNumber( + mojo::Clipboard::Type clipboard_type, + const mojo::Callback<void(uint64_t)>& callback) override; + void GetAvailableMimeTypes( + mojo::Clipboard::Type clipboard_types, + const mojo::Callback<void(mojo::Array<mojo::String>)>& callback) override; + void ReadMimeType( + mojo::Clipboard::Type clipboard_type, + const mojo::String& mime_type, + const mojo::Callback<void(mojo::Array<uint8_t>)>& callback) override; + void WriteClipboardData( + mojo::Clipboard::Type clipboard_type, + mojo::Map<mojo::String, mojo::Array<uint8_t>> data) override; + + private: + uint64_t sequence_number_[kNumClipboards]; + + // Internal struct which stores the current state of the clipboard. + class ClipboardData; + + // The current clipboard state. This is what is read from. + scoped_ptr<ClipboardData> clipboard_state_[kNumClipboards]; + mojo::StrongBinding<mojo::Clipboard> binding_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardStandaloneImpl); +}; + +} // namespace clipboard + +#endif // SERVICES_CLIPBOARD_CLIPBOARD_STANDALONE_IMPL_H_ diff --git a/mojo/services/clipboard/main.cc b/mojo/services/clipboard/main.cc new file mode 100644 index 0000000..0c5e17a --- /dev/null +++ b/mojo/services/clipboard/main.cc @@ -0,0 +1,37 @@ +// 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 "mojo/application/application_runner_chromium.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/services/clipboard/clipboard_standalone_impl.h" + +class Delegate : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory<mojo::Clipboard> { + public: + Delegate() {} + ~Delegate() override {} + + // mojo::ApplicationDelegate implementation. + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + connection->AddService(this); + return true; + } + + // mojo::InterfaceFactory<mojo::Clipboard> implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::Clipboard> request) override { + // TODO(erg): Write native implementations of the clipboard. For now, we + // just build a clipboard which doesn't interact with the system. + new clipboard::ClipboardStandaloneImpl(request.Pass()); + } +}; + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new Delegate); + return runner.Run(shell_handle); +} diff --git a/mojo/services/gles2/BUILD.gn b/mojo/services/gles2/BUILD.gn new file mode 100644 index 0000000..a34b9b3 --- /dev/null +++ b/mojo/services/gles2/BUILD.gn @@ -0,0 +1,59 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") + +source_set("gles2") { + visibility = [ + "//mojo/shell:lib", # For android + "//mojo/services/native_viewport:*", + ] + + sources = [ + "command_buffer_driver.cc", + "command_buffer_driver.h", + "command_buffer_impl.cc", + "command_buffer_impl.h", + "gpu_state.cc", + "gpu_state.h", + "gpu_impl.cc", + "gpu_impl.h", + ] + + public_deps = [ + ":lib", + ] + deps = [ + "//base", + "//gpu/command_buffer/service", + "//mojo/converters/geometry", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/gpu/public/interfaces", + "//ui/gfx", + "//ui/gfx/geometry", + "//ui/gl", + ] + + include_dirs = [ "../.." ] +} + +source_set("lib") { + sources = [ + "command_buffer_type_conversions.cc", + "command_buffer_type_conversions.h", + "mojo_buffer_backing.cc", + "mojo_buffer_backing.h", + ] + + deps = [ + "//base", + "//gpu/command_buffer/common", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/mojo_services/src/gpu/public/interfaces", + ] + + include_dirs = [ "../.." ] +} diff --git a/mojo/services/gles2/DEPS b/mojo/services/gles2/DEPS new file mode 100644 index 0000000..f34d3eb --- /dev/null +++ b/mojo/services/gles2/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+gpu", + "+mojo/converters", + "+third_party/mojo_services/src/geometry", + "+third_party/mojo_services/src/gpu", + "+ui", +] diff --git a/mojo/services/gles2/command_buffer_driver.cc b/mojo/services/gles2/command_buffer_driver.cc new file mode 100644 index 0000000..3ae617b --- /dev/null +++ b/mojo/services/gles2/command_buffer_driver.cc @@ -0,0 +1,250 @@ +// 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. + +#include "mojo/services/gles2/command_buffer_driver.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/shared_memory.h" +#include "gpu/command_buffer/common/constants.h" +#include "gpu/command_buffer/common/value_state.h" +#include "gpu/command_buffer/service/command_buffer_service.h" +#include "gpu/command_buffer/service/context_group.h" +#include "gpu/command_buffer/service/gles2_cmd_decoder.h" +#include "gpu/command_buffer/service/gpu_scheduler.h" +#include "gpu/command_buffer/service/image_manager.h" +#include "gpu/command_buffer/service/mailbox_manager.h" +#include "gpu/command_buffer/service/memory_tracking.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "gpu/command_buffer/service/valuebuffer_manager.h" +#include "mojo/services/gles2/command_buffer_type_conversions.h" +#include "mojo/services/gles2/mojo_buffer_backing.h" +#include "ui/gfx/vsync_provider.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_surface.h" + +namespace gles2 { + +namespace { + +class MemoryTrackerStub : public gpu::gles2::MemoryTracker { + public: + MemoryTrackerStub() {} + + void TrackMemoryAllocatedChange( + size_t old_size, + size_t new_size, + gpu::gles2::MemoryTracker::Pool pool) override {} + + bool EnsureGPUMemoryAvailable(size_t size_needed) override { return true; }; + + private: + ~MemoryTrackerStub() override {} + + DISALLOW_COPY_AND_ASSIGN(MemoryTrackerStub); +}; + +} // anonymous namespace + +CommandBufferDriver::Client::~Client() { +} + +CommandBufferDriver::CommandBufferDriver( + gfx::GLShareGroup* share_group, + gpu::gles2::MailboxManager* mailbox_manager, + gpu::SyncPointManager* sync_point_manager) + : CommandBufferDriver(gfx::kNullAcceleratedWidget, + share_group, + mailbox_manager, + sync_point_manager) { +} + +CommandBufferDriver::CommandBufferDriver( + gfx::AcceleratedWidget widget, + gfx::GLShareGroup* share_group, + gpu::gles2::MailboxManager* mailbox_manager, + gpu::SyncPointManager* sync_point_manager) + : client_(nullptr), + widget_(widget), + share_group_(share_group), + mailbox_manager_(mailbox_manager), + sync_point_manager_(sync_point_manager), + weak_factory_(this) { +} + +CommandBufferDriver::~CommandBufferDriver() { + if (decoder_) { + bool have_context = decoder_->MakeCurrent(); + decoder_->Destroy(have_context); + } +} + +void CommandBufferDriver::Initialize( + mojo::CommandBufferSyncClientPtr sync_client, + mojo::CommandBufferLostContextObserverPtr loss_observer, + mojo::ScopedSharedBufferHandle shared_state) { + sync_client_ = sync_client.Pass(); + loss_observer_ = loss_observer.Pass(); + bool success = DoInitialize(shared_state.Pass()); + mojo::GpuCapabilitiesPtr capabilities = + success ? mojo::GpuCapabilities::From(decoder_->GetCapabilities()) + : mojo::GpuCapabilities::New(); + sync_client_->DidInitialize(success, capabilities.Pass()); +} + +bool CommandBufferDriver::DoInitialize( + mojo::ScopedSharedBufferHandle shared_state) { + if (widget_ == gfx::kNullAcceleratedWidget) + surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1)); + else { + surface_ = gfx::GLSurface::CreateViewGLSurface(widget_); + if (auto vsync_provider = surface_->GetVSyncProvider()) { + vsync_provider->GetVSyncParameters( + base::Bind(&CommandBufferDriver::OnUpdateVSyncParameters, + weak_factory_.GetWeakPtr())); + } + } + + if (!surface_.get()) + return false; + + // TODO(piman): virtual contexts, gpu preference. + context_ = gfx::GLContext::CreateGLContext(share_group_.get(), surface_.get(), + gfx::PreferIntegratedGpu); + if (!context_.get()) + return false; + + if (!context_->MakeCurrent(surface_.get())) + return false; + + // TODO(piman): ShaderTranslatorCache is currently per-ContextGroup but + // only needs to be per-thread. + bool bind_generates_resource = false; + scoped_refptr<gpu::gles2::ContextGroup> context_group = + new gpu::gles2::ContextGroup( + mailbox_manager_.get(), new MemoryTrackerStub, + new gpu::gles2::ShaderTranslatorCache, nullptr, nullptr, nullptr, + bind_generates_resource); + + command_buffer_.reset( + new gpu::CommandBufferService(context_group->transfer_buffer_manager())); + bool result = command_buffer_->Initialize(); + DCHECK(result); + + decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group.get())); + scheduler_.reset(new gpu::GpuScheduler(command_buffer_.get(), decoder_.get(), + decoder_.get())); + decoder_->set_engine(scheduler_.get()); + decoder_->SetResizeCallback( + base::Bind(&CommandBufferDriver::OnResize, base::Unretained(this))); + decoder_->SetWaitSyncPointCallback(base::Bind( + &CommandBufferDriver::OnWaitSyncPoint, base::Unretained(this))); + + gpu::gles2::DisallowedFeatures disallowed_features; + + // TODO(piman): attributes. + std::vector<int32> attrib_vector; + if (!decoder_->Initialize(surface_, context_, false /* offscreen */, + gfx::Size(1, 1), disallowed_features, + attrib_vector)) + return false; + + command_buffer_->SetPutOffsetChangeCallback(base::Bind( + &gpu::GpuScheduler::PutChanged, base::Unretained(scheduler_.get()))); + command_buffer_->SetGetBufferChangeCallback(base::Bind( + &gpu::GpuScheduler::SetGetBuffer, base::Unretained(scheduler_.get()))); + command_buffer_->SetParseErrorCallback( + base::Bind(&CommandBufferDriver::OnParseError, base::Unretained(this))); + + // TODO(piman): other callbacks + + const size_t kSize = sizeof(gpu::CommandBufferSharedState); + scoped_ptr<gpu::BufferBacking> backing( + gles2::MojoBufferBacking::Create(shared_state.Pass(), kSize)); + if (!backing) + return false; + + command_buffer_->SetSharedStateBuffer(backing.Pass()); + return true; +} + +void CommandBufferDriver::SetGetBuffer(int32_t buffer) { + command_buffer_->SetGetBuffer(buffer); +} + +void CommandBufferDriver::Flush(int32_t put_offset) { + if (!context_->MakeCurrent(surface_.get())) { + DLOG(WARNING) << "Context lost"; + OnContextLost(gpu::error::kUnknown); + return; + } + command_buffer_->Flush(put_offset); +} + +void CommandBufferDriver::MakeProgress(int32_t last_get_offset) { + // TODO(piman): handle out-of-order. + sync_client_->DidMakeProgress( + mojo::CommandBufferState::From(command_buffer_->GetLastState())); +} + +void CommandBufferDriver::RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + // Take ownership of the memory and map it into this process. + // This validates the size. + scoped_ptr<gpu::BufferBacking> backing( + gles2::MojoBufferBacking::Create(transfer_buffer.Pass(), size)); + if (!backing) { + DVLOG(0) << "Failed to map shared memory."; + return; + } + command_buffer_->RegisterTransferBuffer(id, backing.Pass()); +} + +void CommandBufferDriver::DestroyTransferBuffer(int32_t id) { + command_buffer_->DestroyTransferBuffer(id); +} + +void CommandBufferDriver::Echo(const mojo::Callback<void()>& callback) { + callback.Run(); +} + +void CommandBufferDriver::OnParseError() { + gpu::CommandBuffer::State state = command_buffer_->GetLastState(); + OnContextLost(state.context_lost_reason); +} + +void CommandBufferDriver::OnResize(gfx::Size size, float scale_factor) { + surface_->Resize(size); +} + +bool CommandBufferDriver::OnWaitSyncPoint(uint32_t sync_point) { + if (!sync_point) + return true; + if (sync_point_manager_->IsSyncPointRetired(sync_point)) + return true; + scheduler_->SetScheduled(false); + sync_point_manager_->AddSyncPointCallback( + sync_point, base::Bind(&CommandBufferDriver::OnSyncPointRetired, + weak_factory_.GetWeakPtr())); + return scheduler_->IsScheduled(); +} + +void CommandBufferDriver::OnSyncPointRetired() { + scheduler_->SetScheduled(true); +} + +void CommandBufferDriver::OnContextLost(uint32_t reason) { + loss_observer_->DidLoseContext(reason); + client_->DidLoseContext(); +} + +void CommandBufferDriver::OnUpdateVSyncParameters( + const base::TimeTicks timebase, + const base::TimeDelta interval) { + client_->UpdateVSyncParameters(timebase, interval); +} + +} // namespace gles2 diff --git a/mojo/services/gles2/command_buffer_driver.h b/mojo/services/gles2/command_buffer_driver.h new file mode 100644 index 0000000..5006567 --- /dev/null +++ b/mojo/services/gles2/command_buffer_driver.h @@ -0,0 +1,103 @@ +// 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. + +#ifndef SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_ +#define SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/timer/timer.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_widget_types.h" + +namespace gpu { +class CommandBufferService; +class GpuScheduler; +class GpuControlService; +class SyncPointManager; +namespace gles2 { +class GLES2Decoder; +class MailboxManager; +} +} + +namespace gfx { +class GLContext; +class GLShareGroup; +class GLSurface; +} + +namespace gles2 { + +class CommandBufferDriver { + public: + class Client { + public: + virtual ~Client(); + virtual void UpdateVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval) = 0; + virtual void DidLoseContext() = 0; + }; + // Offscreen. + CommandBufferDriver(gfx::GLShareGroup* share_group, + gpu::gles2::MailboxManager* mailbox_manager, + gpu::SyncPointManager* sync_point_manager); + // Onscreen. + CommandBufferDriver(gfx::AcceleratedWidget widget, + gfx::GLShareGroup* share_group, + gpu::gles2::MailboxManager* mailbox_manager, + gpu::SyncPointManager* sync_point_manager); + ~CommandBufferDriver(); + + void set_client(scoped_ptr<Client> client) { client_ = client.Pass(); } + + void Initialize(mojo::CommandBufferSyncClientPtr sync_client, + mojo::CommandBufferLostContextObserverPtr loss_observer, + mojo::ScopedSharedBufferHandle shared_state); + void SetGetBuffer(int32_t buffer); + void Flush(int32_t put_offset); + void MakeProgress(int32_t last_get_offset); + void RegisterTransferBuffer(int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size); + void DestroyTransferBuffer(int32_t id); + void Echo(const mojo::Callback<void()>& callback); + + private: + bool DoInitialize(mojo::ScopedSharedBufferHandle shared_state); + void OnResize(gfx::Size size, float scale_factor); + bool OnWaitSyncPoint(uint32_t sync_point); + void OnSyncPointRetired(); + void OnParseError(); + void OnContextLost(uint32_t reason); + void OnUpdateVSyncParameters(const base::TimeTicks timebase, + const base::TimeDelta interval); + + scoped_ptr<Client> client_; + mojo::CommandBufferSyncClientPtr sync_client_; + mojo::CommandBufferLostContextObserverPtr loss_observer_; + gfx::AcceleratedWidget widget_; + scoped_ptr<gpu::CommandBufferService> command_buffer_; + scoped_ptr<gpu::gles2::GLES2Decoder> decoder_; + scoped_ptr<gpu::GpuScheduler> scheduler_; + scoped_refptr<gfx::GLContext> context_; + scoped_refptr<gfx::GLSurface> surface_; + scoped_refptr<gfx::GLShareGroup> share_group_; + scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_; + scoped_refptr<gpu::SyncPointManager> sync_point_manager_; + + scoped_refptr<base::SingleThreadTaskRunner> context_lost_task_runner_; + base::Callback<void(int32_t)> context_lost_callback_; + + base::WeakPtrFactory<CommandBufferDriver> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferDriver); +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_COMMAND_BUFFER_DRIVER_H_ diff --git a/mojo/services/gles2/command_buffer_impl.cc b/mojo/services/gles2/command_buffer_impl.cc new file mode 100644 index 0000000..b89279f --- /dev/null +++ b/mojo/services/gles2/command_buffer_impl.cc @@ -0,0 +1,160 @@ +// 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 "mojo/services/gles2/command_buffer_impl.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "mojo/services/gles2/command_buffer_driver.h" + +namespace gles2 { +namespace { +void DestroyDriver(scoped_ptr<CommandBufferDriver> driver) { + // Just let ~scoped_ptr run. +} + +void RunCallback(const mojo::Callback<void()>& callback) { + callback.Run(); +} + +class CommandBufferDriverClientImpl : public CommandBufferDriver::Client { + public: + CommandBufferDriverClientImpl( + base::WeakPtr<CommandBufferImpl> command_buffer, + scoped_refptr<base::SingleThreadTaskRunner> control_task_runner) + : command_buffer_(command_buffer), + control_task_runner_(control_task_runner) {} + + private: + void UpdateVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval) override { + control_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferImpl::UpdateVSyncParameters, + command_buffer_, timebase, interval)); + } + + void DidLoseContext() override { + control_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferImpl::DidLoseContext, + command_buffer_)); + } + + base::WeakPtr<CommandBufferImpl> command_buffer_; + scoped_refptr<base::SingleThreadTaskRunner> control_task_runner_; +}; +} + +CommandBufferImpl::CommandBufferImpl( + mojo::InterfaceRequest<mojo::CommandBuffer> request, + mojo::ViewportParameterListenerPtr listener, + scoped_refptr<base::SingleThreadTaskRunner> control_task_runner, + gpu::SyncPointManager* sync_point_manager, + scoped_ptr<CommandBufferDriver> driver) + : sync_point_manager_(sync_point_manager), + driver_task_runner_(base::MessageLoop::current()->task_runner()), + driver_(driver.Pass()), + viewport_parameter_listener_(listener.Pass()), + binding_(this), + weak_factory_(this) { + driver_->set_client(make_scoped_ptr(new CommandBufferDriverClientImpl( + weak_factory_.GetWeakPtr(), control_task_runner))); + + control_task_runner->PostTask( + FROM_HERE, base::Bind(&CommandBufferImpl::BindToRequest, + base::Unretained(this), base::Passed(&request))); +} + +CommandBufferImpl::~CommandBufferImpl() { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&DestroyDriver, base::Passed(&driver_))); +} + +void CommandBufferImpl::Initialize( + mojo::CommandBufferSyncClientPtr sync_client, + mojo::CommandBufferSyncPointClientPtr sync_point_client, + mojo::CommandBufferLostContextObserverPtr loss_observer, + mojo::ScopedSharedBufferHandle shared_state) { + sync_point_client_ = sync_point_client.Pass(); + driver_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CommandBufferDriver::Initialize, + base::Unretained(driver_.get()), base::Passed(&sync_client), + base::Passed(&loss_observer), + base::Passed(&shared_state))); +} + +void CommandBufferImpl::SetGetBuffer(int32_t buffer) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferDriver::SetGetBuffer, + base::Unretained(driver_.get()), buffer)); +} + +void CommandBufferImpl::Flush(int32_t put_offset) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferDriver::Flush, + base::Unretained(driver_.get()), put_offset)); +} + +void CommandBufferImpl::MakeProgress(int32_t last_get_offset) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferDriver::MakeProgress, + base::Unretained(driver_.get()), last_get_offset)); +} + +void CommandBufferImpl::RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferDriver::RegisterTransferBuffer, + base::Unretained(driver_.get()), id, + base::Passed(&transfer_buffer), size)); +} + +void CommandBufferImpl::DestroyTransferBuffer(int32_t id) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&CommandBufferDriver::DestroyTransferBuffer, + base::Unretained(driver_.get()), id)); +} + +void CommandBufferImpl::InsertSyncPoint(bool retire) { + uint32_t sync_point = sync_point_manager_->GenerateSyncPoint(); + sync_point_client_->DidInsertSyncPoint(sync_point); + if (retire) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&gpu::SyncPointManager::RetireSyncPoint, + sync_point_manager_, sync_point)); + } +} + +void CommandBufferImpl::RetireSyncPoint(uint32_t sync_point) { + driver_task_runner_->PostTask( + FROM_HERE, base::Bind(&gpu::SyncPointManager::RetireSyncPoint, + sync_point_manager_, sync_point)); +} + +void CommandBufferImpl::Echo(const mojo::Callback<void()>& callback) { + driver_task_runner_->PostTaskAndReply(FROM_HERE, base::Bind(&base::DoNothing), + base::Bind(&RunCallback, callback)); +} + +void CommandBufferImpl::BindToRequest( + mojo::InterfaceRequest<mojo::CommandBuffer> request) { + binding_.Bind(request.Pass()); +} + +void CommandBufferImpl::DidLoseContext() { + binding_.OnConnectionError(); +} + +void CommandBufferImpl::UpdateVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval) { + if (!viewport_parameter_listener_) + return; + viewport_parameter_listener_->OnVSyncParametersUpdated( + timebase.ToInternalValue(), interval.ToInternalValue()); +} + +} // namespace gles2 diff --git a/mojo/services/gles2/command_buffer_impl.h b/mojo/services/gles2/command_buffer_impl.h new file mode 100644 index 0000000..61b33a2 --- /dev/null +++ b/mojo/services/gles2/command_buffer_impl.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ +#define SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h" + +namespace gpu { +class SyncPointManager; +} + +namespace gles2 { +class CommandBufferDriver; + +// This class listens to the CommandBuffer message pipe on a low-latency thread +// so that we can insert sync points without blocking on the GL driver. It +// forwards most method calls to the CommandBufferDriver, which runs on the +// same thread as the native viewport. +class CommandBufferImpl : public mojo::CommandBuffer { + public: + CommandBufferImpl( + mojo::InterfaceRequest<CommandBuffer> request, + mojo::ViewportParameterListenerPtr listener, + scoped_refptr<base::SingleThreadTaskRunner> control_task_runner, + gpu::SyncPointManager* sync_point_manager, + scoped_ptr<CommandBufferDriver> driver); + ~CommandBufferImpl() override; + + void Initialize(mojo::CommandBufferSyncClientPtr sync_client, + mojo::CommandBufferSyncPointClientPtr sync_point_client, + mojo::CommandBufferLostContextObserverPtr loss_observer, + mojo::ScopedSharedBufferHandle shared_state) override; + void SetGetBuffer(int32_t buffer) override; + void Flush(int32_t put_offset) override; + void MakeProgress(int32_t last_get_offset) override; + void RegisterTransferBuffer(int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) override; + void DestroyTransferBuffer(int32_t id) override; + void InsertSyncPoint(bool retire) override; + void RetireSyncPoint(uint32_t sync_point) override; + void Echo(const mojo::Callback<void()>& callback) override; + + void DidLoseContext(); + void UpdateVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval); + + private: + void BindToRequest(mojo::InterfaceRequest<CommandBuffer> request); + + scoped_refptr<gpu::SyncPointManager> sync_point_manager_; + scoped_refptr<base::SingleThreadTaskRunner> driver_task_runner_; + scoped_ptr<CommandBufferDriver> driver_; + mojo::CommandBufferSyncPointClientPtr sync_point_client_; + mojo::ViewportParameterListenerPtr viewport_parameter_listener_; + mojo::StrongBinding<CommandBuffer> binding_; + + base::WeakPtrFactory<CommandBufferImpl> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(CommandBufferImpl); +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_COMMAND_BUFFER_IMPL_H_ diff --git a/mojo/services/gles2/command_buffer_type_conversions.cc b/mojo/services/gles2/command_buffer_type_conversions.cc new file mode 100644 index 0000000..9c068be --- /dev/null +++ b/mojo/services/gles2/command_buffer_type_conversions.cc @@ -0,0 +1,170 @@ +// 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 "mojo/services/gles2/command_buffer_type_conversions.h" + +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" + +namespace mojo { + +CommandBufferStatePtr +TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State>::Convert( + const gpu::CommandBuffer::State& input) { + CommandBufferStatePtr result(CommandBufferState::New()); + result->get_offset = input.get_offset; + result->token = input.token; + result->error = input.error; + result->context_lost_reason = input.context_lost_reason; + result->generation = input.generation; + return result.Pass(); +} + +gpu::CommandBuffer::State +TypeConverter<gpu::CommandBuffer::State, CommandBufferStatePtr>::Convert( + const CommandBufferStatePtr& input) { + gpu::CommandBuffer::State state; + state.get_offset = input->get_offset; + state.token = input->token; + state.error = static_cast<gpu::error::Error>(input->error); + state.context_lost_reason = + static_cast<gpu::error::ContextLostReason>(input->context_lost_reason); + state.generation = input->generation; + return state; +} + +GpuShaderPrecisionPtr +TypeConverter<GpuShaderPrecisionPtr, gpu::Capabilities::ShaderPrecision>:: + Convert(const gpu::Capabilities::ShaderPrecision& input) { + GpuShaderPrecisionPtr result(GpuShaderPrecision::New()); + result->min_range = input.min_range; + result->max_range = input.max_range; + result->precision = input.precision; + return result.Pass(); +} + +gpu::Capabilities::ShaderPrecision TypeConverter< + gpu::Capabilities::ShaderPrecision, + GpuShaderPrecisionPtr>::Convert(const GpuShaderPrecisionPtr& input) { + gpu::Capabilities::ShaderPrecision result; + result.min_range = input->min_range; + result.max_range = input->max_range; + result.precision = input->precision; + return result; +} + +GpuPerStagePrecisionsPtr +TypeConverter<GpuPerStagePrecisionsPtr, gpu::Capabilities::PerStagePrecisions>:: + Convert(const gpu::Capabilities::PerStagePrecisions& input) { + GpuPerStagePrecisionsPtr result(GpuPerStagePrecisions::New()); + result->low_int = GpuShaderPrecision::From(input.low_int); + result->medium_int = GpuShaderPrecision::From(input.medium_int); + result->high_int = GpuShaderPrecision::From(input.high_int); + result->low_float = GpuShaderPrecision::From(input.low_float); + result->medium_float = GpuShaderPrecision::From(input.medium_float); + result->high_float = GpuShaderPrecision::From(input.high_float); + return result.Pass(); +} + +gpu::Capabilities::PerStagePrecisions TypeConverter< + gpu::Capabilities::PerStagePrecisions, + GpuPerStagePrecisionsPtr>::Convert(const GpuPerStagePrecisionsPtr& input) { + gpu::Capabilities::PerStagePrecisions result; + result.low_int = input->low_int.To<gpu::Capabilities::ShaderPrecision>(); + result.medium_int = + input->medium_int.To<gpu::Capabilities::ShaderPrecision>(); + result.high_int = input->high_int.To<gpu::Capabilities::ShaderPrecision>(); + result.low_float = input->low_float.To<gpu::Capabilities::ShaderPrecision>(); + result.medium_float = + input->medium_float.To<gpu::Capabilities::ShaderPrecision>(); + result.high_float = + input->high_float.To<gpu::Capabilities::ShaderPrecision>(); + return result; +} + +GpuCapabilitiesPtr +TypeConverter<GpuCapabilitiesPtr, gpu::Capabilities>::Convert( + const gpu::Capabilities& input) { + GpuCapabilitiesPtr result(GpuCapabilities::New()); + result->vertex_shader_precisions = + GpuPerStagePrecisions::From(input.vertex_shader_precisions); + result->fragment_shader_precisions = + GpuPerStagePrecisions::From(input.fragment_shader_precisions); + result->max_combined_texture_image_units = + input.max_combined_texture_image_units; + result->max_cube_map_texture_size = input.max_cube_map_texture_size; + result->max_fragment_uniform_vectors = input.max_fragment_uniform_vectors; + result->max_renderbuffer_size = input.max_renderbuffer_size; + result->max_texture_image_units = input.max_texture_image_units; + result->max_texture_size = input.max_texture_size; + result->max_varying_vectors = input.max_varying_vectors; + result->max_vertex_attribs = input.max_vertex_attribs; + result->max_vertex_texture_image_units = input.max_vertex_texture_image_units; + result->max_vertex_uniform_vectors = input.max_vertex_uniform_vectors; + result->num_compressed_texture_formats = input.num_compressed_texture_formats; + result->num_shader_binary_formats = input.num_shader_binary_formats; + result->bind_generates_resource_chromium = + input.bind_generates_resource_chromium; + result->post_sub_buffer = input.post_sub_buffer; + result->egl_image_external = input.egl_image_external; + result->texture_format_bgra8888 = input.texture_format_bgra8888; + result->texture_format_etc1 = input.texture_format_etc1; + result->texture_format_etc1_npot = input.texture_format_etc1_npot; + result->texture_rectangle = input.texture_rectangle; + result->iosurface = input.iosurface; + result->texture_usage = input.texture_usage; + result->texture_storage = input.texture_storage; + result->discard_framebuffer = input.discard_framebuffer; + result->sync_query = input.sync_query; + result->image = input.image; + result->future_sync_points = input.future_sync_points; + result->blend_equation_advanced = input.blend_equation_advanced; + result->blend_equation_advanced_coherent = + input.blend_equation_advanced_coherent; + return result.Pass(); +} + +gpu::Capabilities TypeConverter<gpu::Capabilities, GpuCapabilitiesPtr>::Convert( + const GpuCapabilitiesPtr& input) { + gpu::Capabilities result; + result.vertex_shader_precisions = + input->vertex_shader_precisions + .To<gpu::Capabilities::PerStagePrecisions>(); + result.fragment_shader_precisions = + input->fragment_shader_precisions + .To<gpu::Capabilities::PerStagePrecisions>(); + result.max_combined_texture_image_units = + input->max_combined_texture_image_units; + result.max_cube_map_texture_size = input->max_cube_map_texture_size; + result.max_fragment_uniform_vectors = input->max_fragment_uniform_vectors; + result.max_renderbuffer_size = input->max_renderbuffer_size; + result.max_texture_image_units = input->max_texture_image_units; + result.max_texture_size = input->max_texture_size; + result.max_varying_vectors = input->max_varying_vectors; + result.max_vertex_attribs = input->max_vertex_attribs; + result.max_vertex_texture_image_units = input->max_vertex_texture_image_units; + result.max_vertex_uniform_vectors = input->max_vertex_uniform_vectors; + result.num_compressed_texture_formats = input->num_compressed_texture_formats; + result.num_shader_binary_formats = input->num_shader_binary_formats; + result.bind_generates_resource_chromium = + input->bind_generates_resource_chromium; + result.post_sub_buffer = input->post_sub_buffer; + result.egl_image_external = input->egl_image_external; + result.texture_format_bgra8888 = input->texture_format_bgra8888; + result.texture_format_etc1 = input->texture_format_etc1; + result.texture_format_etc1_npot = input->texture_format_etc1_npot; + result.texture_rectangle = input->texture_rectangle; + result.iosurface = input->iosurface; + result.texture_usage = input->texture_usage; + result.texture_storage = input->texture_storage; + result.discard_framebuffer = input->discard_framebuffer; + result.sync_query = input->sync_query; + result.image = input->image; + result.future_sync_points = input->future_sync_points; + result.blend_equation_advanced = input->blend_equation_advanced; + result.blend_equation_advanced_coherent = + input->blend_equation_advanced_coherent; + return result; +} + +} // namespace gles2 diff --git a/mojo/services/gles2/command_buffer_type_conversions.h b/mojo/services/gles2/command_buffer_type_conversions.h new file mode 100644 index 0000000..9243a56 --- /dev/null +++ b/mojo/services/gles2/command_buffer_type_conversions.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ +#define SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ + +#include "gpu/command_buffer/common/capabilities.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/type_converter.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" + +namespace mojo { + +class CommandBufferState; + +template <> +struct TypeConverter<CommandBufferStatePtr, gpu::CommandBuffer::State> { + static CommandBufferStatePtr Convert(const gpu::CommandBuffer::State& input); +}; + +template <> +struct TypeConverter<gpu::CommandBuffer::State, CommandBufferStatePtr> { + static gpu::CommandBuffer::State Convert(const CommandBufferStatePtr& input); +}; + +template <> +struct TypeConverter<GpuShaderPrecisionPtr, + gpu::Capabilities::ShaderPrecision> { + static GpuShaderPrecisionPtr Convert( + const gpu::Capabilities::ShaderPrecision& input); +}; + +template <> +struct TypeConverter<gpu::Capabilities::ShaderPrecision, + GpuShaderPrecisionPtr> { + static gpu::Capabilities::ShaderPrecision Convert( + const GpuShaderPrecisionPtr& input); +}; + +template <> +struct TypeConverter<GpuPerStagePrecisionsPtr, + gpu::Capabilities::PerStagePrecisions> { + static GpuPerStagePrecisionsPtr Convert( + const gpu::Capabilities::PerStagePrecisions& input); +}; + +template <> +struct TypeConverter<gpu::Capabilities::PerStagePrecisions, + GpuPerStagePrecisionsPtr> { + static gpu::Capabilities::PerStagePrecisions Convert( + const GpuPerStagePrecisionsPtr& input); +}; + +template <> +struct TypeConverter<GpuCapabilitiesPtr, gpu::Capabilities> { + static GpuCapabilitiesPtr Convert(const gpu::Capabilities& input); +}; + +template <> +struct TypeConverter<gpu::Capabilities, GpuCapabilitiesPtr> { + static gpu::Capabilities Convert(const GpuCapabilitiesPtr& input); +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_COMMAND_BUFFER_TYPE_CONVERSIONS_H_ diff --git a/mojo/services/gles2/gpu_impl.cc b/mojo/services/gles2/gpu_impl.cc new file mode 100644 index 0000000..304da8d --- /dev/null +++ b/mojo/services/gles2/gpu_impl.cc @@ -0,0 +1,36 @@ +// 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 "mojo/services/gles2/gpu_impl.h" + +#include "gpu/command_buffer/service/mailbox_manager.h" +#include "gpu/command_buffer/service/mailbox_manager_impl.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/gles2/command_buffer_driver.h" +#include "mojo/services/gles2/command_buffer_impl.h" +#include "ui/gl/gl_share_group.h" +#include "ui/gl/gl_surface.h" + +namespace gles2 { + +GpuImpl::GpuImpl(mojo::InterfaceRequest<Gpu> request, + const scoped_refptr<GpuState>& state) + : binding_(this, request.Pass()), state_(state) { +} + +GpuImpl::~GpuImpl() { +} + +void GpuImpl::CreateOffscreenGLES2Context( + mojo::InterfaceRequest<mojo::CommandBuffer> request) { + new CommandBufferImpl(request.Pass(), mojo::ViewportParameterListenerPtr(), + state_->control_task_runner(), + state_->sync_point_manager(), + make_scoped_ptr(new CommandBufferDriver( + state_->share_group(), state_->mailbox_manager(), + state_->sync_point_manager()))); +} + +} // namespace gles2 diff --git a/mojo/services/gles2/gpu_impl.h b/mojo/services/gles2/gpu_impl.h new file mode 100644 index 0000000..3e50a9a --- /dev/null +++ b/mojo/services/gles2/gpu_impl.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef SERVICES_GLES2_GPU_IMPL_H_ +#define SERVICES_GLES2_GPU_IMPL_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread.h" +#include "mojo/services/gles2/gpu_state.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/geometry/public/interfaces/geometry.mojom.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h" + +namespace gfx { +class GLShareGroup; +} + +namespace gpu { +class SyncPointManager; +namespace gles2 { +class MailboxManager; +} +} + +namespace gles2 { + +class GpuImpl : public mojo::Gpu { + public: + GpuImpl(mojo::InterfaceRequest<mojo::Gpu> request, + const scoped_refptr<GpuState>& state); + ~GpuImpl() override; + + private: + void CreateOffscreenGLES2Context(mojo::InterfaceRequest<mojo::CommandBuffer> + command_buffer_request) override; + + mojo::StrongBinding<Gpu> binding_; + scoped_refptr<GpuState> state_; + + DISALLOW_COPY_AND_ASSIGN(GpuImpl); +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_GPU_IMPL_H_ diff --git a/mojo/services/gles2/gpu_state.cc b/mojo/services/gles2/gpu_state.cc new file mode 100644 index 0000000..63d6e29 --- /dev/null +++ b/mojo/services/gles2/gpu_state.cc @@ -0,0 +1,21 @@ +// Copyright 2015 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 "mojo/services/gles2/gpu_state.h" + +namespace gles2 { + +GpuState::GpuState() + : control_thread_("gpu_command_buffer_control"), + sync_point_manager_(gpu::SyncPointManager::Create(true)), + share_group_(new gfx::GLShareGroup), + mailbox_manager_(new gpu::gles2::MailboxManagerImpl) { + control_thread_.Start(); +} + +GpuState::~GpuState() { +} + +} // namespace gles2 diff --git a/mojo/services/gles2/gpu_state.h b/mojo/services/gles2/gpu_state.h new file mode 100644 index 0000000..1d75318 --- /dev/null +++ b/mojo/services/gles2/gpu_state.h @@ -0,0 +1,52 @@ +// Copyright 2015 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. + +#ifndef SERVICES_GLES2_GPU_STATE_H_ +#define SERVICES_GLES2_GPU_STATE_H_ + +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread.h" +#include "gpu/command_buffer/service/mailbox_manager_impl.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "ui/gl/gl_share_group.h" + +namespace gles2 { + +// We need to share these across all CommandBuffer instances so that contexts +// they create can share resources with each other via mailboxes. +class GpuState : public base::RefCounted<GpuState> { + public: + GpuState(); + + // We run the CommandBufferImpl on the control_task_runner, which forwards + // most method class to the CommandBufferDriver, which runs on the "driver", + // thread (i.e., the thread on which GpuImpl instances are created). + scoped_refptr<base::SingleThreadTaskRunner> control_task_runner() { + return control_thread_.task_runner(); + } + + // These objects are intended to be used on the "driver" thread (i.e., the + // thread on which GpuImpl instances are created). + gfx::GLShareGroup* share_group() const { return share_group_.get(); } + gpu::gles2::MailboxManager* mailbox_manager() const { + return mailbox_manager_.get(); + } + gpu::SyncPointManager* sync_point_manager() const { + return sync_point_manager_.get(); + } + + private: + friend class base::RefCounted<GpuState>; + ~GpuState(); + + base::Thread control_thread_; + scoped_refptr<gpu::SyncPointManager> sync_point_manager_; + scoped_refptr<gfx::GLShareGroup> share_group_; + scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_; +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_GPU_STATE_H_ diff --git a/mojo/services/gles2/mojo_buffer_backing.cc b/mojo/services/gles2/mojo_buffer_backing.cc new file mode 100644 index 0000000..3e6c38e --- /dev/null +++ b/mojo/services/gles2/mojo_buffer_backing.cc @@ -0,0 +1,34 @@ +// 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 "mojo/services/gles2/mojo_buffer_backing.h" + +#include "base/logging.h" + +namespace gles2 { + +MojoBufferBacking::MojoBufferBacking(mojo::ScopedSharedBufferHandle handle, + void* memory, + size_t size) + : handle_(handle.Pass()), memory_(memory), size_(size) {} + +MojoBufferBacking::~MojoBufferBacking() { mojo::UnmapBuffer(memory_); } + +// static +scoped_ptr<gpu::BufferBacking> MojoBufferBacking::Create( + mojo::ScopedSharedBufferHandle handle, + size_t size) { + void* memory = NULL; + MojoResult result = mojo::MapBuffer( + handle.get(), 0, size, &memory, MOJO_MAP_BUFFER_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return scoped_ptr<BufferBacking>(); + DCHECK(memory); + return scoped_ptr<BufferBacking>( + new MojoBufferBacking(handle.Pass(), memory, size)); +} +void* MojoBufferBacking::GetMemory() const { return memory_; } +size_t MojoBufferBacking::GetSize() const { return size_; } + +} // namespace gles2 diff --git a/mojo/services/gles2/mojo_buffer_backing.h b/mojo/services/gles2/mojo_buffer_backing.h new file mode 100644 index 0000000..6dfd509 --- /dev/null +++ b/mojo/services/gles2/mojo_buffer_backing.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ +#define SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "gpu/command_buffer/common/buffer.h" +#include "third_party/mojo/src/mojo/public/cpp/system/core.h" + +namespace gles2 { + +class MojoBufferBacking : public gpu::BufferBacking { + public: + MojoBufferBacking(mojo::ScopedSharedBufferHandle handle, + void* memory, + size_t size); + ~MojoBufferBacking() override; + + static scoped_ptr<gpu::BufferBacking> Create( + mojo::ScopedSharedBufferHandle handle, + size_t size); + + void* GetMemory() const override; + size_t GetSize() const override; + + private: + mojo::ScopedSharedBufferHandle handle_; + void* memory_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(MojoBufferBacking); +}; + +} // namespace gles2 + +#endif // SERVICES_GLES2_MOJO_BUFFER_BACKING_H_ diff --git a/mojo/services/keyboard/public/interfaces/BUILD.gn b/mojo/services/keyboard/public/interfaces/BUILD.gn new file mode 100644 index 0000000..6aa9389 --- /dev/null +++ b/mojo/services/keyboard/public/interfaces/BUILD.gn @@ -0,0 +1,12 @@ +# 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. + +import("//build/module_args/mojo.gni") +import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "keyboard.mojom", + ] +} diff --git a/mojo/services/keyboard/public/interfaces/keyboard.mojom b/mojo/services/keyboard/public/interfaces/keyboard.mojom new file mode 100644 index 0000000..d11253e --- /dev/null +++ b/mojo/services/keyboard/public/interfaces/keyboard.mojom @@ -0,0 +1,10 @@ +// 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. + +module mojo; + +interface Keyboard { + Show(); + Hide(); +}; diff --git a/mojo/services/kiosk_wm/BUILD.gn b/mojo/services/kiosk_wm/BUILD.gn new file mode 100644 index 0000000..99c25f0 --- /dev/null +++ b/mojo/services/kiosk_wm/BUILD.gn @@ -0,0 +1,34 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("$mojo_sdk_root/mojo/public/tools/bindings/mojom.gni") + +mojo_native_application("kiosk_wm") { + sources = [ + "kiosk_wm.cc", + "kiosk_wm.h", + "main.cc", + "merged_service_provider.cc", + "merged_service_provider.h", + "navigator_host_impl.cc", + "navigator_host_impl.h", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/common:common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/utility", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/input_events/public/interfaces", + "//third_party/mojo_services/src/navigation/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//mojo/services/window_manager:lib", + "//ui/base", + ] +} diff --git a/mojo/services/kiosk_wm/DEPS b/mojo/services/kiosk_wm/DEPS new file mode 100644 index 0000000..191abbe --- /dev/null +++ b/mojo/services/kiosk_wm/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+mojo/application", + "+mojo/services/window_manager", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/services/kiosk_wm/README.md b/mojo/services/kiosk_wm/README.md new file mode 100644 index 0000000..4c38010 --- /dev/null +++ b/mojo/services/kiosk_wm/README.md @@ -0,0 +1,6 @@ +A very simple window kiosk-like window manager. + +This can handle a single application at a time, any further calls to +WindowManager::Embed will replace the existing application. + +Renders full-screen. On linux defaults to a Nexus-5 aspect ratio.
\ No newline at end of file diff --git a/mojo/services/kiosk_wm/kiosk_wm.cc b/mojo/services/kiosk_wm/kiosk_wm.cc new file mode 100644 index 0000000..9aa2aab --- /dev/null +++ b/mojo/services/kiosk_wm/kiosk_wm.cc @@ -0,0 +1,134 @@ +// 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 "mojo/services/kiosk_wm/kiosk_wm.h" + +#include "mojo/services/kiosk_wm/merged_service_provider.h" +#include "mojo/services/window_manager/basic_focus_rules.h" + +namespace kiosk_wm { + +KioskWM::KioskWM() + : window_manager_app_(new window_manager::WindowManagerApp(this, this)), + root_(nullptr), + content_(nullptr), + navigator_host_(this), + weak_factory_(this) { + exposed_services_impl_.AddService(this); +} + +KioskWM::~KioskWM() { +} + +base::WeakPtr<KioskWM> KioskWM::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +void KioskWM::Initialize(mojo::ApplicationImpl* app) { + window_manager_app_->Initialize(app); + + // Format: --args-for="app_url default_url" + if (app->args().size() > 1) + default_url_ = app->args()[1]; +} + +bool KioskWM::ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) { + window_manager_app_->ConfigureIncomingConnection(connection); + return true; +} + +bool KioskWM::ConfigureOutgoingConnection( + mojo::ApplicationConnection* connection) { + window_manager_app_->ConfigureOutgoingConnection(connection); + return true; +} + +void KioskWM::OnEmbed( + mojo::View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + // KioskWM does not support being embedded more than once. + CHECK(!root_); + + root_ = root; + root_->AddObserver(this); + + // Resize to match the Nexus 5 aspect ratio: + window_manager_app_->SetViewportSize(gfx::Size(320, 640)); + + content_ = root->view_manager()->CreateView(); + content_->SetBounds(root_->bounds()); + root_->AddChild(content_); + content_->SetVisible(true); + + window_manager_app_->InitFocus( + make_scoped_ptr(new window_manager::BasicFocusRules(root_))); + window_manager_app_->accelerator_manager()->Register( + ui::Accelerator(ui::VKEY_BROWSER_BACK, 0), + ui::AcceleratorManager::kNormalPriority, this); + + // Now that we're ready, either load a pending url or the default url. + if (!pending_url_.empty()) + Embed(pending_url_, services.Pass(), exposed_services.Pass()); + else if (!default_url_.empty()) + Embed(default_url_, services.Pass(), exposed_services.Pass()); +} + +void KioskWM::Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + // We can get Embed calls before we've actually been + // embedded into the root view and content_ is created. + // Just save the last url, we'll embed it when we're ready. + if (!content_) { + pending_url_ = url; + return; + } + + merged_service_provider_.reset( + new MergedServiceProvider(exposed_services.Pass(), this)); + content_->Embed(url, services.Pass(), + merged_service_provider_->GetServiceProviderPtr().Pass()); + + navigator_host_.RecordNavigation(url); +} + +void KioskWM::Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NavigatorHost> request) { + navigator_host_.Bind(request.Pass()); +} + +void KioskWM::OnViewManagerDisconnected( + mojo::ViewManager* view_manager) { + root_ = nullptr; +} + +void KioskWM::OnViewDestroyed(mojo::View* view) { + view->RemoveObserver(this); +} + +void KioskWM::OnViewBoundsChanged(mojo::View* view, + const mojo::Rect& old_bounds, + const mojo::Rect& new_bounds) { + content_->SetBounds(new_bounds); +} + +// Convenience method: +void KioskWM::ReplaceContentWithURL(const mojo::String& url) { + Embed(url, nullptr, nullptr); +} + +bool KioskWM::AcceleratorPressed(const ui::Accelerator& accelerator) { + if (accelerator.key_code() != ui::VKEY_BROWSER_BACK) + return false; + navigator_host_.RequestNavigateHistory(-1); + return true; +} + +bool KioskWM::CanHandleAccelerators() const { + return true; +} + +} // namespace kiosk_wm diff --git a/mojo/services/kiosk_wm/kiosk_wm.h b/mojo/services/kiosk_wm/kiosk_wm.h new file mode 100644 index 0000000..5ae0ded --- /dev/null +++ b/mojo/services/kiosk_wm/kiosk_wm.h @@ -0,0 +1,95 @@ +// 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. + +#ifndef SERVICES_KIOSK_WM_KIOSK_WM_H_ +#define SERVICES_KIOSK_WM_KIOSK_WM_H_ + +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/connect.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/services/kiosk_wm/navigator_host_impl.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "mojo/services/window_manager/window_manager_delegate.h" +#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h" +#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" +#include "ui/base/accelerators/accelerator.h" + +namespace kiosk_wm { + +class MergedServiceProvider; + +class KioskWM : public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate, + public mojo::ViewObserver, + public window_manager::WindowManagerDelegate, + public mojo::InterfaceFactory<mojo::NavigatorHost>, + public ui::AcceleratorTarget { + public: + KioskWM(); + ~KioskWM() override; + + base::WeakPtr<KioskWM> GetWeakPtr(); + + void ReplaceContentWithURL(const mojo::String& url); + + private: + // Overridden from mojo::ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* app) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + bool ConfigureOutgoingConnection( + mojo::ApplicationConnection* connection) override; + + // Overridden from mojo::ViewManagerDelegate: + void OnEmbed(mojo::View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override; + + // Overriden from mojo::ViewObserver: + void OnViewDestroyed(mojo::View* view) override; + void OnViewBoundsChanged(mojo::View* view, + const mojo::Rect& old_bounds, + const mojo::Rect& new_bounds) override; + + // Overridden from WindowManagerDelegate: + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + + // Overridden from mojo::InterfaceFactory<mojo::NavigatorHost>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NavigatorHost> request) override; + + // Overriden from ui::AcceleratorTarget: + bool AcceleratorPressed(const ui::Accelerator& accelerator) override; + bool CanHandleAccelerators() const override; + + scoped_ptr<window_manager::WindowManagerApp> window_manager_app_; + + // Only support being embedded once, so both application-level + // and embedding-level state are shared on the same object. + mojo::View* root_; + mojo::View* content_; + std::string default_url_; + std::string pending_url_; + + mojo::ServiceProviderImpl exposed_services_impl_; + scoped_ptr<MergedServiceProvider> merged_service_provider_; + + NavigatorHostImpl navigator_host_; + + base::WeakPtrFactory<KioskWM> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(KioskWM); +}; + +} // namespace kiosk_wm + +#endif // SERVICES_KIOSK_WM_KIOSK_WM_H_ diff --git a/mojo/services/kiosk_wm/main.cc b/mojo/services/kiosk_wm/main.cc new file mode 100644 index 0000000..fa60bf5 --- /dev/null +++ b/mojo/services/kiosk_wm/main.cc @@ -0,0 +1,13 @@ +// 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 "mojo/application/application_runner_chromium.h" +#include "mojo/public/c/system/main.h" + +#include "mojo/services/kiosk_wm/kiosk_wm.h" + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new kiosk_wm::KioskWM); + return runner.Run(shell_handle); +} diff --git a/mojo/services/kiosk_wm/merged_service_provider.cc b/mojo/services/kiosk_wm/merged_service_provider.cc new file mode 100644 index 0000000..8987096 --- /dev/null +++ b/mojo/services/kiosk_wm/merged_service_provider.cc @@ -0,0 +1,35 @@ +// Copyright 2015 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 "mojo/services/kiosk_wm/merged_service_provider.h" + +namespace kiosk_wm { + +MergedServiceProvider::MergedServiceProvider( + mojo::ServiceProviderPtr exposed_services, + mojo::InterfaceFactory<mojo::NavigatorHost>* factory) + : exposed_services_(exposed_services.Pass()), factory_(factory) { +} + +MergedServiceProvider::~MergedServiceProvider() { +} + +mojo::ServiceProviderPtr MergedServiceProvider::GetServiceProviderPtr() { + mojo::ServiceProviderPtr sp; + binding_.reset(new mojo::Binding<mojo::ServiceProvider>(this, GetProxy(&sp))); + return sp.Pass(); +} + +void MergedServiceProvider::ConnectToService( + const mojo::String& interface_name, + mojo::ScopedMessagePipeHandle pipe) { + if (interface_name == mojo::NavigatorHost::Name_) { + factory_->Create(nullptr, + mojo::MakeRequest<mojo::NavigatorHost>(pipe.Pass())); + } else if (exposed_services_.get()) { + exposed_services_->ConnectToService(interface_name, pipe.Pass()); + } +} + +} // namespace kiosk_wm diff --git a/mojo/services/kiosk_wm/merged_service_provider.h b/mojo/services/kiosk_wm/merged_service_provider.h new file mode 100644 index 0000000..3b76138 --- /dev/null +++ b/mojo/services/kiosk_wm/merged_service_provider.h @@ -0,0 +1,41 @@ +// Copyright 2015 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. + +#ifndef SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_ +#define SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h" + +namespace kiosk_wm { + +// Used to wrap the second incoming WindowManager Embed() "exposed_services" +// parameter with a new ServiceProvider that adds the KioskWM's +// NavigatorHost service. +class MergedServiceProvider : public mojo::ServiceProvider { + public: + MergedServiceProvider(mojo::ServiceProviderPtr exposed_services, + mojo::InterfaceFactory<mojo::NavigatorHost>* factory); + ~MergedServiceProvider() override; + + mojo::ServiceProviderPtr GetServiceProviderPtr(); + + private: + // mojo::ServiceProvider: + void ConnectToService(const mojo::String& interface_name, + mojo::ScopedMessagePipeHandle pipe) override; + + mojo::ServiceProviderPtr exposed_services_; + mojo::InterfaceFactory<mojo::NavigatorHost>* factory_; + scoped_ptr<mojo::Binding<ServiceProvider>> binding_; + + DISALLOW_COPY_AND_ASSIGN(MergedServiceProvider); +}; + +} // namespace kiosk_wm + +#endif // SERVICES_KIOSK_WM_MERGED_SERVICE_PROVIDER_H_ diff --git a/mojo/services/kiosk_wm/navigator_host_impl.cc b/mojo/services/kiosk_wm/navigator_host_impl.cc new file mode 100644 index 0000000..3b28339 --- /dev/null +++ b/mojo/services/kiosk_wm/navigator_host_impl.cc @@ -0,0 +1,54 @@ +// 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 "mojo/services/kiosk_wm/navigator_host_impl.h" + +#include "mojo/services/kiosk_wm/kiosk_wm.h" + +namespace kiosk_wm { + +NavigatorHostImpl::NavigatorHostImpl(KioskWM* window_manager) + : current_index_(-1), kiosk_wm_(window_manager) { +} + +NavigatorHostImpl::~NavigatorHostImpl() { +} + +void NavigatorHostImpl::Bind( + mojo::InterfaceRequest<mojo::NavigatorHost> request) { + bindings_.AddBinding(this, request.Pass()); +} + +void NavigatorHostImpl::DidNavigateLocally(const mojo::String& url) { + RecordNavigation(url); + // TODO(abarth): Do something interesting. +} + +void NavigatorHostImpl::RequestNavigate(mojo::Target target, + mojo::URLRequestPtr request) { + // kiosk_wm sets up default services including navigation. + kiosk_wm_->ReplaceContentWithURL(request->url); +} + +void NavigatorHostImpl::RequestNavigateHistory(int32_t delta) { + if (history_.empty()) + return; + current_index_ = + std::max(0, std::min(current_index_ + delta, + static_cast<int32_t>(history_.size()) - 1)); + kiosk_wm_->ReplaceContentWithURL(history_[current_index_]); +} + +void NavigatorHostImpl::RecordNavigation(const std::string& url) { + if (current_index_ >= 0 && history_[current_index_] == url) { + // This is a navigation to the current entry, ignore. + return; + } + + history_.erase(history_.begin() + (current_index_ + 1), history_.end()); + history_.push_back(url); + ++current_index_; +} + +} // namespace kiosk_wm diff --git a/mojo/services/kiosk_wm/navigator_host_impl.h b/mojo/services/kiosk_wm/navigator_host_impl.h new file mode 100644 index 0000000..bf47a97 --- /dev/null +++ b/mojo/services/kiosk_wm/navigator_host_impl.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_ +#define SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_ + +#include "base/memory/weak_ptr.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "third_party/mojo_services/src/navigation/public/interfaces/navigation.mojom.h" + +namespace kiosk_wm { +class KioskWM; + +class NavigatorHostImpl : public mojo::NavigatorHost { + public: + NavigatorHostImpl(KioskWM* kiosk_wm); + ~NavigatorHostImpl() override; + + void Bind(mojo::InterfaceRequest<mojo::NavigatorHost> request); + + void RecordNavigation(const std::string& url); + + // mojo::NavigatorHost implementation: + void DidNavigateLocally(const mojo::String& url) override; + void RequestNavigate(mojo::Target target, + mojo::URLRequestPtr request) override; + void RequestNavigateHistory(int32_t delta) override; + + private: + std::vector<std::string> history_; + int32_t current_index_; + + KioskWM* kiosk_wm_; + mojo::WeakBindingSet<NavigatorHost> bindings_; + + DISALLOW_COPY_AND_ASSIGN(NavigatorHostImpl); +}; + +} // namespace kiosk_wm + +#endif // SERVICES_KIOSK_WM_NAVIGATOR_HOST_IMPL_H_ diff --git a/mojo/services/native_viewport/BUILD.gn b/mojo/services/native_viewport/BUILD.gn new file mode 100644 index 0000000..3e77370 --- /dev/null +++ b/mojo/services/native_viewport/BUILD.gn @@ -0,0 +1,110 @@ +# 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. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") + + group("native_viewport") { + deps = [ + ":lib", + ":native_viewport_java", + ":jni_headers", + ] + } + + android_library("native_viewport_java") { + java_files = + [ "android/src/org/chromium/mojo/PlatformViewportAndroid.java" ] + + deps = [ + "//base:base_java", + ] + } + + generate_jni("jni_headers") { + sources = [ + "android/src/org/chromium/mojo/PlatformViewportAndroid.java", + ] + + jni_package = "mojo" + } +} else { + mojo_native_application("native_viewport") { + output_name = "native_viewport_service" + sources = [ + "main.cc", + ] + deps = [ + ":lib", + "//base", + "//mojo/application", + "//mojo/common:tracing_impl", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/cpp:args", + "//mojo/services/gles2", + "//ui/events", + "//ui/events/platform", + "//ui/gl", + ] + } +} + +source_set("lib") { + sources = [ + "native_viewport_impl.cc", + "native_viewport_impl.h", + "onscreen_context_provider.cc", + "onscreen_context_provider.h", + "platform_viewport.h", + "platform_viewport_android.cc", + "platform_viewport_android.h", + "platform_viewport_headless.cc", + "platform_viewport_headless.h", + "platform_viewport_stub.cc", + "platform_viewport_win.cc", + "platform_viewport_x11.cc", + ] + + if (!is_ios) { + sources -= [ "platform_viewport_stub.cc" ] + } + + deps = [ + "//base", + "//gpu/command_buffer/service", + "//mojo/application", + "//mojo/common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//mojo/environment:chromium", + "//mojo/services/gles2", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//ui/events", + "//ui/events/platform", + "//ui/gfx", + "//ui/gfx/geometry", + "//ui/gl", + "//ui/platform_window", + ] + + if (is_android) { + deps += [ ":jni_headers" ] + } + + if (use_x11) { + deps += [ "//ui/platform_window/x11" ] + } else { + sources -= [ "platform_viewport_x11.cc" ] + } + + if (is_win) { + deps += [ "//ui/platform_window/win:win_window" ] + } +} diff --git a/mojo/services/native_viewport/DEPS b/mojo/services/native_viewport/DEPS new file mode 100644 index 0000000..9c70113 --- /dev/null +++ b/mojo/services/native_viewport/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+mojo/application", + "+mojo/converters", + "+mojo/services/gles2", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java new file mode 100644 index 0000000..15eb96c --- /dev/null +++ b/mojo/services/native_viewport/android/src/org/chromium/mojo/PlatformViewportAndroid.java @@ -0,0 +1,183 @@ +// 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. + +package org.chromium.mojo; + +import android.app.Activity; +import android.content.Context; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +/** + * Exposes SurfaceView to native code. + */ +@JNINamespace("native_viewport") +public class PlatformViewportAndroid extends SurfaceView { + + private long mNativeMojoViewport; + private final SurfaceHolder.Callback mSurfaceCallback; + + @SuppressWarnings("unused") + @CalledByNative + public static void createForActivity(Activity activity, long nativeViewport) { + activity.setContentView(new PlatformViewportAndroid(activity, nativeViewport)); + } + + public PlatformViewportAndroid(Context context, long nativeViewport) { + super(context); + + setFocusable(true); + setFocusableInTouchMode(true); + + mNativeMojoViewport = nativeViewport; + assert mNativeMojoViewport != 0; + + final float density = context.getResources().getDisplayMetrics().density; + + mSurfaceCallback = new SurfaceHolder.Callback() { + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + assert mNativeMojoViewport != 0; + nativeSurfaceSetSize(mNativeMojoViewport, width, height, density); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + assert mNativeMojoViewport != 0; + nativeSurfaceCreated(mNativeMojoViewport, holder.getSurface()); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + assert mNativeMojoViewport != 0; + nativeSurfaceDestroyed(mNativeMojoViewport); + } + }; + getHolder().addCallback(mSurfaceCallback); + + } + + // TODO(abarth): Someone needs to call destroy at some point. + public void destroy() { + getHolder().removeCallback(mSurfaceCallback); + nativeDestroy(mNativeMojoViewport); + mNativeMojoViewport = 0; + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility == View.VISIBLE) { + requestFocusFromTouch(); + requestFocus(); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + final int actionMasked = event.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_POINTER_DOWN + || actionMasked == MotionEvent.ACTION_POINTER_UP) { + // Up/down events identify a single point. + return notifyTouchEventAtIndex(event, event.getActionIndex()); + } + assert event.getPointerCount() != 0; + // All other types can have more than one point. + boolean result = false; + for (int i = 0, count = event.getPointerCount(); i < count; i++) { + final boolean sub_result = notifyTouchEventAtIndex(event, i); + result |= sub_result; + } + return result; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (privateDispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (privateDispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyEventPreIme(event); + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + if (privateDispatchKeyEvent(event)) { + return true; + } + return super.dispatchKeyShortcutEvent(event); + } + + private boolean notifyTouchEventAtIndex(MotionEvent event, int index) { + return nativeTouchEvent(mNativeMojoViewport, event.getEventTime(), event.getActionMasked(), + event.getPointerId(index), event.getX(index), event.getY(index), + event.getPressure(index), event.getTouchMajor(index), event.getTouchMinor(index), + event.getOrientation(index), event.getAxisValue(MotionEvent.AXIS_HSCROLL, index), + event.getAxisValue(MotionEvent.AXIS_VSCROLL, index)); + } + + private boolean privateDispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_MULTIPLE) { + boolean result = false; + if (event.getKeyCode() == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) { + String characters = event.getCharacters(); + for (int i = 0; i < characters.length(); ++i) { + char c = characters.charAt(i); + int codepoint = c; + if (codepoint >= Character.MIN_SURROGATE + && codepoint < (Character.MAX_SURROGATE + 1)) { + i++; + char c2 = characters.charAt(i); + codepoint = Character.toCodePoint(c, c2); + } + result |= nativeKeyEvent(mNativeMojoViewport, true, 0, codepoint); + result |= nativeKeyEvent(mNativeMojoViewport, false, 0, codepoint); + } + } else { + for (int i = 0; i < event.getRepeatCount(); ++i) { + result |= nativeKeyEvent( + mNativeMojoViewport, true, event.getKeyCode(), event.getUnicodeChar()); + result |= nativeKeyEvent( + mNativeMojoViewport, false, event.getKeyCode(), event.getUnicodeChar()); + } + } + return result; + } else { + return nativeKeyEvent(mNativeMojoViewport, event.getAction() == KeyEvent.ACTION_DOWN, + event.getKeyCode(), event.getUnicodeChar()); + } + } + + private static native void nativeDestroy(long nativePlatformViewportAndroid); + + private static native void nativeSurfaceCreated( + long nativePlatformViewportAndroid, Surface surface); + + private static native void nativeSurfaceDestroyed( + long nativePlatformViewportAndroid); + + private static native void nativeSurfaceSetSize( + long nativePlatformViewportAndroid, int width, int height, float density); + + private static native boolean nativeTouchEvent(long nativePlatformViewportAndroid, long timeMs, + int maskedAction, int pointerId, float x, float y, float pressure, float touchMajor, + float touchMinor, float orientation, float hWheel, float vWheel); + + private static native boolean nativeKeyEvent( + long nativePlatformViewportAndroid, boolean pressed, int keyCode, int unicodeCharacter); +} diff --git a/mojo/services/native_viewport/main.cc b/mojo/services/native_viewport/main.cc new file mode 100644 index 0000000..504e555 --- /dev/null +++ b/mojo/services/native_viewport/main.cc @@ -0,0 +1,99 @@ +// 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 <algorithm> + +#include "base/command_line.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "mojo/services/gles2/gpu_impl.h" +#include "mojo/services/native_viewport/native_viewport_impl.h" +#include "third_party/mojo_services/src/native_viewport/public/cpp/args.h" +#include "ui/events/event_switches.h" +#include "ui/gl/gl_surface.h" + +using mojo::ApplicationConnection; +using mojo::Gpu; +using mojo::NativeViewport; + +namespace native_viewport { + +class NativeViewportAppDelegate : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory<NativeViewport>, + public mojo::InterfaceFactory<Gpu> { + public: + NativeViewportAppDelegate() : is_headless_(false) {} + ~NativeViewportAppDelegate() override {} + + private: + // mojo::ApplicationDelegate implementation. + void Initialize(mojo::ApplicationImpl* application) override { + tracing_.Initialize(application); + +#if defined(OS_LINUX) + // Apply the switch for kTouchEvents to CommandLine (if set). This allows + // redirecting the mouse to a touch device on X for testing. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + const std::string touch_event_string("--" + + std::string(switches::kTouchDevices)); + auto touch_iter = std::find(application->args().begin(), + application->args().end(), + touch_event_string); + if (touch_iter != application->args().end() && + ++touch_iter != application->args().end()) { + command_line->AppendSwitchASCII(touch_event_string, *touch_iter); + } +#endif + + if (application->HasArg(mojo::kUseTestConfig)) + gfx::GLSurface::InitializeOneOffForTests(); + else + gfx::GLSurface::InitializeOneOff(); + + is_headless_ = application->HasArg(mojo::kUseHeadlessConfig); + } + + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService<NativeViewport>(this); + connection->AddService<Gpu>(this); + return true; + } + + // mojo::InterfaceFactory<NativeViewport> implementation. + void Create(ApplicationConnection* connection, + mojo::InterfaceRequest<NativeViewport> request) override { + if (!gpu_state_.get()) + gpu_state_ = new gles2::GpuState; + new NativeViewportImpl(is_headless_, gpu_state_, request.Pass()); + } + + // mojo::InterfaceFactory<Gpu> implementation. + void Create(ApplicationConnection* connection, + mojo::InterfaceRequest<Gpu> request) override { + if (!gpu_state_.get()) + gpu_state_ = new gles2::GpuState; + new gles2::GpuImpl(request.Pass(), gpu_state_); + } + + scoped_refptr<gles2::GpuState> gpu_state_; + bool is_headless_; + mojo::TracingImpl tracing_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportAppDelegate); +}; +} + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner( + new native_viewport::NativeViewportAppDelegate); + runner.set_message_loop_type(base::MessageLoop::TYPE_UI); + return runner.Run(shell_handle); +} diff --git a/mojo/services/native_viewport/native_viewport_impl.cc b/mojo/services/native_viewport/native_viewport_impl.cc new file mode 100644 index 0000000..f5bcc27 --- /dev/null +++ b/mojo/services/native_viewport/native_viewport_impl.cc @@ -0,0 +1,164 @@ +// 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 "mojo/services/native_viewport/native_viewport_impl.h" + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/services/gles2/gpu_state.h" +#include "mojo/services/native_viewport/platform_viewport_headless.h" +#include "ui/events/event.h" + +namespace native_viewport { + +NativeViewportImpl::NativeViewportImpl( + bool is_headless, + const scoped_refptr<gles2::GpuState>& gpu_state, + mojo::InterfaceRequest<mojo::NativeViewport> request) + : is_headless_(is_headless), + context_provider_(gpu_state), + sent_metrics_(false), + metrics_(mojo::ViewportMetrics::New()), + binding_(this, request.Pass()), + weak_factory_(this) { + binding_.set_error_handler(this); +} + +NativeViewportImpl::~NativeViewportImpl() { + // Destroy the NativeViewport early on as it may call us back during + // destruction and we want to be in a known state. + platform_viewport_.reset(); +} + +void NativeViewportImpl::Create(mojo::SizePtr size, + const CreateCallback& callback) { + create_callback_ = callback; + metrics_->size = size.Clone(); + if (is_headless_) + platform_viewport_ = PlatformViewportHeadless::Create(this); + else + platform_viewport_ = PlatformViewport::Create(this); + platform_viewport_->Init(gfx::Rect(size.To<gfx::Size>())); +} + +void NativeViewportImpl::RequestMetrics( + const RequestMetricsCallback& callback) { + if (!sent_metrics_) { + callback.Run(metrics_.Clone()); + sent_metrics_ = true; + return; + } + metrics_callback_ = callback; +} + +void NativeViewportImpl::Show() { + platform_viewport_->Show(); +} + +void NativeViewportImpl::Hide() { + platform_viewport_->Hide(); +} + +void NativeViewportImpl::Close() { + DCHECK(platform_viewport_); + platform_viewport_->Close(); +} + +void NativeViewportImpl::SetSize(mojo::SizePtr size) { + platform_viewport_->SetBounds(gfx::Rect(size.To<gfx::Size>())); +} + +void NativeViewportImpl::GetContextProvider( + mojo::InterfaceRequest<mojo::ContextProvider> request) { + context_provider_.Bind(request.Pass()); +} + +void NativeViewportImpl::SetEventDispatcher( + mojo::NativeViewportEventDispatcherPtr dispatcher) { + event_dispatcher_ = dispatcher.Pass(); +} + +void NativeViewportImpl::OnMetricsChanged(mojo::ViewportMetricsPtr metrics) { + if (metrics_->Equals(*metrics)) + return; + + metrics_ = metrics.Pass(); + sent_metrics_ = false; + + if (!metrics_callback_.is_null()) { + metrics_callback_.Run(metrics_.Clone()); + metrics_callback_.reset(); + sent_metrics_ = true; + } +} + +void NativeViewportImpl::OnAcceleratedWidgetAvailable( + gfx::AcceleratedWidget widget) { + context_provider_.SetAcceleratedWidget(widget); + // TODO: The metrics here might not match the actual window size on android + // where we don't know the actual size until the first OnMetricsChanged call. + create_callback_.Run(metrics_.Clone()); + sent_metrics_ = true; + create_callback_.reset(); +} + +void NativeViewportImpl::OnAcceleratedWidgetDestroyed() { + context_provider_.SetAcceleratedWidget(gfx::kNullAcceleratedWidget); +} + +bool NativeViewportImpl::OnEvent(mojo::EventPtr event) { + if (event.is_null() || !event_dispatcher_.get()) + return false; + + mojo::NativeViewportEventDispatcher::OnEventCallback callback; + switch (event->action) { + case mojo::EVENT_TYPE_POINTER_MOVE: { + // TODO(sky): add logic to remember last event location and not send if + // the same. + if (pointers_waiting_on_ack_.count(event->pointer_data->pointer_id)) + return false; + + pointers_waiting_on_ack_.insert(event->pointer_data->pointer_id); + callback = + base::Bind(&NativeViewportImpl::AckEvent, weak_factory_.GetWeakPtr(), + event->pointer_data->pointer_id); + break; + } + + case mojo::EVENT_TYPE_POINTER_CANCEL: + pointers_waiting_on_ack_.clear(); + break; + + case mojo::EVENT_TYPE_POINTER_UP: + pointers_waiting_on_ack_.erase(event->pointer_data->pointer_id); + break; + + default: + break; + } + + event_dispatcher_->OnEvent(event.Pass(), callback); + return false; +} + +void NativeViewportImpl::OnDestroyed() { + // This will signal a connection error and cause us to delete |this|. + binding_.Close(); +} + +void NativeViewportImpl::OnConnectionError() { + binding_.set_error_handler(nullptr); + delete this; +} + +void NativeViewportImpl::AckEvent(int32 pointer_id) { + pointers_waiting_on_ack_.erase(pointer_id); +} + +} // namespace native_viewport diff --git a/mojo/services/native_viewport/native_viewport_impl.h b/mojo/services/native_viewport/native_viewport_impl.h new file mode 100644 index 0000000..3c2344a --- /dev/null +++ b/mojo/services/native_viewport/native_viewport_impl.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_ +#define SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_ + +#include <set> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/services/native_viewport/onscreen_context_provider.h" +#include "mojo/services/native_viewport/platform_viewport.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "ui/gfx/geometry/rect.h" + +namespace gles2 { +class GpuState; +} + +namespace ui { +class Event; +} + +namespace native_viewport { + +// A NativeViewportImpl is bound to a message pipe and to a PlatformViewport. +// The NativeViewportImpl's lifetime ends when either the message pipe is closed +// or the PlatformViewport informs the NativeViewportImpl that it has been +// destroyed. +class NativeViewportImpl : public mojo::NativeViewport, + public PlatformViewport::Delegate, + public mojo::ErrorHandler { + public: + NativeViewportImpl(bool is_headless, + const scoped_refptr<gles2::GpuState>& gpu_state, + mojo::InterfaceRequest<mojo::NativeViewport> request); + ~NativeViewportImpl() override; + + // NativeViewport implementation. + void Create(mojo::SizePtr size, const CreateCallback& callback) override; + void RequestMetrics(const RequestMetricsCallback& callback) override; + void Show() override; + void Hide() override; + void Close() override; + void SetSize(mojo::SizePtr size) override; + void GetContextProvider( + mojo::InterfaceRequest<mojo::ContextProvider> request) override; + void SetEventDispatcher( + mojo::NativeViewportEventDispatcherPtr dispatcher) override; + + // PlatformViewport::Delegate implementation. + void OnMetricsChanged(mojo::ViewportMetricsPtr metrics) override; + void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override; + void OnAcceleratedWidgetDestroyed() override; + bool OnEvent(mojo::EventPtr event) override; + void OnDestroyed() override; + + // mojo::ErrorHandler implementation. + void OnConnectionError() override; + + private: + // Callback when the dispatcher has processed a message we're waiting on + // an ack from. |pointer_id| identifies the pointer the message was associated + // with. + void AckEvent(int32 pointer_id); + + bool is_headless_; + scoped_ptr<PlatformViewport> platform_viewport_; + OnscreenContextProvider context_provider_; + bool sent_metrics_; + mojo::ViewportMetricsPtr metrics_; + CreateCallback create_callback_; + RequestMetricsCallback metrics_callback_; + mojo::NativeViewportEventDispatcherPtr event_dispatcher_; + mojo::Binding<mojo::NativeViewport> binding_; + + // Set of pointer_ids we've sent a move to and are waiting on an ack. + std::set<int32> pointers_waiting_on_ack_; + + base::WeakPtrFactory<NativeViewportImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportImpl); +}; + +} // namespace native_viewport + +#endif // SERVICES_NATIVE_VIEWPORT_NATIVE_VIEWPORT_IMPL_H_ diff --git a/mojo/services/native_viewport/onscreen_context_provider.cc b/mojo/services/native_viewport/onscreen_context_provider.cc new file mode 100644 index 0000000..af72736 --- /dev/null +++ b/mojo/services/native_viewport/onscreen_context_provider.cc @@ -0,0 +1,58 @@ +// Copyright 2015 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 "mojo/services/native_viewport/onscreen_context_provider.h" + +#include "mojo/services/gles2/command_buffer_driver.h" +#include "mojo/services/gles2/command_buffer_impl.h" +#include "mojo/services/gles2/gpu_state.h" + +namespace native_viewport { + +OnscreenContextProvider::OnscreenContextProvider( + const scoped_refptr<gles2::GpuState>& state) + : state_(state), widget_(gfx::kNullAcceleratedWidget), binding_(this) { +} + +OnscreenContextProvider::~OnscreenContextProvider() { +} + +void OnscreenContextProvider::Bind( + mojo::InterfaceRequest<mojo::ContextProvider> request) { + binding_.Bind(request.Pass()); +} + +void OnscreenContextProvider::SetAcceleratedWidget( + gfx::AcceleratedWidget widget) { + widget_ = widget; + if (widget_ != gfx::kNullAcceleratedWidget && + !pending_create_callback_.is_null()) + CreateAndReturnCommandBuffer(); +} + +void OnscreenContextProvider::Create( + mojo::ViewportParameterListenerPtr viewport_parameter_listener, + const CreateCallback& callback) { + if (!pending_create_callback_.is_null()) + pending_create_callback_.Run(nullptr); + pending_listener_ = viewport_parameter_listener.Pass(); + pending_create_callback_ = callback; + + if (widget_ != gfx::kNullAcceleratedWidget) + CreateAndReturnCommandBuffer(); +} + +void OnscreenContextProvider::CreateAndReturnCommandBuffer() { + mojo::CommandBufferPtr cb; + new gles2::CommandBufferImpl( + GetProxy(&cb), pending_listener_.Pass(), state_->control_task_runner(), + state_->sync_point_manager(), + make_scoped_ptr(new gles2::CommandBufferDriver( + widget_, state_->share_group(), state_->mailbox_manager(), + state_->sync_point_manager()))); + pending_create_callback_.Run(cb.Pass()); + pending_create_callback_.reset(); +} + +} // namespace mojo diff --git a/mojo/services/native_viewport/onscreen_context_provider.h b/mojo/services/native_viewport/onscreen_context_provider.h new file mode 100644 index 0000000..e8a05d5 --- /dev/null +++ b/mojo/services/native_viewport/onscreen_context_provider.h @@ -0,0 +1,46 @@ +// Copyright 2015 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. + +#ifndef SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_ +#define SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_ + +#include "base/memory/ref_counted.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/context_provider.mojom.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h" +#include "ui/gfx/native_widget_types.h" + +namespace gles2 { +class GpuState; +} + +namespace native_viewport { + +class OnscreenContextProvider : public mojo::ContextProvider { + public: + explicit OnscreenContextProvider(const scoped_refptr<gles2::GpuState>& state); + ~OnscreenContextProvider() override; + + void Bind(mojo::InterfaceRequest<mojo::ContextProvider> request); + + void SetAcceleratedWidget(gfx::AcceleratedWidget widget); + + private: + // mojo::ContextProvider implementation: + void Create(mojo::ViewportParameterListenerPtr viewport_parameter_listener, + const CreateCallback& callback) override; + + void CreateAndReturnCommandBuffer(); + + scoped_refptr<gles2::GpuState> state_; + gfx::AcceleratedWidget widget_; + mojo::ViewportParameterListenerPtr pending_listener_; + CreateCallback pending_create_callback_; + mojo::Binding<mojo::ContextProvider> binding_; + + DISALLOW_COPY_AND_ASSIGN(OnscreenContextProvider); +}; + +} // namespace mojo + +#endif // SERVICES_NATIVE_VIEWPORT_ONSCREEN_CONTEXT_PROVIDER_H_ diff --git a/mojo/services/native_viewport/platform_viewport.h b/mojo/services/native_viewport/platform_viewport.h new file mode 100644 index 0000000..62594b0 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_ +#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_ + +#include "base/memory/scoped_ptr.h" +#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_widget_types.h" + +namespace gfx { +class Rect; +} + +namespace native_viewport { + +// Encapsulation of platform-specific Viewport. +class PlatformViewport { + public: + class Delegate { + public: + virtual ~Delegate() {} + + virtual void OnMetricsChanged(mojo::ViewportMetricsPtr metrics) = 0; + virtual void OnAcceleratedWidgetAvailable( + gfx::AcceleratedWidget widget) = 0; + virtual void OnAcceleratedWidgetDestroyed() = 0; + virtual bool OnEvent(mojo::EventPtr event) = 0; + virtual void OnDestroyed() = 0; + }; + + virtual ~PlatformViewport() {} + + virtual void Init(const gfx::Rect& bounds) = 0; + virtual void Show() = 0; + virtual void Hide() = 0; + virtual void Close() = 0; + virtual gfx::Size GetSize() = 0; + virtual void SetBounds(const gfx::Rect& bounds) = 0; + + static scoped_ptr<PlatformViewport> Create(Delegate* delegate); +}; + +} // namespace native_viewport + +#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_H_ diff --git a/mojo/services/native_viewport/platform_viewport_android.cc b/mojo/services/native_viewport/platform_viewport_android.cc new file mode 100644 index 0000000..90bfc76 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_android.cc @@ -0,0 +1,208 @@ +// 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. + +#include "mojo/services/native_viewport/platform_viewport_android.h" + +#include <android/input.h> +#include <android/native_window_jni.h> + +#include "base/android/jni_android.h" +#include "jni/PlatformViewportAndroid_jni.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "ui/events/event.h" +#include "ui/events/keycodes/keyboard_code_conversion_android.h" +#include "ui/gfx/geometry/point.h" + +namespace native_viewport { +namespace { + +mojo::EventType MotionEventActionToEventType(jint action) { + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + return mojo::EVENT_TYPE_POINTER_DOWN; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + return mojo::EVENT_TYPE_POINTER_UP; + case AMOTION_EVENT_ACTION_MOVE: + return mojo::EVENT_TYPE_POINTER_MOVE; + case AMOTION_EVENT_ACTION_CANCEL: + return mojo::EVENT_TYPE_POINTER_CANCEL; + case AMOTION_EVENT_ACTION_OUTSIDE: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + case AMOTION_EVENT_ACTION_SCROLL: + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_EXIT: + default: + NOTIMPLEMENTED() << "Unimplemented motion action: " << action; + } + return mojo::EVENT_TYPE_UNKNOWN; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// PlatformViewportAndroid, public: + +// static +bool PlatformViewportAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +PlatformViewportAndroid::PlatformViewportAndroid(Delegate* delegate) + : delegate_(delegate), + window_(NULL), + id_generator_(0), + weak_factory_(this) { +} + +PlatformViewportAndroid::~PlatformViewportAndroid() { + if (window_) + ReleaseWindow(); +} + +void PlatformViewportAndroid::Destroy(JNIEnv* env, jobject obj) { + delegate_->OnDestroyed(); +} + +void PlatformViewportAndroid::SurfaceCreated(JNIEnv* env, + jobject obj, + jobject jsurface) { + base::android::ScopedJavaLocalRef<jobject> protector(env, jsurface); + // Note: This ensures that any local references used by + // ANativeWindow_fromSurface are released immediately. This is needed as a + // workaround for https://code.google.com/p/android/issues/detail?id=68174 + { + base::android::ScopedJavaLocalFrame scoped_local_reference_frame(env); + window_ = ANativeWindow_fromSurface(env, jsurface); + } + delegate_->OnAcceleratedWidgetAvailable(window_); +} + +void PlatformViewportAndroid::SurfaceDestroyed(JNIEnv* env, jobject obj) { + DCHECK(window_); + delegate_->OnAcceleratedWidgetDestroyed(); + ReleaseWindow(); +} + +void PlatformViewportAndroid::SurfaceSetSize(JNIEnv* env, + jobject obj, + jint width, + jint height, + jfloat density) { + metrics_ = mojo::ViewportMetrics::New(); + metrics_->size = mojo::Size::New(); + metrics_->size->width = static_cast<int>(width); + metrics_->size->height = static_cast<int>(height); + metrics_->device_pixel_ratio = density; + delegate_->OnMetricsChanged(metrics_.Clone()); +} + +bool PlatformViewportAndroid::TouchEvent(JNIEnv* env, + jobject obj, + jlong time_ms, + jint masked_action, + jint pointer_id, + jfloat x, + jfloat y, + jfloat pressure, + jfloat touch_major, + jfloat touch_minor, + jfloat orientation, + jfloat h_wheel, + jfloat v_wheel) { + mojo::EventPtr event(mojo::Event::New()); + event->time_stamp = time_ms; + event->action = MotionEventActionToEventType(masked_action); + if (event->action == mojo::EVENT_TYPE_UNKNOWN) + return false; + + event->pointer_data = mojo::PointerData::New(); + event->pointer_data->pointer_id = pointer_id; + event->pointer_data->x = x; + event->pointer_data->y = y; + event->pointer_data->screen_x = x; + event->pointer_data->screen_y = y; + event->pointer_data->pressure = pressure; + event->pointer_data->radius_major = touch_major; + event->pointer_data->radius_minor = touch_minor; + event->pointer_data->orientation = orientation; + event->pointer_data->horizontal_wheel = h_wheel; + event->pointer_data->vertical_wheel = v_wheel; + delegate_->OnEvent(event.Pass()); + + return true; +} + +bool PlatformViewportAndroid::KeyEvent(JNIEnv* env, + jobject obj, + bool pressed, + jint key_code, + jint unicode_character) { + ui::KeyEvent event(pressed ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED, + ui::KeyboardCodeFromAndroidKeyCode(key_code), 0); + event.set_platform_keycode(key_code); + delegate_->OnEvent(mojo::Event::From(event)); + if (pressed && unicode_character) { + ui::KeyEvent char_event(unicode_character, + ui::KeyboardCodeFromAndroidKeyCode(key_code), 0); + char_event.set_platform_keycode(key_code); + delegate_->OnEvent(mojo::Event::From(char_event)); + } + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformViewportAndroid, PlatformViewport implementation: + +void PlatformViewportAndroid::Init(const gfx::Rect& bounds) { + JNIEnv* env = base::android::AttachCurrentThread(); + Java_PlatformViewportAndroid_createForActivity( + env, + base::android::GetApplicationContext(), + reinterpret_cast<jlong>(this)); +} + +void PlatformViewportAndroid::Show() { + // Nothing to do. View is created visible. +} + +void PlatformViewportAndroid::Hide() { + // Nothing to do. View is always visible. +} + +void PlatformViewportAndroid::Close() { + // TODO(beng): close activity containing MojoView? + + // TODO(beng): perform this in response to view destruction. + delegate_->OnDestroyed(); +} + +gfx::Size PlatformViewportAndroid::GetSize() { + return metrics_->size.To<gfx::Size>(); +} + +void PlatformViewportAndroid::SetBounds(const gfx::Rect& bounds) { + NOTIMPLEMENTED(); +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformViewportAndroid, private: + +void PlatformViewportAndroid::ReleaseWindow() { + ANativeWindow_release(window_); + window_ = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// PlatformViewport, public: + +// static +scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) { + return scoped_ptr<PlatformViewport>( + new PlatformViewportAndroid(delegate)).Pass(); +} + +} // namespace native_viewport diff --git a/mojo/services/native_viewport/platform_viewport_android.h b/mojo/services/native_viewport/platform_viewport_android.h new file mode 100644 index 0000000..ebfe9495 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_android.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_ +#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/services/native_viewport/platform_viewport.h" +#include "ui/events/event_constants.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/sequential_id_generator.h" + +namespace gpu { +class GLInProcessContext; +} + +struct ANativeWindow; + +namespace native_viewport { + +class PlatformViewportAndroid : public PlatformViewport { + public: + static bool Register(JNIEnv* env); + + explicit PlatformViewportAndroid(Delegate* delegate); + virtual ~PlatformViewportAndroid(); + + void Destroy(JNIEnv* env, jobject obj); + void SurfaceCreated(JNIEnv* env, jobject obj, jobject jsurface); + void SurfaceDestroyed(JNIEnv* env, jobject obj); + void SurfaceSetSize(JNIEnv* env, + jobject obj, + jint width, + jint height, + jfloat density); + bool TouchEvent(JNIEnv* env, + jobject obj, + jlong time_ms, + jint masked_action, + jint pointer_id, + jfloat x, + jfloat y, + jfloat pressure, + jfloat touch_major, + jfloat touch_minor, + jfloat orientation, + jfloat h_wheel, + jfloat v_wheel); + bool KeyEvent(JNIEnv* env, + jobject obj, + bool pressed, + jint key_code, + jint unicode_character); + + private: + // Overridden from PlatformViewport: + virtual void Init(const gfx::Rect& bounds) override; + virtual void Show() override; + virtual void Hide() override; + virtual void Close() override; + virtual gfx::Size GetSize() override; + virtual void SetBounds(const gfx::Rect& bounds) override; + + void ReleaseWindow(); + + Delegate* delegate_; + ANativeWindow* window_; + mojo::ViewportMetricsPtr metrics_; + ui::SequentialIDGenerator id_generator_; + + base::WeakPtrFactory<PlatformViewportAndroid> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformViewportAndroid); +}; + +} // namespace native_viewport + +#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_ANDROID_H_ diff --git a/mojo/services/native_viewport/platform_viewport_headless.cc b/mojo/services/native_viewport/platform_viewport_headless.cc new file mode 100644 index 0000000..af3cec7 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_headless.cc @@ -0,0 +1,49 @@ +// 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. + +#include "mojo/services/native_viewport/platform_viewport_headless.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" + +namespace native_viewport { + +PlatformViewportHeadless::PlatformViewportHeadless(Delegate* delegate) + : delegate_(delegate) { +} + +PlatformViewportHeadless::~PlatformViewportHeadless() { +} + +void PlatformViewportHeadless::Init(const gfx::Rect& bounds) { + metrics_ = mojo::ViewportMetrics::New(); + metrics_->size = mojo::Size::From(bounds.size()); +} + +void PlatformViewportHeadless::Show() { +} + +void PlatformViewportHeadless::Hide() { +} + +void PlatformViewportHeadless::Close() { + delegate_->OnDestroyed(); +} + +gfx::Size PlatformViewportHeadless::GetSize() { + return metrics_->size.To<gfx::Size>(); +} + +void PlatformViewportHeadless::SetBounds(const gfx::Rect& bounds) { + metrics_->size = mojo::Size::From(bounds.size()); + delegate_->OnMetricsChanged(metrics_->Clone()); +} + +// static +scoped_ptr<PlatformViewport> PlatformViewportHeadless::Create( + Delegate* delegate) { + return scoped_ptr<PlatformViewport>( + new PlatformViewportHeadless(delegate)).Pass(); +} + +} // namespace native_viewport diff --git a/mojo/services/native_viewport/platform_viewport_headless.h b/mojo/services/native_viewport/platform_viewport_headless.h new file mode 100644 index 0000000..fe7d148 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_headless.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_ +#define SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_ + +#include "base/macros.h" +#include "mojo/services/native_viewport/platform_viewport.h" +#include "ui/gfx/geometry/rect.h" + +namespace native_viewport { + +class PlatformViewportHeadless : public PlatformViewport { + public: + ~PlatformViewportHeadless() override; + + static scoped_ptr<PlatformViewport> Create(Delegate* delegate); + + private: + explicit PlatformViewportHeadless(Delegate* delegate); + + // Overridden from PlatformViewport: + void Init(const gfx::Rect& bounds) override; + void Show() override; + void Hide() override; + void Close() override; + gfx::Size GetSize() override; + void SetBounds(const gfx::Rect& bounds) override; + + Delegate* delegate_; + mojo::ViewportMetricsPtr metrics_; + + DISALLOW_COPY_AND_ASSIGN(PlatformViewportHeadless); +}; + +} // namespace native_viewport + +#endif // SERVICES_NATIVE_VIEWPORT_PLATFORM_VIEWPORT_HEADLESS_H_ diff --git a/mojo/services/native_viewport/platform_viewport_stub.cc b/mojo/services/native_viewport/platform_viewport_stub.cc new file mode 100644 index 0000000..80a7e94 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_stub.cc @@ -0,0 +1,14 @@ +// 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 "mojo/services/native_viewport/platform_viewport_headless.h" + +namespace mojo { + +// static +scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) { + return PlatformViewportHeadless::Create(delegate); +} + +} // namespace mojo diff --git a/mojo/services/native_viewport/platform_viewport_win.cc b/mojo/services/native_viewport/platform_viewport_win.cc new file mode 100644 index 0000000..8dcd589 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_win.cc @@ -0,0 +1,137 @@ +// 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. + +#include "mojo/services/native_viewport/platform_viewport.h" + +#include "base/memory/scoped_ptr.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/platform_window/platform_window_delegate.h" +#include "ui/platform_window/win/win_window.h" + +namespace native_viewport { +namespace { +float ConvertUIWheelValueToMojoValue(int offset) { + // Mojo's event type takes a value between -1 and 1. Normalize by allowing + // up to 20 of ui's offset. This is a bit arbitrary. + return std::max( + -1.0f, std::min(1.0f, static_cast<float>(offset) / + (20 * static_cast<float>( + ui::MouseWheelEvent::kWheelDelta)))); +} +} // namespace + +class PlatformViewportWin : public PlatformViewport, + public ui::PlatformWindowDelegate { + public: + explicit PlatformViewportWin(Delegate* delegate) + : delegate_(delegate) { + } + + ~PlatformViewportWin() { + // Destroy the platform-window while |this| is still alive. + platform_window_.reset(); + } + + private: + // Overridden from PlatformViewport: + void Init(const gfx::Rect& bounds) override { + metrics_ = mojo::ViewportMetrics::New(); + metrics_->size = mojo::Size::From(bounds.size()); + platform_window_.reset(new ui::WinWindow(this, bounds)); + } + + void Show() override { + platform_window_->Show(); + } + + void Hide() override { + platform_window_->Hide(); + } + + void Close() override { + platform_window_->Close(); + } + + gfx::Size GetSize() override { return metrics_->size.To<gfx::Size>(); } + + void SetBounds(const gfx::Rect& bounds) override { + platform_window_->SetBounds(bounds); + } + + // ui::PlatformWindowDelegate: + void OnBoundsChanged(const gfx::Rect& new_bounds) override { + metrics_->size = mojo::Size::From(new_bounds.size()); + delegate_->OnMetricsChanged(metrics_.Clone()); + } + + void OnDamageRect(const gfx::Rect& damaged_region) override { + } + + void DispatchEvent(ui::Event* event) override { + // TODO(jam): this code is copied from the X11 version. + mojo::EventPtr mojo_event(mojo::Event::From(*event)); + if (event->IsMouseWheelEvent()) { + // Mojo's event type has a different meaning for wheel events. Convert + // between the two. + ui::MouseWheelEvent* wheel_event = + static_cast<ui::MouseWheelEvent*>(event); + DCHECK(mojo_event->pointer_data); + mojo_event->pointer_data->horizontal_wheel = + ConvertUIWheelValueToMojoValue(wheel_event->x_offset()); + mojo_event->pointer_data->horizontal_wheel = + ConvertUIWheelValueToMojoValue(wheel_event->y_offset()); + } + delegate_->OnEvent(mojo_event.Pass()); + + switch (event->type()) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_TOUCH_PRESSED: + platform_window_->SetCapture(); + break; + case ui::ET_MOUSE_RELEASED: + case ui::ET_TOUCH_RELEASED: + platform_window_->ReleaseCapture(); + break; + default: + break; + } + } + + void OnCloseRequest() override { + platform_window_->Close(); + } + + void OnClosed() override { + delegate_->OnDestroyed(); + } + + void OnWindowStateChanged(ui::PlatformWindowState state) override { + } + + void OnLostCapture() override { + } + + void OnAcceleratedWidgetAvailable( + gfx::AcceleratedWidget widget) override { + delegate_->OnAcceleratedWidgetAvailable(widget); + } + + void OnActivationChanged(bool active) override {} + + scoped_ptr<ui::PlatformWindow> platform_window_; + Delegate* delegate_; + mojo::ViewportMetricsPtr metrics_; + + DISALLOW_COPY_AND_ASSIGN(PlatformViewportWin); +}; + +// static +scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) { + return scoped_ptr<PlatformViewport>(new PlatformViewportWin(delegate)).Pass(); +} + +} // namespace native_viewport diff --git a/mojo/services/native_viewport/platform_viewport_x11.cc b/mojo/services/native_viewport/platform_viewport_x11.cc new file mode 100644 index 0000000..a3cff26 --- /dev/null +++ b/mojo/services/native_viewport/platform_viewport_x11.cc @@ -0,0 +1,167 @@ +// 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. + +#include "mojo/services/native_viewport/platform_viewport.h" + +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/converters/input_events/mojo_extended_key_event_data.h" +#include "ui/events/event.h" +#include "ui/events/event_utils.h" +#include "ui/events/platform/platform_event_dispatcher.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/platform_window/platform_window.h" +#include "ui/platform_window/platform_window_delegate.h" +#include "ui/platform_window/x11/x11_window.h" + +namespace native_viewport { +namespace { + +float ConvertUIWheelValueToMojoValue(int offset) { + // Mojo's event type takes a value between -1 and 1. Normalize by allowing + // up to 20 of ui's offset. This is a bit arbitrary. + return std::max( + -1.0f, std::min(1.0f, static_cast<float>(offset) / + (20 * static_cast<float>( + ui::MouseWheelEvent::kWheelDelta)))); +} +} // namespace + +class PlatformViewportX11 : public PlatformViewport, + public ui::PlatformWindowDelegate { + public: + explicit PlatformViewportX11(Delegate* delegate) : delegate_(delegate) { + } + + ~PlatformViewportX11() override { + // Destroy the platform-window while |this| is still alive. + platform_window_.reset(); + } + + private: + // Overridden from PlatformViewport: + void Init(const gfx::Rect& bounds) override { + CHECK(!event_source_); + CHECK(!platform_window_); + + event_source_ = ui::PlatformEventSource::CreateDefault(); + + metrics_ = mojo::ViewportMetrics::New(); + metrics_->size = mojo::Size::From(bounds.size()); + + platform_window_.reset(new ui::X11Window(this)); + platform_window_->SetBounds(bounds); + } + + void Show() override { platform_window_->Show(); } + + void Hide() override { platform_window_->Hide(); } + + void Close() override { platform_window_->Close(); } + + gfx::Size GetSize() override { return metrics_->size.To<gfx::Size>(); } + + void SetBounds(const gfx::Rect& bounds) override { + platform_window_->SetBounds(bounds); + } + + // ui::PlatformWindowDelegate: + void OnBoundsChanged(const gfx::Rect& new_bounds) override { + metrics_->size = mojo::Size::From(new_bounds.size()); + delegate_->OnMetricsChanged(metrics_.Clone()); + } + + void OnDamageRect(const gfx::Rect& damaged_region) override {} + + void DispatchEvent(ui::Event* event) override { + mojo::EventPtr mojo_event(mojo::Event::From(*event)); + if (event->IsMouseWheelEvent()) { + // Mojo's event type has a different meaning for wheel events. Convert + // between the two. + ui::MouseWheelEvent* wheel_event = + static_cast<ui::MouseWheelEvent*>(event); + DCHECK(mojo_event->pointer_data); + mojo_event->pointer_data->horizontal_wheel = + ConvertUIWheelValueToMojoValue(wheel_event->x_offset()); + mojo_event->pointer_data->horizontal_wheel = + ConvertUIWheelValueToMojoValue(wheel_event->y_offset()); + } + delegate_->OnEvent(mojo_event.Pass()); + + switch (event->type()) { + case ui::ET_MOUSE_PRESSED: + case ui::ET_TOUCH_PRESSED: + platform_window_->SetCapture(); + break; + case ui::ET_MOUSE_RELEASED: + case ui::ET_TOUCH_RELEASED: + platform_window_->ReleaseCapture(); + break; + default: + break; + } + + // We want to emulate the WM_CHAR generation behaviour of Windows. + // + // On Linux, we've previously inserted characters by having + // InputMethodAuraLinux take all key down events and send a character event + // to the TextInputClient. This causes a mismatch in code that has to be + // shared between Windows and Linux, including blink code. Now that we're + // trying to have one way of doing things, we need to standardize on and + // emulate Windows character events. + // + // This is equivalent to what we're doing in the current Linux port, but + // done once instead of done multiple times in different places. + if (event->type() == ui::ET_KEY_PRESSED) { + ui::KeyEvent* key_press_event = static_cast<ui::KeyEvent*>(event); + ui::KeyEvent char_event(key_press_event->GetCharacter(), + key_press_event->key_code(), + key_press_event->flags()); + + DCHECK_EQ(key_press_event->GetCharacter(), char_event.GetCharacter()); + DCHECK_EQ(key_press_event->key_code(), char_event.key_code()); + DCHECK_EQ(key_press_event->flags(), char_event.flags()); + + char_event.SetExtendedKeyEventData( + make_scoped_ptr(new mojo::MojoExtendedKeyEventData( + key_press_event->GetLocatedWindowsKeyboardCode(), + key_press_event->GetText(), + key_press_event->GetUnmodifiedText()))); + char_event.set_platform_keycode(key_press_event->platform_keycode()); + + delegate_->OnEvent(mojo::Event::From(char_event)); + } + } + + void OnCloseRequest() override { platform_window_->Close(); } + + void OnClosed() override { delegate_->OnDestroyed(); } + + void OnWindowStateChanged(ui::PlatformWindowState state) override {} + + void OnLostCapture() override {} + + void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override { + delegate_->OnAcceleratedWidgetAvailable(widget); + } + + void OnActivationChanged(bool active) override {} + + scoped_ptr<ui::PlatformEventSource> event_source_; + scoped_ptr<ui::PlatformWindow> platform_window_; + Delegate* delegate_; + mojo::ViewportMetricsPtr metrics_; + + DISALLOW_COPY_AND_ASSIGN(PlatformViewportX11); +}; + +// static +scoped_ptr<PlatformViewport> PlatformViewport::Create(Delegate* delegate) { + return make_scoped_ptr(new PlatformViewportX11(delegate)); +} + +} // namespace native_viewport diff --git a/mojo/services/surfaces/BUILD.gn b/mojo/services/surfaces/BUILD.gn new file mode 100644 index 0000000..c1f8d2e --- /dev/null +++ b/mojo/services/surfaces/BUILD.gn @@ -0,0 +1,47 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/mojo_application.gni") + +mojo_native_application("surfaces") { + output_name = "surfaces_service" + sources = [ + "context_provider_mojo.cc", + "context_provider_mojo.h", + "display_factory_impl.cc", + "display_factory_impl.h", + "display_impl.cc", + "display_impl.h", + "surfaces_impl.cc", + "surfaces_impl.h", + "surfaces_output_surface.cc", + "surfaces_output_surface.h", + "surfaces_scheduler.cc", + "surfaces_scheduler.h", + "surfaces_service_application.cc", + "surfaces_service_application.h", + ] + + deps = [ + "//base", + "//cc", + "//cc/surfaces", + "//cc/surfaces:surface_id", + "//gpu/command_buffer/client:gles2_interface", + "//mojo/application", + "//mojo/common", + "//mojo/common:tracing_impl", + "//mojo/converters/geometry", + "//mojo/converters/surfaces", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/c/gles2", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/environment", + "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/gpu/public/interfaces", + "//third_party/mojo_services/src/surfaces/public/interfaces", + "//ui/gfx/geometry", + ] +} diff --git a/mojo/services/surfaces/DEPS b/mojo/services/surfaces/DEPS new file mode 100644 index 0000000..3b182bf --- /dev/null +++ b/mojo/services/surfaces/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+cc", + "+gpu", + "+mojo/application", + "+mojo/converters", + "+third_party/mojo_services", +] diff --git a/mojo/services/surfaces/context_provider_mojo.cc b/mojo/services/surfaces/context_provider_mojo.cc new file mode 100644 index 0000000..2874794 --- /dev/null +++ b/mojo/services/surfaces/context_provider_mojo.cc @@ -0,0 +1,80 @@ +// 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 "mojo/services/surfaces/context_provider_mojo.h" + +#include "base/logging.h" +#include "mojo/public/cpp/environment/environment.h" + +namespace mojo { + +ContextProviderMojo::ContextProviderMojo( + ScopedMessagePipeHandle command_buffer_handle) + : command_buffer_handle_(command_buffer_handle.Pass()), + context_(nullptr), + context_lost_(false) { +} + +bool ContextProviderMojo::BindToCurrentThread() { + DCHECK(command_buffer_handle_.is_valid()); + context_ = MojoGLES2CreateContext(command_buffer_handle_.release().value(), + &ContextLostThunk, this, + Environment::GetDefaultAsyncWaiter()); + DCHECK(context_); + return !!context_; +} + +gpu::gles2::GLES2Interface* ContextProviderMojo::ContextGL() { + if (!context_) + return nullptr; + return static_cast<gpu::gles2::GLES2Interface*>( + MojoGLES2GetGLES2Interface(context_)); +} + +gpu::ContextSupport* ContextProviderMojo::ContextSupport() { + if (!context_) + return nullptr; + return static_cast<gpu::ContextSupport*>( + MojoGLES2GetContextSupport(context_)); +} + +class GrContext* ContextProviderMojo::GrContext() { + return NULL; +} + +cc::ContextProvider::Capabilities ContextProviderMojo::ContextCapabilities() { + return capabilities_; +} + +void ContextProviderMojo::SetupLock() { +} + +base::Lock* ContextProviderMojo::GetLock() { + return &context_lock_; +} + +bool ContextProviderMojo::IsContextLost() { + return context_lost_; +} +bool ContextProviderMojo::DestroyedOnMainThread() { + return !context_; +} + +void ContextProviderMojo::SetLostContextCallback( + const LostContextCallback& lost_context_callback) { + lost_context_callback_ = lost_context_callback; +} + +ContextProviderMojo::~ContextProviderMojo() { + if (context_) + MojoGLES2DestroyContext(context_); +} + +void ContextProviderMojo::ContextLost() { + context_lost_ = true; + if (!lost_context_callback_.is_null()) + lost_context_callback_.Run(); +} + +} // namespace mojo diff --git a/mojo/services/surfaces/context_provider_mojo.h b/mojo/services/surfaces/context_provider_mojo.h new file mode 100644 index 0000000..48d12f2 --- /dev/null +++ b/mojo/services/surfaces/context_provider_mojo.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_ +#define SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_ + +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "cc/output/context_provider.h" +#include "mojo/public/c/gles2/gles2.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class ContextProviderMojo : public cc::ContextProvider { + public: + explicit ContextProviderMojo(ScopedMessagePipeHandle command_buffer_handle); + + // cc::ContextProvider implementation. + bool BindToCurrentThread() override; + gpu::gles2::GLES2Interface* ContextGL() override; + gpu::ContextSupport* ContextSupport() override; + class GrContext* GrContext() override; + Capabilities ContextCapabilities() override; + bool IsContextLost() override; + void VerifyContexts() override {} + void DeleteCachedResources() override {} + bool DestroyedOnMainThread() override; + void SetLostContextCallback( + const LostContextCallback& lost_context_callback) override; + void SetMemoryPolicyChangedCallback( + const MemoryPolicyChangedCallback& memory_policy_changed_callback) + override {} + void SetupLock() override; + base::Lock* GetLock() override; + + protected: + friend class base::RefCountedThreadSafe<ContextProviderMojo>; + ~ContextProviderMojo() override; + + private: + static void ContextLostThunk(void* closure) { + static_cast<ContextProviderMojo*>(closure)->ContextLost(); + } + void ContextLost(); + + cc::ContextProvider::Capabilities capabilities_; + ScopedMessagePipeHandle command_buffer_handle_; + MojoGLES2Context context_; + bool context_lost_; + LostContextCallback lost_context_callback_; + + base::Lock context_lock_; + + DISALLOW_COPY_AND_ASSIGN(ContextProviderMojo); +}; + +} // namespace mojo + +#endif // SERVICES_SURFACES_CONTEXT_PROVIDER_MOJO_H_ diff --git a/mojo/services/surfaces/display_factory_impl.cc b/mojo/services/surfaces/display_factory_impl.cc new file mode 100644 index 0000000..11e9bf2 --- /dev/null +++ b/mojo/services/surfaces/display_factory_impl.cc @@ -0,0 +1,36 @@ +// Copyright 2015 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 "mojo/services/surfaces/display_factory_impl.h" + +#include "cc/surfaces/surface_id.h" + +namespace surfaces { + +DisplayFactoryImpl::DisplayFactoryImpl( + cc::SurfaceManager* manager, + uint32_t id_namespace, + SurfacesScheduler* scheduler, + mojo::InterfaceRequest<mojo::DisplayFactory> request) + : id_namespace_(id_namespace), + next_local_id_(1u), + scheduler_(scheduler), + manager_(manager), + binding_(this, request.Pass()) { +} + +DisplayFactoryImpl::~DisplayFactoryImpl() { +} + +void DisplayFactoryImpl::Create( + mojo::ContextProviderPtr context_provider, + mojo::ResourceReturnerPtr returner, + mojo::InterfaceRequest<mojo::Display> display_request) { + cc::SurfaceId cc_id(static_cast<uint64_t>(id_namespace_) << 32 | + next_local_id_++); + new DisplayImpl(manager_, cc_id, scheduler_, context_provider.Pass(), + returner.Pass(), display_request.Pass()); +} + +} // namespace surfaces diff --git a/mojo/services/surfaces/display_factory_impl.h b/mojo/services/surfaces/display_factory_impl.h new file mode 100644 index 0000000..7a21c43 --- /dev/null +++ b/mojo/services/surfaces/display_factory_impl.h @@ -0,0 +1,44 @@ +// Copyright 2015 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. + +#ifndef SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_ +#define SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_ + +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/services/surfaces/display_impl.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h" + +namespace cc { +class SurfaceManager; +} + +namespace surfaces { +class SurfacesScheduler; + +class DisplayFactoryImpl : public mojo::DisplayFactory { + public: + DisplayFactoryImpl(cc::SurfaceManager* manager, + uint32_t id_namespace, + SurfacesScheduler* scheduler, + mojo::InterfaceRequest<mojo::DisplayFactory> request); + ~DisplayFactoryImpl() override; + + private: + // mojo::DisplayFactory implementation. + void Create(mojo::ContextProviderPtr context_provider, + mojo::ResourceReturnerPtr returner, + mojo::InterfaceRequest<mojo::Display> display_request) override; + + // We use one ID namespace for all DisplayImpls since the ID is used only by + // cc and not exposed through mojom. + uint32_t id_namespace_; + uint32_t next_local_id_; + SurfacesScheduler* scheduler_; + cc::SurfaceManager* manager_; + mojo::StrongBinding<mojo::DisplayFactory> binding_; +}; + +} // namespace surfaces + +#endif // SERVICES_SURFACES_DISPLAY_FACTORY_IMPL_H_ diff --git a/mojo/services/surfaces/display_impl.cc b/mojo/services/surfaces/display_impl.cc new file mode 100644 index 0000000..3fff760 --- /dev/null +++ b/mojo/services/surfaces/display_impl.cc @@ -0,0 +1,137 @@ +// Copyright 2015 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 "mojo/services/surfaces/display_impl.h" + +#include "cc/output/compositor_frame.h" +#include "cc/surfaces/display.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "mojo/services/surfaces/context_provider_mojo.h" +#include "mojo/services/surfaces/surfaces_output_surface.h" +#include "mojo/services/surfaces/surfaces_scheduler.h" + +namespace surfaces { +namespace { +void CallCallback(const mojo::Closure& callback, cc::SurfaceDrawStatus status) { + callback.Run(); +} +} + +DisplayImpl::DisplayImpl(cc::SurfaceManager* manager, + cc::SurfaceId cc_id, + SurfacesScheduler* scheduler, + mojo::ContextProviderPtr context_provider, + mojo::ResourceReturnerPtr returner, + mojo::InterfaceRequest<mojo::Display> display_request) + : manager_(manager), + factory_(manager, this), + cc_id_(cc_id), + scheduler_(scheduler), + context_provider_(context_provider.Pass()), + returner_(returner.Pass()), + viewport_param_binding_(this), + display_binding_(this, display_request.Pass()) { + mojo::ViewportParameterListenerPtr viewport_parameter_listener; + viewport_param_binding_.Bind(GetProxy(&viewport_parameter_listener)); + context_provider_->Create( + viewport_parameter_listener.Pass(), + base::Bind(&DisplayImpl::OnContextCreated, base::Unretained(this))); +} + +void DisplayImpl::OnContextCreated(mojo::CommandBufferPtr gles2_client) { + DCHECK(!display_); + + cc::RendererSettings settings; + display_.reset(new cc::Display(this, manager_, nullptr, nullptr, settings)); + scheduler_->AddDisplay(display_.get()); + display_->Initialize(make_scoped_ptr(new mojo::DirectOutputSurface( + new mojo::ContextProviderMojo(gles2_client.PassMessagePipe())))); + + factory_.Create(cc_id_); + display_->SetSurfaceId(cc_id_, 1.f); + if (pending_frame_) + Draw(); +} + +DisplayImpl::~DisplayImpl() { + if (display_) { + factory_.Destroy(cc_id_); + scheduler_->RemoveDisplay(display_.get()); + } +} + +void DisplayImpl::SubmitFrame(mojo::FramePtr frame, + const SubmitFrameCallback& callback) { + DCHECK(pending_callback_.is_null()); + pending_frame_ = frame.Pass(); + pending_callback_ = callback; + if (display_) + Draw(); +} + +void DisplayImpl::Draw() { + gfx::Size frame_size = + pending_frame_->passes[0]->output_rect.To<gfx::Rect>().size(); + display_->Resize(frame_size); + factory_.SubmitFrame(cc_id_, + pending_frame_.To<scoped_ptr<cc::CompositorFrame>>(), + base::Bind(&CallCallback, pending_callback_)); + scheduler_->SetNeedsDraw(); + pending_callback_.reset(); +} + +void DisplayImpl::DisplayDamaged() { +} + +void DisplayImpl::DidSwapBuffers() { +} + +void DisplayImpl::DidSwapBuffersComplete() { +} + +void DisplayImpl::CommitVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval) { +} + +void DisplayImpl::OutputSurfaceLost() { + // If our OutputSurface is lost we can't draw until we get a new one. For now, + // destroy the display and create a new one when our ContextProvider provides + // a new one. + // TODO: This is more violent than necessary - we could simply remove this + // display from the scheduler's set and pass a new context in to the + // OutputSurface. It should be able to reinitialize properly. + scheduler_->RemoveDisplay(display_.get()); + display_.reset(); + factory_.Destroy(cc_id_); + viewport_param_binding_.Close(); + mojo::ViewportParameterListenerPtr viewport_parameter_listener; + viewport_param_binding_.Bind(GetProxy(&viewport_parameter_listener)); + context_provider_->Create( + viewport_parameter_listener.Pass(), + base::Bind(&DisplayImpl::OnContextCreated, base::Unretained(this))); +} + +void DisplayImpl::SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) { +} + +void DisplayImpl::OnVSyncParametersUpdated(int64_t timebase, int64_t interval) { + scheduler_->OnVSyncParametersUpdated( + base::TimeTicks::FromInternalValue(timebase), + base::TimeDelta::FromInternalValue(interval)); +} + +void DisplayImpl::ReturnResources(const cc::ReturnedResourceArray& resources) { + if (resources.empty()) + return; + DCHECK(returner_); + + mojo::Array<mojo::ReturnedResourcePtr> ret(resources.size()); + for (size_t i = 0; i < resources.size(); ++i) { + ret[i] = mojo::ReturnedResource::From(resources[i]); + } + returner_->ReturnResources(ret.Pass()); +} + +} // namespace surfaces diff --git a/mojo/services/surfaces/display_impl.h b/mojo/services/surfaces/display_impl.h new file mode 100644 index 0000000..19d11e3 --- /dev/null +++ b/mojo/services/surfaces/display_impl.h @@ -0,0 +1,80 @@ +// Copyright 2015 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. + +#ifndef SERVICES_SURFACES_DISPLAY_IMPL_H_ +#define SERVICES_SURFACES_DISPLAY_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/surfaces/display_client.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_factory_client.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h" + +namespace cc { +class Display; +class SurfaceFactory; +} + +namespace surfaces { +class SurfacesScheduler; + +class DisplayImpl : public mojo::Display, + public mojo::ViewportParameterListener, + public cc::DisplayClient, + public cc::SurfaceFactoryClient { + public: + DisplayImpl(cc::SurfaceManager* manager, + cc::SurfaceId cc_id, + SurfacesScheduler* scheduler, + mojo::ContextProviderPtr context_provider, + mojo::ResourceReturnerPtr returner, + mojo::InterfaceRequest<mojo::Display> display_request); + ~DisplayImpl() override; + + private: + void OnContextCreated(mojo::CommandBufferPtr gles2_client); + + // mojo::Display implementation: + void SubmitFrame(mojo::FramePtr frame, + const SubmitFrameCallback& callback) override; + + // DisplayClient implementation. + void DisplayDamaged() override; + void DidSwapBuffers() override; + void DidSwapBuffersComplete() override; + void CommitVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval) override; + void OutputSurfaceLost() override; + void SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) override; + + // ViewportParameterListener + void OnVSyncParametersUpdated(int64_t timebase, int64_t interval) override; + + // SurfaceFactoryClient implementation. + void ReturnResources(const cc::ReturnedResourceArray& resources) override; + + void Draw(); + + cc::SurfaceManager* manager_; + cc::SurfaceFactory factory_; + cc::SurfaceId cc_id_; + SurfacesScheduler* scheduler_; + mojo::ContextProviderPtr context_provider_; + mojo::ResourceReturnerPtr returner_; + + mojo::FramePtr pending_frame_; + SubmitFrameCallback pending_callback_; + + scoped_ptr<cc::Display> display_; + + mojo::Binding<mojo::ViewportParameterListener> viewport_param_binding_; + mojo::StrongBinding<mojo::Display> display_binding_; + + DISALLOW_COPY_AND_ASSIGN(DisplayImpl); +}; + +} // namespace surfaces + +#endif // SERVICES_SURFACES_DISPLAY_IMPL_H_ diff --git a/mojo/services/surfaces/surfaces_impl.cc b/mojo/services/surfaces/surfaces_impl.cc new file mode 100644 index 0000000..577b6c1 --- /dev/null +++ b/mojo/services/surfaces/surfaces_impl.cc @@ -0,0 +1,81 @@ +// 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 "mojo/services/surfaces/surfaces_impl.h" + +#include "base/trace_event/trace_event.h" +#include "cc/output/compositor_frame.h" +#include "cc/resources/returned_resource.h" +#include "cc/surfaces/surface_id_allocator.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "mojo/services/surfaces/surfaces_scheduler.h" + +using mojo::SurfaceIdPtr; + +namespace surfaces { + +namespace { +void CallCallback(const mojo::Closure& callback, cc::SurfaceDrawStatus status) { + callback.Run(); +} +} + +SurfacesImpl::SurfacesImpl(cc::SurfaceManager* manager, + uint32_t id_namespace, + SurfacesScheduler* scheduler, + mojo::InterfaceRequest<mojo::Surface> request) + : manager_(manager), + factory_(manager, this), + id_namespace_(id_namespace), + scheduler_(scheduler), + binding_(this, request.Pass()) { +} + +SurfacesImpl::~SurfacesImpl() { + factory_.DestroyAll(); +} + +void SurfacesImpl::GetIdNamespace( + const Surface::GetIdNamespaceCallback& callback) { + callback.Run(id_namespace_); +} + +void SurfacesImpl::SetResourceReturner(mojo::ResourceReturnerPtr returner) { + returner_ = returner.Pass(); +} + +void SurfacesImpl::CreateSurface(uint32_t local_id) { + factory_.Create(QualifyIdentifier(local_id)); +} + +void SurfacesImpl::SubmitFrame(uint32_t local_id, + mojo::FramePtr frame, + const mojo::Closure& callback) { + TRACE_EVENT0("mojo", "SurfacesImpl::SubmitFrame"); + factory_.SubmitFrame(QualifyIdentifier(local_id), + frame.To<scoped_ptr<cc::CompositorFrame>>(), + base::Bind(&CallCallback, callback)); + scheduler_->SetNeedsDraw(); +} + +void SurfacesImpl::DestroySurface(uint32_t local_id) { + factory_.Destroy(QualifyIdentifier(local_id)); +} + +void SurfacesImpl::ReturnResources(const cc::ReturnedResourceArray& resources) { + if (resources.empty() || !returner_) + return; + mojo::Array<mojo::ReturnedResourcePtr> ret(resources.size()); + for (size_t i = 0; i < resources.size(); ++i) { + ret[i] = mojo::ReturnedResource::From(resources[i]); + } + returner_->ReturnResources(ret.Pass()); +} + +cc::SurfaceId SurfacesImpl::QualifyIdentifier(uint32_t local_id) { + return cc::SurfaceId(static_cast<uint64_t>(id_namespace_) << 32 | local_id); +} + +} // namespace mojo diff --git a/mojo/services/surfaces/surfaces_impl.h b/mojo/services/surfaces/surfaces_impl.h new file mode 100644 index 0000000..10c6b7b --- /dev/null +++ b/mojo/services/surfaces/surfaces_impl.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef SERVICES_SURFACES_SURFACES_IMPL_H_ +#define SERVICES_SURFACES_SURFACES_IMPL_H_ + +#include "cc/surfaces/display_client.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_factory_client.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/command_buffer.mojom.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/viewport_parameter_listener.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h" + +namespace cc { +class Display; +} + +namespace mojo { +class ApplicationManager; +} + +namespace surfaces { +class SurfacesScheduler; + +class SurfacesImpl : public mojo::Surface, public cc::SurfaceFactoryClient { + public: + SurfacesImpl(cc::SurfaceManager* manager, + uint32_t id_namespace, + SurfacesScheduler* scheduler, + mojo::InterfaceRequest<mojo::Surface> request); + + ~SurfacesImpl() override; + + // Surface implementation. + void GetIdNamespace(const Surface::GetIdNamespaceCallback& callback) override; + void SetResourceReturner(mojo::ResourceReturnerPtr returner) override; + void CreateSurface(uint32_t local_id) override; + void SubmitFrame(uint32_t local_id, + mojo::FramePtr frame, + const mojo::Closure& callback) override; + void DestroySurface(uint32_t local_id) override; + + // SurfaceFactoryClient implementation. + void ReturnResources(const cc::ReturnedResourceArray& resources) override; + + cc::SurfaceFactory* factory() { return &factory_; } + + private: + cc::SurfaceId QualifyIdentifier(uint32_t local_id); + + cc::SurfaceManager* manager_; + cc::SurfaceFactory factory_; + const uint32_t id_namespace_; + SurfacesScheduler* scheduler_; + mojo::ScopedMessagePipeHandle command_buffer_handle_; + mojo::ResourceReturnerPtr returner_; + mojo::StrongBinding<Surface> binding_; + + DISALLOW_COPY_AND_ASSIGN(SurfacesImpl); +}; + +} // namespace surfaces + +#endif // SERVICES_SURFACES_SURFACES_IMPL_H_ diff --git a/mojo/services/surfaces/surfaces_output_surface.cc b/mojo/services/surfaces/surfaces_output_surface.cc new file mode 100644 index 0000000..891ef13 --- /dev/null +++ b/mojo/services/surfaces/surfaces_output_surface.cc @@ -0,0 +1,42 @@ +// 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 "mojo/services/surfaces/surfaces_output_surface.h" + +#include "base/bind.h" +#include "cc/output/compositor_frame.h" +#include "cc/output/context_provider.h" +#include "cc/output/output_surface_client.h" +#include "gpu/command_buffer/client/context_support.h" +#include "gpu/command_buffer/client/gles2_interface.h" + +namespace mojo { + +DirectOutputSurface::DirectOutputSurface( + const scoped_refptr<cc::ContextProvider>& context_provider) + : cc::OutputSurface(context_provider), weak_ptr_factory_(this) { +} + +DirectOutputSurface::~DirectOutputSurface() { +} + +void DirectOutputSurface::SwapBuffers(cc::CompositorFrame* frame) { + DCHECK(context_provider_.get()); + DCHECK(frame->gl_frame_data); + if (frame->gl_frame_data->sub_buffer_rect == + gfx::Rect(frame->gl_frame_data->size)) { + context_provider_->ContextSupport()->Swap(); + } else { + context_provider_->ContextSupport()->PartialSwapBuffers( + frame->gl_frame_data->sub_buffer_rect); + } + uint32_t sync_point = + context_provider_->ContextGL()->InsertSyncPointCHROMIUM(); + context_provider_->ContextSupport()->SignalSyncPoint( + sync_point, base::Bind(&OutputSurface::OnSwapBuffersComplete, + weak_ptr_factory_.GetWeakPtr())); + client_->DidSwapBuffers(); +} + +} // namespace mojo diff --git a/mojo/services/surfaces/surfaces_output_surface.h b/mojo/services/surfaces/surfaces_output_surface.h new file mode 100644 index 0000000..a2e61d0 --- /dev/null +++ b/mojo/services/surfaces/surfaces_output_surface.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_ +#define SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_ + +#include "cc/output/output_surface.h" + +namespace mojo { + +// An OutputSurface implementation that directly draws and +// swaps to an actual GL surface. +class DirectOutputSurface : public cc::OutputSurface { + public: + explicit DirectOutputSurface( + const scoped_refptr<cc::ContextProvider>& context_provider); + ~DirectOutputSurface() override; + + // cc::OutputSurface implementation + void SwapBuffers(cc::CompositorFrame* frame) override; + + private: + base::WeakPtrFactory<DirectOutputSurface> weak_ptr_factory_; +}; + +} // namespace mojo + +#endif // SERVICES_SURFACES_SURFACES_OUTPUT_SURFACE_H_ diff --git a/mojo/services/surfaces/surfaces_scheduler.cc b/mojo/services/surfaces/surfaces_scheduler.cc new file mode 100644 index 0000000..a86dd90 --- /dev/null +++ b/mojo/services/surfaces/surfaces_scheduler.cc @@ -0,0 +1,113 @@ +// 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 "mojo/services/surfaces/surfaces_scheduler.h" + +#include "cc/surfaces/display.h" + +namespace surfaces { + +SurfacesScheduler::SurfacesScheduler() { + cc::SchedulerSettings settings; + scheduler_ = cc::Scheduler::Create( + this, settings, 0, base::MessageLoop::current()->task_runner(), nullptr); + scheduler_->SetCanStart(); + scheduler_->SetVisible(true); + scheduler_->SetCanDraw(true); + scheduler_->SetNeedsCommit(); +} + +SurfacesScheduler::~SurfacesScheduler() { +} + +void SurfacesScheduler::SetNeedsDraw() { + // Don't tell the scheduler we need to draw if we have no active displays + // which can happen if we haven't initialized displays yet or if all active + // displays have lost their context. + if (!displays_.empty()) + scheduler_->SetNeedsRedraw(); +} + +void SurfacesScheduler::OnVSyncParametersUpdated(base::TimeTicks timebase, + base::TimeDelta interval) { + scheduler_->CommitVSyncParameters(timebase, interval); +} + +void SurfacesScheduler::AddDisplay(cc::Display* display) { + DCHECK(displays_.find(display) == displays_.end()); + displays_.insert(display); +} + +void SurfacesScheduler::RemoveDisplay(cc::Display* display) { + auto it = displays_.find(display); + DCHECK(it != displays_.end()); + displays_.erase(it); +} + +void SurfacesScheduler::WillBeginImplFrame(const cc::BeginFrameArgs& args) { +} + +void SurfacesScheduler::ScheduledActionSendBeginMainFrame() { + scheduler_->NotifyBeginMainFrameStarted(); + scheduler_->NotifyReadyToCommit(); +} + +cc::DrawResult SurfacesScheduler::ScheduledActionDrawAndSwapIfPossible() { + base::TimeTicks start = base::TimeTicks::Now(); + for (const auto& it : displays_) { + it->Draw(); + } + base::TimeDelta duration = base::TimeTicks::Now() - start; + + draw_estimate_ = (duration + draw_estimate_) / 2; + return cc::DRAW_SUCCESS; +} + +cc::DrawResult SurfacesScheduler::ScheduledActionDrawAndSwapForced() { + NOTREACHED() << "ScheduledActionDrawAndSwapIfPossible always succeeds."; + return cc::DRAW_SUCCESS; +} + +void SurfacesScheduler::ScheduledActionAnimate() { +} + +void SurfacesScheduler::ScheduledActionCommit() { +} + +void SurfacesScheduler::ScheduledActionActivateSyncTree() { +} + +void SurfacesScheduler::ScheduledActionBeginOutputSurfaceCreation() { + scheduler_->DidCreateAndInitializeOutputSurface(); +} + +void SurfacesScheduler::ScheduledActionPrepareTiles() { +} + +void SurfacesScheduler::DidAnticipatedDrawTimeChange(base::TimeTicks time) { +} + +base::TimeDelta SurfacesScheduler::DrawDurationEstimate() { + return draw_estimate_; +} + +base::TimeDelta SurfacesScheduler::BeginMainFrameToCommitDurationEstimate() { + return base::TimeDelta(); +} + +base::TimeDelta SurfacesScheduler::CommitToActivateDurationEstimate() { + return base::TimeDelta(); +} + +void SurfacesScheduler::DidBeginImplFrameDeadline() { +} + +void SurfacesScheduler::SendBeginFramesToChildren( + const cc::BeginFrameArgs& args) { +} + +void SurfacesScheduler::SendBeginMainFrameNotExpectedSoon() { +} + +} // namespace mojo diff --git a/mojo/services/surfaces/surfaces_scheduler.h b/mojo/services/surfaces/surfaces_scheduler.h new file mode 100644 index 0000000..1c98af1 --- /dev/null +++ b/mojo/services/surfaces/surfaces_scheduler.h @@ -0,0 +1,58 @@ +// 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. + +#ifndef MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_ +#define MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_ + +#include <set> + +#include "cc/scheduler/scheduler.h" + +namespace cc { +class Display; +} + +namespace surfaces { + +class SurfacesScheduler : public cc::SchedulerClient { + public: + SurfacesScheduler(); + ~SurfacesScheduler() override; + + void SetNeedsDraw(); + + void OnVSyncParametersUpdated(base::TimeTicks timebase, + base::TimeDelta interval); + + void AddDisplay(cc::Display* display); + void RemoveDisplay(cc::Display* display); + + private: + void WillBeginImplFrame(const cc::BeginFrameArgs& args) override; + void ScheduledActionSendBeginMainFrame() override; + cc::DrawResult ScheduledActionDrawAndSwapIfPossible() override; + cc::DrawResult ScheduledActionDrawAndSwapForced() override; + void ScheduledActionAnimate() override; + void ScheduledActionCommit() override; + void ScheduledActionActivateSyncTree() override; + void ScheduledActionBeginOutputSurfaceCreation() override; + void ScheduledActionPrepareTiles() override; + void DidAnticipatedDrawTimeChange(base::TimeTicks time) override; + base::TimeDelta DrawDurationEstimate() override; + base::TimeDelta BeginMainFrameToCommitDurationEstimate() override; + base::TimeDelta CommitToActivateDurationEstimate() override; + void DidBeginImplFrameDeadline() override; + void SendBeginFramesToChildren(const cc::BeginFrameArgs& args) override; + void SendBeginMainFrameNotExpectedSoon() override; + + std::set<cc::Display*> displays_; + scoped_ptr<cc::Scheduler> scheduler_; + base::TimeDelta draw_estimate_; + + DISALLOW_COPY_AND_ASSIGN(SurfacesScheduler); +}; + +} // namespace mojo + +#endif // MOJO_SERVICES_SURFACES_SURFACES_SCHEDULER_H_ diff --git a/mojo/services/surfaces/surfaces_service_application.cc b/mojo/services/surfaces/surfaces_service_application.cc new file mode 100644 index 0000000..7d3e370 --- /dev/null +++ b/mojo/services/surfaces/surfaces_service_application.cc @@ -0,0 +1,54 @@ +// 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 "mojo/services/surfaces/surfaces_service_application.h" + +#include "mojo/application/application_runner_chromium.h" +#include "mojo/public/c/system/main.h" +#include "mojo/services/surfaces/display_factory_impl.h" +#include "mojo/services/surfaces/surfaces_impl.h" +#include "mojo/services/surfaces/surfaces_scheduler.h" + +namespace surfaces { + +SurfacesServiceApplication::SurfacesServiceApplication() + : next_id_namespace_(1u) { +} + +SurfacesServiceApplication::~SurfacesServiceApplication() { +} + +void SurfacesServiceApplication::Initialize(mojo::ApplicationImpl* app) { + tracing_.Initialize(app); + scheduler_.reset(new SurfacesScheduler); +} + +bool SurfacesServiceApplication::ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) { + connection->AddService<mojo::DisplayFactory>(this); + connection->AddService<mojo::Surface>(this); + return true; +} + +void SurfacesServiceApplication::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::DisplayFactory> request) { + new DisplayFactoryImpl(&manager_, next_id_namespace_++, scheduler_.get(), + request.Pass()); +} + +void SurfacesServiceApplication::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::Surface> request) { + new SurfacesImpl(&manager_, next_id_namespace_++, scheduler_.get(), + request.Pass()); +} + +} // namespace surfaces + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner( + new surfaces::SurfacesServiceApplication); + return runner.Run(shell_handle); +} diff --git a/mojo/services/surfaces/surfaces_service_application.h b/mojo/services/surfaces/surfaces_service_application.h new file mode 100644 index 0000000..cae7b35 --- /dev/null +++ b/mojo/services/surfaces/surfaces_service_application.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_ +#define SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_ + +#include "base/macros.h" +#include "cc/surfaces/surface_manager.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h" + +namespace mojo { +class ApplicationConnection; +} + +namespace surfaces { +class SurfacesScheduler; + +class SurfacesServiceApplication + : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory<mojo::DisplayFactory>, + public mojo::InterfaceFactory<mojo::Surface> { + public: + SurfacesServiceApplication(); + ~SurfacesServiceApplication() override; + + // ApplicationDelegate implementation. + void Initialize(mojo::ApplicationImpl* app) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + + // InterfaceFactory<DisplayFactory> implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::DisplayFactory> request) override; + + // InterfaceFactory<Surface> implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::Surface> request) override; + + private: + cc::SurfaceManager manager_; + uint32_t next_id_namespace_; + scoped_ptr<SurfacesScheduler> scheduler_; + mojo::TracingImpl tracing_; + + DISALLOW_COPY_AND_ASSIGN(SurfacesServiceApplication); +}; + +} // namespace surfaces + +#endif // SERVICES_SURFACES_SURFACES_SERVICE_APPLICATION_H_ diff --git a/mojo/services/test_service/BUILD.gn b/mojo/services/test_service/BUILD.gn new file mode 100644 index 0000000..f602afc --- /dev/null +++ b/mojo/services/test_service/BUILD.gn @@ -0,0 +1,61 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") + +mojom("bindings") { + sources = [ + "test_request_tracker.mojom", + "test_service.mojom", + ] +} + +mojo_native_application("test_app") { + sources = [ + "test_service_application.cc", + "test_service_application.h", + "test_service_impl.cc", + "test_service_impl.h", + "test_time_service_impl.cc", + "test_time_service_impl.h", + "tracked_service.cc", + "tracked_service.h", + ] + + deps = [ + ":bindings", + "//base", + "//base:i18n", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo/src/mojo/public/cpp/application:standalone", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/mojo/src/mojo/public/cpp/utility", + ] +} + +mojo_native_application("test_request_tracker_app") { + sources = [ + "test_request_tracker_application.cc", + "test_request_tracker_application.h", + "test_request_tracker_impl.cc", + "test_request_tracker_impl.h", + "test_time_service_impl.cc", + "test_time_service_impl.h", + "tracked_service.cc", + "tracked_service.h", + ] + + deps = [ + ":bindings", + "//base", + "//base:i18n", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo/src/mojo/public/cpp/application:standalone", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/cpp/system", + "//third_party/mojo/src/mojo/public/cpp/utility", + ] +} diff --git a/mojo/services/test_service/test_request_tracker.mojom b/mojo/services/test_service/test_request_tracker.mojom new file mode 100644 index 0000000..91f8a2d --- /dev/null +++ b/mojo/services/test_service/test_request_tracker.mojom @@ -0,0 +1,34 @@ +// 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. + +module mojo.test; + +// Various counters that services can periodically send to a +// TestTrackedRequestService for recording. +struct ServiceStats { + uint64 num_new_requests; + double health; +}; + +// A per-service summary of all the ServiceStats the +// TestTrackedRequestService has observed. +struct ServiceReport { + string? service_name; + uint64 total_requests; + double mean_health; +}; + +// A simple interface to obtain a "report" from all services that have +// opted to connect themselves to for request tracking. +interface TestTrackedRequestService { + GetReport() => (array<ServiceReport?>? report); +}; + +// TestRequestTracker records ServiceStats for an individual service +// connection for aggregation in a TestTrackedRequestService. +interface TestRequestTracker { + SetNameAndReturnId(string service_name) => (uint64 id); + // Upload a ServiceStats for tracking. + RecordStats(uint64 client_id, ServiceStats? stats); +}; diff --git a/mojo/services/test_service/test_request_tracker_application.cc b/mojo/services/test_service/test_request_tracker_application.cc new file mode 100644 index 0000000..2fcf1f3 --- /dev/null +++ b/mojo/services/test_service/test_request_tracker_application.cc @@ -0,0 +1,63 @@ +// 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 "mojo/services/test_service/test_request_tracker_application.h" + +#include <assert.h> + +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_runner.h" +#include "mojo/services/test_service/test_time_service_impl.h" + +namespace mojo { +namespace test { + +TestRequestTrackerApplication::TestRequestTrackerApplication() + : app_impl_(nullptr) { +} + +TestRequestTrackerApplication::~TestRequestTrackerApplication() { +} + +void TestRequestTrackerApplication::Initialize(ApplicationImpl* app) { + app_impl_ = app; +} + +bool TestRequestTrackerApplication::ConfigureIncomingConnection( + ApplicationConnection* connection) { + // Every instance of the service and recorder shares the context. + // Note, this app is single-threaded, so this is thread safe. + connection->AddService<TestTimeService>(this); + connection->AddService<TestRequestTracker>(this); + connection->AddService<TestTrackedRequestService>(this); + return true; +} + +void TestRequestTrackerApplication::Create( + ApplicationConnection* connection, + InterfaceRequest<TestTimeService> request) { + new TestTimeServiceImpl(app_impl_, request.Pass()); +} + +void TestRequestTrackerApplication::Create( + ApplicationConnection* connection, + InterfaceRequest<TestRequestTracker> request) { + new TestRequestTrackerImpl(request.Pass(), &context_); +} + +void TestRequestTrackerApplication::Create( + ApplicationConnection* connection, + InterfaceRequest<TestTrackedRequestService> request) { + new TestTrackedRequestServiceImpl(request.Pass(), &context_); +} + +} // namespace test +} // namespace mojo + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunner runner( + new mojo::test::TestRequestTrackerApplication); + return runner.Run(shell_handle); +} diff --git a/mojo/services/test_service/test_request_tracker_application.h b/mojo/services/test_service/test_request_tracker_application.h new file mode 100644 index 0000000..761a845 --- /dev/null +++ b/mojo/services/test_service/test_request_tracker_application.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_ +#define SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_ + +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_request_tracker_impl.h" + +namespace mojo { +class ApplicationImpl; +namespace test { +class TestTimeService; + +// Embeds TestRequestTracker mojo services into an application. +class TestRequestTrackerApplication + : public ApplicationDelegate, + public InterfaceFactory<TestTimeService>, + public InterfaceFactory<TestRequestTracker>, + public InterfaceFactory<TestTrackedRequestService> { + public: + TestRequestTrackerApplication(); + ~TestRequestTrackerApplication() override; + + void Initialize(ApplicationImpl* app) override; + + // ApplicationDelegate methods: + bool ConfigureIncomingConnection(ApplicationConnection* connection) override; + + // InterfaceFactory<TestTimeService> methods: + void Create(ApplicationConnection* connection, + InterfaceRequest<TestTimeService> request) override; + + // InterfaceFactory<TestRequestTracker> methods: + void Create(ApplicationConnection* connection, + InterfaceRequest<TestRequestTracker> request) override; + + // InterfaceFactory<TestTrackedRequestService> methods: + void Create(ApplicationConnection* connection, + InterfaceRequest<TestTrackedRequestService> request) override; + + private: + ApplicationImpl* app_impl_; + TrackingContext context_; + MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerApplication); +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_APPLICATION_H_ diff --git a/mojo/services/test_service/test_request_tracker_impl.cc b/mojo/services/test_service/test_request_tracker_impl.cc new file mode 100644 index 0000000..09fa4d5 --- /dev/null +++ b/mojo/services/test_service/test_request_tracker_impl.cc @@ -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. + +#include "mojo/services/test_service/test_request_tracker_impl.h" + +namespace mojo { +namespace test { + +TrackingContext::TrackingContext() : next_id(1) { +} + +TrackingContext::~TrackingContext() { +} + +TestRequestTrackerImpl::TestRequestTrackerImpl( + InterfaceRequest<TestRequestTracker> request, + TrackingContext* context) + : context_(context), binding_(this, request.Pass()), weak_factory_(this) { +} + +TestRequestTrackerImpl::~TestRequestTrackerImpl() { +} + +void TestRequestTrackerImpl::RecordStats( + uint64_t client_id, + ServiceStatsPtr stats) { + assert(context_->ids_to_names.find(client_id) != + context_->ids_to_names.end()); + context_->records[client_id].push_back(*stats); +} + +void TestRequestTrackerImpl::SetNameAndReturnId( + const String& service_name, + const Callback<void(uint64_t id)>& callback) { + uint64_t id = context_->next_id++; + callback.Run(id); + DCHECK(context_->ids_to_names.find(id) == context_->ids_to_names.end()); + context_->ids_to_names[id] = service_name; +} + +TestTrackedRequestServiceImpl::TestTrackedRequestServiceImpl( + InterfaceRequest<TestTrackedRequestService> request, + TrackingContext* context) + : context_(context), binding_(this, request.Pass()) { +} + +TestTrackedRequestServiceImpl::~TestTrackedRequestServiceImpl() { +} + +void TestTrackedRequestServiceImpl::GetReport( + const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>& callback) { + mojo::Array<ServiceReportPtr> reports; + for (AllRecordsMap::const_iterator it1 = context_->records.begin(); + it1 != context_->records.end(); ++it1) { + ServiceReportPtr report(ServiceReport::New()); + report->service_name = context_->ids_to_names[it1->first]; + double mean_health_numerator = 0; + size_t num_samples = it1->second.size(); + if (num_samples == 0) + continue; + + for (std::vector<ServiceStats>::const_iterator it2 = it1->second.begin(); + it2 != it1->second.end(); ++it2) { + report->total_requests += it2->num_new_requests; + mean_health_numerator += it2->health; + } + report->mean_health = mean_health_numerator / num_samples; + reports.push_back(report.Pass()); + } + callback.Run(reports.Pass()); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/services/test_service/test_request_tracker_impl.h b/mojo/services/test_service/test_request_tracker_impl.h new file mode 100644 index 0000000..0ed220a --- /dev/null +++ b/mojo/services/test_service/test_request_tracker_impl.h @@ -0,0 +1,69 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_ +#define SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_ + +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_request_tracker.mojom.h" + +namespace mojo { +class ApplicationConnection; +namespace test { + +typedef std::map<uint64_t, std::vector<ServiceStats> > AllRecordsMap; + +// Shared state between all instances of TestRequestTrackerImpl +// and the master TrackedRequestService. +struct TrackingContext { + TrackingContext(); + ~TrackingContext(); + AllRecordsMap records; + std::map<uint64_t, std::string> ids_to_names; + uint64_t next_id; +}; + +class TestRequestTrackerImpl : public TestRequestTracker { + public: + TestRequestTrackerImpl(InterfaceRequest<TestRequestTracker> request, + TrackingContext* context); + ~TestRequestTrackerImpl() override; + + // TestRequestTracker. + void SetNameAndReturnId(const String& service_name, + const Callback<void(uint64_t id)>& callback) override; + void RecordStats(uint64_t client_id, ServiceStatsPtr stats) override; + + private: + void UploaderNameCallback(uint64_t id, const mojo::String& name); + TrackingContext* context_; + StrongBinding<TestRequestTracker> binding_; + base::WeakPtrFactory<TestRequestTrackerImpl> weak_factory_; + MOJO_DISALLOW_COPY_AND_ASSIGN(TestRequestTrackerImpl); +}; + +class TestTrackedRequestServiceImpl : public TestTrackedRequestService { + public: + TestTrackedRequestServiceImpl( + InterfaceRequest<TestTrackedRequestService> request, + TrackingContext* context); + ~TestTrackedRequestServiceImpl() override; + + // |TestTrackedRequestService| implementation. + void GetReport(const mojo::Callback<void(mojo::Array<ServiceReportPtr>)>& + callback) override; + + private: + TrackingContext* context_; + StrongBinding<TestTrackedRequestService> binding_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestTrackedRequestServiceImpl); +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TEST_REQUEST_TRACKER_IMPL_H_ diff --git a/mojo/services/test_service/test_service.mojom b/mojo/services/test_service/test_service.mojom new file mode 100644 index 0000000..5144add --- /dev/null +++ b/mojo/services/test_service/test_service.mojom @@ -0,0 +1,19 @@ +// 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. + +module mojo.test; + +interface TestService { + Ping() => (); + // Connect to a TestTimeService at |app_url| and ferry the data back + // in |response|. + ConnectToAppAndGetTime(string? app_url) => (int64 time_usec); + StartTrackingRequests() => (); +}; + +interface TestTimeService { + // Provides a constant time value. + GetPartyTime() => (int64 time_usec); + StartTrackingRequests() => (); +}; diff --git a/mojo/services/test_service/test_service_application.cc b/mojo/services/test_service/test_service_application.cc new file mode 100644 index 0000000..70a2824 --- /dev/null +++ b/mojo/services/test_service/test_service_application.cc @@ -0,0 +1,66 @@ +// 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 "mojo/services/test_service/test_service_application.h" + +#include <assert.h> + +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_runner.h" +#include "mojo/public/cpp/utility/run_loop.h" +#include "mojo/services/test_service/test_service_impl.h" +#include "mojo/services/test_service/test_time_service_impl.h" + +namespace mojo { +namespace test { + +TestServiceApplication::TestServiceApplication() + : ref_count_(0), app_impl_(nullptr) { +} + +TestServiceApplication::~TestServiceApplication() { +} + +void TestServiceApplication::Initialize(ApplicationImpl* app) { + app_impl_ = app; +} + +bool TestServiceApplication::ConfigureIncomingConnection( + ApplicationConnection* connection) { + connection->AddService<TestService>(this); + connection->AddService<TestTimeService>(this); + return true; +} + +void TestServiceApplication::Create(ApplicationConnection* connection, + InterfaceRequest<TestService> request) { + new TestServiceImpl(app_impl_, this, request.Pass()); + AddRef(); +} + +void TestServiceApplication::Create(ApplicationConnection* connection, + InterfaceRequest<TestTimeService> request) { + new TestTimeServiceImpl(app_impl_, request.Pass()); +} + +void TestServiceApplication::AddRef() { + assert(ref_count_ >= 0); + ref_count_++; +} + +void TestServiceApplication::ReleaseRef() { + assert(ref_count_ > 0); + ref_count_--; + if (ref_count_ <= 0) + RunLoop::current()->Quit(); +} + +} // namespace test +} // namespace mojo + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunner runner(new mojo::test::TestServiceApplication); + return runner.Run(shell_handle); +} diff --git a/mojo/services/test_service/test_service_application.h b/mojo/services/test_service/test_service_application.h new file mode 100644 index 0000000..6b36f07 --- /dev/null +++ b/mojo/services/test_service/test_service_application.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ +#define SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ + +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/system/macros.h" + +namespace mojo { +class ApplicationConnection; + +namespace test { +class TestService; +class TestTimeService; + +class TestServiceApplication : public ApplicationDelegate, + public InterfaceFactory<TestService>, + public InterfaceFactory<TestTimeService> { + public: + TestServiceApplication(); + ~TestServiceApplication() override; + + void Initialize(ApplicationImpl* app) override; + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override; + + // InterfaceFactory<TestService> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<TestService> request) override; + + // InterfaceFactory<TestTimeService> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<TestTimeService> request) override; + + void AddRef(); + void ReleaseRef(); + + private: + int ref_count_; + ApplicationImpl* app_impl_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceApplication); +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TEST_SERVICE_APPLICATION_H_ diff --git a/mojo/services/test_service/test_service_impl.cc b/mojo/services/test_service/test_service_impl.cc new file mode 100644 index 0000000..de913eb --- /dev/null +++ b/mojo/services/test_service/test_service_impl.cc @@ -0,0 +1,65 @@ +// 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 "mojo/services/test_service/test_service_impl.h" + +#include "base/bind.h" +#include "base/i18n/time_formatting.h" +#include "base/strings/utf_string_conversions.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/test_service/test_service_application.h" +#include "mojo/services/test_service/test_time_service_impl.h" +#include "mojo/services/test_service/tracked_service.h" + +namespace mojo { +namespace test { + +TestServiceImpl::TestServiceImpl(ApplicationImpl* app_impl, + TestServiceApplication* application, + InterfaceRequest<TestService> request) + : application_(application), + app_impl_(app_impl), + binding_(this, request.Pass()) { + binding_.set_error_handler(this); +} + +TestServiceImpl::~TestServiceImpl() { +} + +void TestServiceImpl::OnConnectionError() { + application_->ReleaseRef(); +} + +void TestServiceImpl::Ping(const mojo::Callback<void()>& callback) { + if (tracking_) + tracking_->RecordNewRequest(); + callback.Run(); +} + +void SendTimeResponse( + const mojo::Callback<void(int64_t)>& requestor_callback, + int64_t time_usec) { + requestor_callback.Run(time_usec); +} + +void TestServiceImpl::ConnectToAppAndGetTime( + const mojo::String& app_url, + const mojo::Callback<void(int64_t)>& callback) { + app_impl_->ConnectToService(app_url, &time_service_); + if (tracking_) { + tracking_->RecordNewRequest(); + time_service_->StartTrackingRequests(mojo::Callback<void()>()); + } + time_service_->GetPartyTime(base::Bind(&SendTimeResponse, callback)); +} + +void TestServiceImpl::StartTrackingRequests( + const mojo::Callback<void()>& callback) { + TestRequestTrackerPtr tracker; + app_impl_->ConnectToService("mojo:test_request_tracker_app", &tracker); + tracking_.reset(new TrackedService(tracker.Pass(), Name_, callback)); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/services/test_service/test_service_impl.h b/mojo/services/test_service/test_service_impl.h new file mode 100644 index 0000000..475a95b --- /dev/null +++ b/mojo/services/test_service/test_service_impl.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ +#define SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_service.mojom.h" + +namespace mojo { +class ApplicationImpl; +namespace test { + +class TrackedService; +class TestServiceApplication; + +class TestServiceImpl : public TestService, ErrorHandler { + public: + TestServiceImpl(ApplicationImpl* app_impl, + TestServiceApplication* application, + InterfaceRequest<TestService> request); + ~TestServiceImpl() override; + + // |TestService| methods: + void Ping(const mojo::Callback<void()>& callback) override; + void ConnectToAppAndGetTime( + const mojo::String& app_url, + const mojo::Callback<void(int64_t)>& callback) override; + void StartTrackingRequests(const mojo::Callback<void()>& callback) override; + + // ErrorHandler methods: + void OnConnectionError() override; + + private: + TestServiceApplication* const application_; + ApplicationImpl* const app_impl_; + TestTimeServicePtr time_service_; + scoped_ptr<TrackedService> tracking_; + StrongBinding<TestService> binding_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestServiceImpl); +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TEST_SERVICE_IMPL_H_ diff --git a/mojo/services/test_service/test_time_service_impl.cc b/mojo/services/test_service/test_time_service_impl.cc new file mode 100644 index 0000000..d11d346 --- /dev/null +++ b/mojo/services/test_service/test_time_service_impl.cc @@ -0,0 +1,43 @@ +// 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 "base/time/time.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/test_service/test_request_tracker.mojom.h" +#include "mojo/services/test_service/test_time_service_impl.h" +#include "mojo/services/test_service/tracked_service.h" + +namespace mojo { +namespace test { + +TestTimeServiceImpl::TestTimeServiceImpl( + ApplicationImpl* app_impl, + InterfaceRequest<TestTimeService> request) + : app_impl_(app_impl), binding_(this, request.Pass()) { +} + +TestTimeServiceImpl::~TestTimeServiceImpl() { +} + +void TestTimeServiceImpl::StartTrackingRequests( + const mojo::Callback<void()>& callback) { + TestRequestTrackerPtr tracker; + app_impl_->ConnectToService("mojo:test_request_tracker_app", &tracker); + tracking_.reset(new TrackedService(tracker.Pass(), Name_, callback)); +} + +void TestTimeServiceImpl::GetPartyTime( + const mojo::Callback<void(int64_t)>& callback) { + if (tracking_) + tracking_->RecordNewRequest(); + base::Time frozen_time(base::Time::UnixEpoch() + + base::TimeDelta::FromDays(10957) + + base::TimeDelta::FromHours(7) + + base::TimeDelta::FromMinutes(59)); + int64 time(frozen_time.ToInternalValue()); + callback.Run(time); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/services/test_service/test_time_service_impl.h b/mojo/services/test_service/test_time_service_impl.h new file mode 100644 index 0000000..242f66c --- /dev/null +++ b/mojo/services/test_service/test_time_service_impl.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_ +#define SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_service.mojom.h" + +namespace mojo { + +class ApplicationConnection; + +namespace test { + +class TrackedService; + +class TestTimeServiceImpl : public TestTimeService { + public: + TestTimeServiceImpl(ApplicationImpl* app_impl, + InterfaceRequest<TestTimeService> request); + ~TestTimeServiceImpl() override; + + // |TestTimeService| methods: + void GetPartyTime( + const mojo::Callback<void(int64_t time_usec)>& callback) override; + void StartTrackingRequests(const mojo::Callback<void()>& callback) override; + + private: + ApplicationImpl* app_impl_; + scoped_ptr<TrackedService> tracking_; + StrongBinding<TestTimeService> binding_; + MOJO_DISALLOW_COPY_AND_ASSIGN(TestTimeServiceImpl); +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TEST_TIME_SERVICE_IMPL_H_ diff --git a/mojo/services/test_service/tracked_service.cc b/mojo/services/test_service/tracked_service.cc new file mode 100644 index 0000000..a2fedfc --- /dev/null +++ b/mojo/services/test_service/tracked_service.cc @@ -0,0 +1,53 @@ +// 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 "mojo/services/test_service/tracked_service.h" + +#include "base/bind.h" + +namespace mojo { +namespace test { + +TrackedService::TrackedService(TestRequestTrackerPtr tracker, + const std::string& service_name, + const mojo::Callback<void()>& callback) + : id_(0u), + requests_since_upload_(0u), + service_name_(service_name), + tracker_(tracker.Pass()), + tracking_connected_callback_(callback) { + tracker_->SetNameAndReturnId( + service_name, base::Bind(&TrackedService::SetId, base::Unretained(this))); +} + +TrackedService::~TrackedService() { +} + +void TrackedService::RecordNewRequest() { + requests_since_upload_++; + if (id_ == 0u) + return; + SendStats(); +} + +void TrackedService::SendStats() { + ServiceStatsPtr stats(ServiceStats::New()); + stats->num_new_requests = requests_since_upload_; + stats->health = 0.7; + tracker_->RecordStats(id_, stats.Pass()); + requests_since_upload_ = 0u; +} + +void TrackedService::SetId(uint64_t id) { + assert(id != 0u); + assert(id_ == 0u); + id_ = id; + tracking_connected_callback_.Run(); + if (requests_since_upload_ == 0u) + return; + SendStats(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/services/test_service/tracked_service.h b/mojo/services/test_service/tracked_service.h new file mode 100644 index 0000000..62ec14a --- /dev/null +++ b/mojo/services/test_service/tracked_service.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_ +#define SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_ + +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/test_service/test_request_tracker.mojom.h" + +namespace mojo { +namespace test { + +class TrackedService { + public: + TrackedService(TestRequestTrackerPtr tracker, + const std::string& service_name, + const mojo::Callback<void()>& tracking_connected_callback); + ~TrackedService(); + + // Call whenever an event happens that you want to be recorded. + void RecordNewRequest(); + + private: + void SetId(uint64_t id); + void SendStats(); + + uint64_t id_; + uint64_t requests_since_upload_; + const std::string service_name_; + TestRequestTrackerPtr tracker_; + mojo::Callback<void()> tracking_connected_callback_; +}; + +} // namespace test +} // namespace mojo + +#endif // SERVICES_TEST_SERVICE_TRACKED_SERVICE_H_ diff --git a/mojo/services/tracing/BUILD.gn b/mojo/services/tracing/BUILD.gn new file mode 100644 index 0000000..0555476 --- /dev/null +++ b/mojo/services/tracing/BUILD.gn @@ -0,0 +1,29 @@ +# 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. + +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") + +mojo_native_application("tracing") { + sources = [ + "main.cc", + "trace_data_sink.cc", + "trace_data_sink.h", + ] + + deps = [ + ":bindings", + "//base", + "//mojo/application", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo/src/mojo/public/cpp/system", + ] +} + +mojom("bindings") { + sources = [ + "tracing.mojom", + ] +} diff --git a/mojo/services/tracing/DEPS b/mojo/services/tracing/DEPS new file mode 100644 index 0000000..bfc9a9e --- /dev/null +++ b/mojo/services/tracing/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/application", +] diff --git a/mojo/services/tracing/main.cc b/mojo/services/tracing/main.cc new file mode 100644 index 0000000..6379a0a --- /dev/null +++ b/mojo/services/tracing/main.cc @@ -0,0 +1,116 @@ +// 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 "base/bind.h" +#include "base/memory/scoped_vector.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/weak_binding_set.h" +#include "mojo/common/weak_interface_ptr_set.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/services/tracing/trace_data_sink.h" +#include "mojo/services/tracing/tracing.mojom.h" + +namespace tracing { + +namespace { + +class CollectorImpl : public TraceDataCollector { + public: + CollectorImpl(mojo::InterfaceRequest<TraceDataCollector> request, + TraceDataSink* sink) + : sink_(sink), binding_(this, request.Pass()) {} + + ~CollectorImpl() override {} + + // tracing::TraceDataCollector implementation. + void DataCollected(const mojo::String& json) override { + sink_->AddChunk(json.To<std::string>()); + } + + private: + TraceDataSink* sink_; + mojo::Binding<TraceDataCollector> binding_; + + DISALLOW_COPY_AND_ASSIGN(CollectorImpl); +}; + +} // namespace + +class TracingApp : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory<TraceCoordinator>, + public TraceCoordinator { + public: + TracingApp() {} + ~TracingApp() override {} + + private: + // mojo::ApplicationDelegate implementation. + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + connection->AddService<TraceCoordinator>(this); + + // If someone connects to us they may want to use the TraceCoordinator + // interface and/or they may want to expose themselves to be traced. Attempt + // to connect to the TraceController interface to see if the application + // connecting to us wants to be traced. They can refuse the connection or + // close the pipe if not. + TraceControllerPtr controller_ptr; + connection->ConnectToService(&controller_ptr); + controller_ptrs_.AddInterfacePtr(controller_ptr.Pass()); + return true; + } + + // mojo::InterfaceFactory<TraceCoordinator> implementation. + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<TraceCoordinator> request) override { + coordinator_bindings_.AddBinding(this, request.Pass()); + } + + // tracing::TraceCoordinator implementation. + void Start(mojo::ScopedDataPipeProducerHandle stream, + const mojo::String& categories) override { + sink_.reset(new TraceDataSink(stream.Pass())); + controller_ptrs_.ForAllPtrs( + [categories, this](TraceController* controller) { + TraceDataCollectorPtr ptr; + collector_impls_.push_back( + new CollectorImpl(GetProxy(&ptr), sink_.get())); + controller->StartTracing(categories, ptr.Pass()); + }); + } + void StopAndFlush() override { + controller_ptrs_.ForAllPtrs( + [](TraceController* controller) { controller->StopTracing(); }); + + // TODO: We really should keep track of how many connections we have here + // and flush + reset the sink after we receive a EndTracing or a detect a + // pipe closure on all pipes. + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&TracingApp::AllDataCollected, base::Unretained(this)), + base::TimeDelta::FromSeconds(1)); + } + + void AllDataCollected() { + collector_impls_.clear(); + sink_->Flush(); + } + + scoped_ptr<TraceDataSink> sink_; + ScopedVector<CollectorImpl> collector_impls_; + mojo::WeakInterfacePtrSet<TraceController> controller_ptrs_; + mojo::WeakBindingSet<TraceCoordinator> coordinator_bindings_; + + DISALLOW_COPY_AND_ASSIGN(TracingApp); +}; + +} // namespace tracing + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new tracing::TracingApp); + return runner.Run(shell_handle); +} diff --git a/mojo/services/tracing/trace_data_sink.cc b/mojo/services/tracing/trace_data_sink.cc new file mode 100644 index 0000000..151a9d3 --- /dev/null +++ b/mojo/services/tracing/trace_data_sink.cc @@ -0,0 +1,43 @@ +// 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 "mojo/services/tracing/trace_data_sink.h" + +#include "base/logging.h" +#include "mojo/common/data_pipe_utils.h" + +using mojo::common::BlockingCopyFromString; + +namespace tracing { +namespace { + +const char kStart[] = "{\"traceEvents\":["; +const char kEnd[] = "]}"; + +} // namespace + +TraceDataSink::TraceDataSink(mojo::ScopedDataPipeProducerHandle pipe) + : pipe_(pipe.Pass()), empty_(true) { + BlockingCopyFromString(kStart, pipe_); +} + +TraceDataSink::~TraceDataSink() { + if (pipe_.is_valid()) + Flush(); + DCHECK(!pipe_.is_valid()); +} + +void TraceDataSink::AddChunk(const std::string& json) { + if (!empty_) + BlockingCopyFromString(",", pipe_); + empty_ = false; + BlockingCopyFromString(json, pipe_); +} + +void TraceDataSink::Flush() { + BlockingCopyFromString(kEnd, pipe_); + pipe_.reset(); +} + +} // namespace tracing diff --git a/mojo/services/tracing/trace_data_sink.h b/mojo/services/tracing/trace_data_sink.h new file mode 100644 index 0000000..c5d3f14 --- /dev/null +++ b/mojo/services/tracing/trace_data_sink.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef SERVICES_TRACING_TRACE_DATA_SINK_H_ +#define SERVICES_TRACING_TRACE_DATA_SINK_H_ + +#include <string> + +#include "base/basictypes.h" +#include "mojo/public/cpp/system/data_pipe.h" + +namespace tracing { + +class TraceDataSink { + public: + explicit TraceDataSink(mojo::ScopedDataPipeProducerHandle pipe); + ~TraceDataSink(); + + void AddChunk(const std::string& json); + void Flush(); + + private: + mojo::ScopedDataPipeProducerHandle pipe_; + bool empty_; + + DISALLOW_COPY_AND_ASSIGN(TraceDataSink); +}; + +} // namespace tracing + +#endif // SERVICES_TRACING_TRACE_DATA_SINK_H_ diff --git a/mojo/services/tracing/tracing.mojom b/mojo/services/tracing/tracing.mojom new file mode 100644 index 0000000..fa8be1f --- /dev/null +++ b/mojo/services/tracing/tracing.mojom @@ -0,0 +1,28 @@ +// 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. + +module tracing; + +// To participate in the tracing ecosystem, implement the TraceController +// interface and connect to the tracing app. Then, when the controller's Start() +// function is called collect tracing data and pass it back via the provided +// TraceDataCollector interface up until Stop() is called. + +interface TraceController { + StartTracing(string categories, TraceDataCollector collector); + StopTracing(); +}; + +interface TraceDataCollector { + DataCollected(string json); +}; + +interface TraceCoordinator { + // Request tracing data from all connected TraceControllers to stream to + // |stream|. + Start(handle<data_pipe_producer> stream, string categories); + + // Stop tracing and flush results to file. + StopAndFlush(); +}; diff --git a/mojo/services/view_manager/BUILD.gn b/mojo/services/view_manager/BUILD.gn new file mode 100644 index 0000000..a96bcc6 --- /dev/null +++ b/mojo/services/view_manager/BUILD.gn @@ -0,0 +1,179 @@ +# 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. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//testing/test.gni") + +mojo_native_application("view_manager") { + sources = [ + "main.cc", + "view_manager_app.cc", + "view_manager_app.h", + ] + + deps = [ + ":view_manager_lib", + "//base", + "//mojo/application", + "//mojo/common:tracing_impl", + "//mojo/environment:chromium", + "//mojo/converters/geometry", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} + +source_set("view_manager_lib") { + sources = [ + "access_policy.h", + "access_policy_delegate.h", + "animation_runner.cc", + "animation_runner.h", + "animation_runner_observer.h", + "client_connection.cc", + "client_connection.h", + "connection_manager.cc", + "connection_manager.h", + "connection_manager_delegate.h", + "default_access_policy.cc", + "default_access_policy.h", + "display_manager.cc", + "display_manager.h", + "scheduled_animation_group.cc", + "scheduled_animation_group.h", + "server_view.cc", + "server_view.h", + "server_view_delegate.h", + "view_coordinate_conversions.cc", + "view_coordinate_conversions.h", + "view_manager_service_impl.cc", + "view_manager_service_impl.h", + "window_manager_access_policy.cc", + "window_manager_access_policy.h", + ] + + public_deps = [ + "//third_party/mojo_services/src/view_manager/public/cpp", + ] + + deps = [ + "//base", + "//cc/surfaces", + "//cc/surfaces:surface_id", + "//mojo/application", + "//mojo/common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//mojo/converters/surfaces", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/cpp/bindings:callback", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/input_events/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//third_party/mojo_services/src/surfaces/public/cpp", + "//third_party/mojo_services/src/surfaces/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp:common", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//ui/gfx", + "//ui/gfx/geometry", + ] +} + +source_set("test_support") { + testonly = true + + sources = [ + "test_change_tracker.cc", + "test_change_tracker.h", + ] + + deps = [ + "//base", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/cpp:common", + "//third_party/mojo_services/src/view_manager/public/interfaces", + ] +} + +test("view_manager_service_unittests") { + sources = [ + "animation_runner_unittest.cc", + "scheduled_animation_group_unittest.cc", + "test_server_view_delegate.cc", + "test_server_view_delegate.h", + "view_coordinate_conversions_unittest.cc", + "view_manager_service_unittest.cc", + ] + + deps = [ + ":test_support", + ":view_manager_lib", + "//base", + "//base/test:test_config", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//third_party/mojo/src/mojo/edk/test:run_all_unittests", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/cpp:args", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//testing/gtest", + "//ui/gfx", + "//ui/gfx:test_support", + "//ui/gfx/geometry", + ] + + if (!is_android) { # TODO(GYP) Enable on Android when osmesa links. + deps += [ "//third_party/mesa:osmesa" ] + } +} + +mojo_native_application("mojo_view_manager_client_apptests") { + testonly = true + + sources = [ + "view_manager_client_apptest.cc", + ] + + deps = [ + "//base", + "//base/test:test_config", + "//mojo/application", + "//mojo/application:test_support", + "//third_party/mojo_services/src/geometry/public/cpp:cpp", + "//third_party/mojo_services/src/view_manager/public/cpp", + ] +} + +mojo_native_application("view_manager_service_apptests") { + testonly = true + + sources = [ + "view_manager_service_apptest.cc", + ] + + deps = [ + ":test_support", + "//base", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} diff --git a/mojo/services/view_manager/DEPS b/mojo/services/view_manager/DEPS new file mode 100644 index 0000000..3612d0e --- /dev/null +++ b/mojo/services/view_manager/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+cc", + "+mojo/application", + "+mojo/converters", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/services/view_manager/access_policy.h b/mojo/services/view_manager/access_policy.h new file mode 100644 index 0000000..6f22f91 --- /dev/null +++ b/mojo/services/view_manager/access_policy.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_ +#define SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_ + +#include "mojo/services/view_manager/ids.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +namespace view_manager { + +class ServerView; + +// AccessPolicy is used by ViewManagerServiceImpl to determine what a connection +// is allowed to do. +class AccessPolicy { + public: + virtual ~AccessPolicy() {} + + // Unless otherwise mentioned all arguments have been validated. That is the + // |view| arguments are non-null unless otherwise stated (eg CanSetView() is + // allowed to take a NULL view). + virtual bool CanRemoveViewFromParent(const ServerView* view) const = 0; + virtual bool CanAddView(const ServerView* parent, + const ServerView* child) const = 0; + virtual bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const = 0; + virtual bool CanDeleteView(const ServerView* view) const = 0; + virtual bool CanGetViewTree(const ServerView* view) const = 0; + // Used when building a view tree (GetViewTree()) to decide if we should + // descend into |view|. + virtual bool CanDescendIntoViewForViewTree(const ServerView* view) const = 0; + virtual bool CanEmbed(const ServerView* view) const = 0; + virtual bool CanChangeViewVisibility(const ServerView* view) const = 0; + virtual bool CanSetViewSurfaceId(const ServerView* view) const = 0; + virtual bool CanSetViewBounds(const ServerView* view) const = 0; + virtual bool CanSetViewProperties(const ServerView* view) const = 0; + + // Returns whether the connection should notify on a hierarchy change. + // |new_parent| and |old_parent| are initially set to the new and old parents + // but may be altered so that the client only sees a certain set of views. + virtual bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const = 0; +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_ACCESS_POLICY_H_ diff --git a/mojo/services/view_manager/access_policy_delegate.h b/mojo/services/view_manager/access_policy_delegate.h new file mode 100644 index 0000000..1922176 --- /dev/null +++ b/mojo/services/view_manager/access_policy_delegate.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ +#define SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "mojo/services/view_manager/ids.h" + +namespace view_manager { + +class ServerView; + +// Delegate used by the AccessPolicy implementations to get state. +class AccessPolicyDelegate { + public: + // Returns true if |id| is the root of the connection. + virtual bool IsRootForAccessPolicy(const ViewId& id) const = 0; + + // Returns true if |view| has been exposed to the client. + virtual bool IsViewKnownForAccessPolicy(const ServerView* view) const = 0; + + // Returns true if Embed(view) has been invoked on |view|. + virtual bool IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const = 0; + + protected: + virtual ~AccessPolicyDelegate() {} +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ diff --git a/mojo/services/view_manager/animation_runner.cc b/mojo/services/view_manager/animation_runner.cc new file mode 100644 index 0000000..dc910a1 --- /dev/null +++ b/mojo/services/view_manager/animation_runner.cc @@ -0,0 +1,157 @@ +// 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 "mojo/services/view_manager/animation_runner.h" + +#include "base/memory/scoped_vector.h" +#include "mojo/services/view_manager/animation_runner_observer.h" +#include "mojo/services/view_manager/scheduled_animation_group.h" +#include "mojo/services/view_manager/server_view.h" + +namespace view_manager { +namespace { + +bool ConvertViewAndAnimationPairToScheduledAnimationGroup( + const std::vector<AnimationRunner::ViewAndAnimationPair>& views, + AnimationRunner::AnimationId id, + base::TimeTicks now, + ScopedVector<ScheduledAnimationGroup>* groups) { + for (const auto& view_animation_pair : views) { + DCHECK(view_animation_pair.second); + scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create( + view_animation_pair.first, now, id, *(view_animation_pair.second))); + if (!group.get()) + return false; + groups->push_back(group.release()); + } + return true; +} + +} // namespace + +AnimationRunner::AnimationRunner(base::TimeTicks now) + : next_id_(1), last_tick_time_(now) { +} + +AnimationRunner::~AnimationRunner() { +} + +void AnimationRunner::AddObserver(AnimationRunnerObserver* observer) { + observers_.AddObserver(observer); +} + +void AnimationRunner::RemoveObserver(AnimationRunnerObserver* observer) { + observers_.RemoveObserver(observer); +} + +AnimationRunner::AnimationId AnimationRunner::Schedule( + const std::vector<ViewAndAnimationPair>& views, + base::TimeTicks now) { + DCHECK_GE(now, last_tick_time_); + + const AnimationId animation_id = next_id_++; + ScopedVector<ScheduledAnimationGroup> groups; + if (!ConvertViewAndAnimationPairToScheduledAnimationGroup(views, animation_id, + now, &groups)) { + return 0; + } + + // Cancel any animations for the views. + for (auto* group : groups) { + ScheduledAnimationGroup* current_group = + view_to_animation_map_.get(group->view()); + if (current_group) + current_group->SetValuesToTargetValuesForPropertiesNotIn(*group); + + CancelAnimationForViewImpl(group->view(), CANCEL_SOURCE_SCHEDULE); + } + + for (auto* group : groups) { + group->ObtainStartValues(); + view_to_animation_map_.set(group->view(), make_scoped_ptr(group)); + DCHECK(!id_to_views_map_[animation_id].count(group->view())); + id_to_views_map_[animation_id].insert(group->view()); + } + // |view_to_animation_map_| owns the groups. + groups.weak_clear(); + + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationScheduled(animation_id)); + return animation_id; +} + +void AnimationRunner::CancelAnimation(AnimationId id) { + if (id_to_views_map_.count(id) == 0) + return; + + std::set<ServerView*> views(id_to_views_map_[id]); + for (ServerView* view : views) + CancelAnimationForView(view); +} + +void AnimationRunner::CancelAnimationForView(ServerView* view) { + CancelAnimationForViewImpl(view, CANCEL_SOURCE_CANCEL); +} + +void AnimationRunner::Tick(base::TimeTicks time) { + DCHECK(time >= last_tick_time_); + last_tick_time_ = time; + if (view_to_animation_map_.empty()) + return; + + // The animation ids of any views whose animation completes are added here. We + // notify after processing all views so that if an observer mutates us in some + // way we're aren't left in a weird state. + std::set<AnimationId> animations_completed; + for (ViewToAnimationMap::iterator i = view_to_animation_map_.begin(); + i != view_to_animation_map_.end();) { + if (i->second->Tick(time)) { + const AnimationId animation_id = i->second->id(); + ServerView* view = i->first; + ++i; + if (RemoveViewFromMaps(view)) + animations_completed.insert(animation_id); + } else { + ++i; + } + } + for (const AnimationId& id : animations_completed) { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, OnAnimationDone(id)); + } +} + +void AnimationRunner::CancelAnimationForViewImpl(ServerView* view, + CancelSource source) { + if (!view_to_animation_map_.contains(view)) + return; + + const AnimationId animation_id = view_to_animation_map_.get(view)->id(); + if (RemoveViewFromMaps(view)) { + // This was the last view in the group. + if (source == CANCEL_SOURCE_CANCEL) { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationCanceled(animation_id)); + } else { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationInterrupted(animation_id)); + } + } +} + +bool AnimationRunner::RemoveViewFromMaps(ServerView* view) { + DCHECK(view_to_animation_map_.contains(view)); + + const AnimationId animation_id = view_to_animation_map_.get(view)->id(); + view_to_animation_map_.erase(view); + + DCHECK(id_to_views_map_.count(animation_id)); + id_to_views_map_[animation_id].erase(view); + if (!id_to_views_map_[animation_id].empty()) + return false; + + id_to_views_map_.erase(animation_id); + return true; +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/animation_runner.h b/mojo/services/view_manager/animation_runner.h new file mode 100644 index 0000000..64bf494 --- /dev/null +++ b/mojo/services/view_manager/animation_runner.h @@ -0,0 +1,114 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_ +#define SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_ + +#include <algorithm> +#include <map> +#include <set> +#include <vector> + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/observer_list.h" +#include "base/time/time.h" + +namespace mojo { +class AnimationGroup; +} + +namespace view_manager { + +class AnimationRunnerObserver; +class ScheduledAnimationGroup; +class ServerView; + +// AnimationRunner is responsible for maintaing and running a set of animations. +// The animations are represented as a set of AnimationGroups. New animations +// are scheduled by way of Schedule(). A |view| may only have one animation +// running at a time. Schedule()ing a new animation for a view already animating +// implicitly cancels the current animation for the view. Animations progress +// by way of the Tick() function. +class AnimationRunner { + public: + using AnimationId = uint32_t; + using ViewAndAnimationPair = + std::pair<ServerView*, const mojo::AnimationGroup*>; + + explicit AnimationRunner(base::TimeTicks now); + ~AnimationRunner(); + + void AddObserver(AnimationRunnerObserver* observer); + void RemoveObserver(AnimationRunnerObserver* observer); + + // Schedules animations. If any of the groups are not valid no animations are + // scheuled and 0 is returned. If there is an existing animation in progress + // for any of the views it is canceled and any properties that were animating + // but are no longer animating are set to their target value. + AnimationId Schedule(const std::vector<ViewAndAnimationPair>& views, + base::TimeTicks now); + + // Cancels an animation scheduled by an id that was previously returned from + // Schedule(). + void CancelAnimation(AnimationId id); + + // Cancels the animation scheduled for |view|. Does nothing if there is no + // animation scheduled for |view|. This does not change |view|. That is, any + // in progress animations are stopped. + void CancelAnimationForView(ServerView* view); + + // Advance the animations updating values appropriately. + void Tick(base::TimeTicks time); + + // Returns true if there are animations currently scheduled. + bool HasAnimations() const { return !view_to_animation_map_.empty(); } + + // Returns true if the animation identified by |id| is valid and animating. + bool IsAnimating(AnimationId id) const { + return id_to_views_map_.count(id) > 0; + } + + // Returns the views that are currently animating for |id|. Returns an empty + // set if |id| does not identify a valid animation. + std::set<ServerView*> GetViewsAnimating(AnimationId id) { + return IsAnimating(id) ? id_to_views_map_.find(id)->second + : std::set<ServerView*>(); + } + + private: + enum CancelSource { + // Cancel is the result of scheduling another animation for the view. + CANCEL_SOURCE_SCHEDULE, + + // Cancel originates from an explicit call to cancel. + CANCEL_SOURCE_CANCEL, + }; + + using ViewToAnimationMap = + base::ScopedPtrHashMap<ServerView*, ScheduledAnimationGroup>; + using IdToViewsMap = std::map<AnimationId, std::set<ServerView*>>; + + void CancelAnimationForViewImpl(ServerView* view, CancelSource source); + + // Removes |view| from both |view_to_animation_map_| and |id_to_views_map_|. + // Returns true if there are no more views animating with the animation id + // the view is associated with. + bool RemoveViewFromMaps(ServerView* view); + + AnimationId next_id_; + + base::TimeTicks last_tick_time_; + + ObserverList<AnimationRunnerObserver> observers_; + + ViewToAnimationMap view_to_animation_map_; + + IdToViewsMap id_to_views_map_; + + DISALLOW_COPY_AND_ASSIGN(AnimationRunner); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_H_ diff --git a/mojo/services/view_manager/animation_runner_observer.h b/mojo/services/view_manager/animation_runner_observer.h new file mode 100644 index 0000000..9b79983 --- /dev/null +++ b/mojo/services/view_manager/animation_runner_observer.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ +#define SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ + +namespace view_manager { + +class AnimationRunnerObserver { + public: + virtual void OnAnimationScheduled(uint32_t id) = 0; + virtual void OnAnimationDone(uint32_t id) = 0; + virtual void OnAnimationInterrupted(uint32_t id) = 0; + virtual void OnAnimationCanceled(uint32_t id) = 0; + + protected: + virtual ~AnimationRunnerObserver() {} +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ diff --git a/mojo/services/view_manager/animation_runner_unittest.cc b/mojo/services/view_manager/animation_runner_unittest.cc new file mode 100644 index 0000000..c43f0b1 --- /dev/null +++ b/mojo/services/view_manager/animation_runner_unittest.cc @@ -0,0 +1,641 @@ +// 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 "mojo/services/view_manager/animation_runner.h" + +#include "base/strings/stringprintf.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "mojo/services/view_manager/animation_runner_observer.h" +#include "mojo/services/view_manager/scheduled_animation_group.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +using base::TimeDelta; +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::ANIMATION_TWEEN_TYPE_LINEAR; +using mojo::AnimationElement; +using mojo::AnimationGroup; +using mojo::AnimationProperty; +using mojo::AnimationSequence; +using mojo::AnimationTweenType; +using mojo::AnimationValue; +using mojo::AnimationValuePtr; +using mojo::Transform; + +namespace view_manager { +namespace { + +class TestAnimationRunnerObserver : public AnimationRunnerObserver { + public: + TestAnimationRunnerObserver() {} + ~TestAnimationRunnerObserver() override {} + + std::vector<std::string>* changes() { return &changes_; } + std::vector<uint32_t>* change_ids() { return &change_ids_; } + + void clear_changes() { + changes_.clear(); + change_ids_.clear(); + } + + // AnimationRunnerDelgate: + void OnAnimationScheduled(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("scheduled"); + } + void OnAnimationDone(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("done"); + } + void OnAnimationInterrupted(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("interrupted"); + } + void OnAnimationCanceled(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("canceled"); + } + + private: + std::vector<uint32_t> change_ids_; + std::vector<std::string> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestAnimationRunnerObserver); +}; + +// Creates an AnimationValuePtr from the specified float value. +AnimationValuePtr FloatAnimationValue(float float_value) { + AnimationValuePtr value(AnimationValue::New()); + value->float_value = float_value; + return value.Pass(); +} + +// Creates an AnimationValuePtr from the specified transform. +AnimationValuePtr TransformAnimationValue(const gfx::Transform& transform) { + AnimationValuePtr value(AnimationValue::New()); + value->transform = Transform::From(transform); + return value.Pass(); +} + +// Adds an AnimationElement to |group|s last sequence with the specified value. +void AddElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value, + AnimationProperty property, + AnimationTweenType tween_type) { + AnimationSequence& sequence = + *(group->sequences[group->sequences.size() - 1]); + sequence.elements.push_back(AnimationElement::New()); + AnimationElement& element = + *(sequence.elements[sequence.elements.size() - 1]); + element.property = property; + element.duration = time.InMicroseconds(); + element.tween_type = tween_type; + element.start_value = start_value.Pass(); + element.target_value = target_value.Pass(); +} + +void AddOpacityElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, start_value.Pass(), target_value.Pass(), + ANIMATION_PROPERTY_OPACITY, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void AddTransformElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, start_value.Pass(), target_value.Pass(), + ANIMATION_PROPERTY_TRANSFORM, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void AddPauseElement(AnimationGroup* group, TimeDelta time) { + AddElement(group, time, AnimationValuePtr(), AnimationValuePtr(), + ANIMATION_PROPERTY_NONE, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void InitGroupForView(AnimationGroup* group, + const ViewId& id, + int cycle_count) { + group->view_id = ViewIdToTransportId(id); + group->sequences.push_back(AnimationSequence::New()); + group->sequences[group->sequences.size() - 1]->cycle_count = cycle_count; +} + +} // namespace + +class AnimationRunnerTest : public testing::Test { + public: + AnimationRunnerTest() + : initial_time_(base::TimeTicks::Now()), runner_(initial_time_) { + runner_.AddObserver(&runner_observer_); + } + ~AnimationRunnerTest() override { runner_.RemoveObserver(&runner_observer_); } + + protected: + // Convenience to schedule an animation for a single view/group pair. + AnimationRunner::AnimationId ScheduleForSingleView( + ServerView* view, + const AnimationGroup* group, + base::TimeTicks now) { + std::vector<AnimationRunner::ViewAndAnimationPair> pairs; + pairs.push_back(std::make_pair(view, group)); + return runner_.Schedule(pairs, now); + } + + // If |id| is valid and there is only one view schedule against the animation + // it is returned; otherwise returns null. + ServerView* GetSingleViewAnimating(AnimationRunner::AnimationId id) { + std::set<ServerView*> views(runner_.GetViewsAnimating(id)); + return views.size() == 1 ? *views.begin() : nullptr; + } + + const base::TimeTicks initial_time_; + TestAnimationRunnerObserver runner_observer_; + AnimationRunner runner_; + + private: + DISALLOW_COPY_AND_ASSIGN(AnimationRunnerTest); +}; + +// Opacity from 1 to .5 over 1000. +TEST_F(AnimationRunnerTest, SingleProperty) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + runner_observer_.clear_changes(); + + EXPECT_TRUE(runner_.HasAnimations()); + + // Opacity should still be 1 (the initial value). + EXPECT_EQ(1.f, view.opacity()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Run well past the end. Value should progress to end and delegate should + // be notified. + runner_.Tick(initial_time_ + TimeDelta::FromSeconds(10)); + EXPECT_EQ(.5f, view.opacity()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + + EXPECT_FALSE(runner_.HasAnimations()); +} + +// Opacity from 1 to .5, followed by transform from identity to 2x,3x. +TEST_F(AnimationRunnerTest, TwoPropertiesInSequence) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5f)); + + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(2000), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(1.f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Animate half way from through opacity animation. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Finish first element (opacity). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Half way through second (transform). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Opacity from .5 to 1 over 1000, transform to 2x,4x over 500. +TEST_F(AnimationRunnerTest, TwoPropertiesInParallel) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 1)); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + FloatAnimationValue(.5f), FloatAnimationValue(1)); + + group.sequences.push_back(AnimationSequence::New()); + group.sequences[1]->cycle_count = 1; + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(1.f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Animate to 250, which is 1/4 way through opacity and half way through + // transform. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250)); + + EXPECT_EQ(.625f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + // Animate to 500, which is 1/2 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + // Animate to 750, which is 3/4 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.875f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(1.f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Opacity from .5 to 1 over 1000, pause for 500, 1 to .5 over 500, with a cycle +// count of 3. +TEST_F(AnimationRunnerTest, Cycles) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + view.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 3); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + AddPauseElement(&group, TimeDelta::FromMicroseconds(500)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(.5f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1250)); + EXPECT_EQ(1.f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2500)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3250)); + EXPECT_EQ(1.f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3750)); + EXPECT_EQ(.75f, view.opacity()); + + // Animate to the end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(6500)); + EXPECT_EQ(.5f, view.opacity()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); +} + +// Verifies scheduling the same view twice sends an interrupt. +TEST_F(AnimationRunnerTest, ScheduleTwice) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Schedule again. We should get an interrupt, but opacity shouldn't change. + const uint32_t animation2_id = ScheduleForSingleView( + &view, &group, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Id should have changed. + EXPECT_NE(animation_id, animation2_id); + + EXPECT_FALSE(runner_.IsAnimating(animation_id)); + EXPECT_EQ(&view, GetSingleViewAnimating(animation2_id)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(2u, runner_observer_.changes()->size()); + EXPECT_EQ("interrupted", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(1)); + EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(1)); + runner_observer_.clear_changes(); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.625f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(0)); +} + +// Verifies Remove() works. +TEST_F(AnimationRunnerTest, CancelAnimationForView) { + // Create an animation and advance it part way. + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + EXPECT_EQ(&view, GetSingleViewAnimating(animation_id)); + + EXPECT_TRUE(runner_.HasAnimations()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Cancel the animation. + runner_.CancelAnimationForView(&view); + + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_EQ(nullptr, GetSingleViewAnimating(animation_id)); + + EXPECT_EQ(.75f, view.opacity()); + + EXPECT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("canceled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Verifies a tick with a very large delta and a sequence that repeats forever +// doesn't take a long time. +TEST_F(AnimationRunnerTest, InfiniteRepeatWithHugeGap) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + view.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 0); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(1)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000000000750)); + + EXPECT_EQ(.75f, view.opacity()); + + ASSERT_EQ(0u, runner_observer_.changes()->size()); +} + +// Verifies a second schedule sets any properties that are no longer animating +// to their final value. +TEST_F(AnimationRunnerTest, RescheduleSetsPropertiesToFinalValue) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + ScheduleForSingleView(&view, &group, initial_time_); + + // Schedule() again, this time without animating opacity. + group.sequences[0]->elements[0]->property = ANIMATION_PROPERTY_NONE; + ScheduleForSingleView(&view, &group, initial_time_); + + // Opacity should go to final value. + EXPECT_EQ(.5f, view.opacity()); + // Transform shouldn't have changed since newly scheduled animation also has + // transform in it. + EXPECT_TRUE(view.transform().IsIdentity()); +} + +// Opacity from 1 to .5 over 1000 of v1 and v2 transform to 2x,4x over 500. +TEST_F(AnimationRunnerTest, TwoViews) { + TestServerViewDelegate view_delegate; + ServerView view1(&view_delegate, ViewId()); + ServerView view2(&view_delegate, ViewId(1, 2)); + + AnimationGroup group1; + InitGroupForView(&group1, view1.id(), 1); + AddOpacityElement(&group1, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + AnimationGroup group2; + InitGroupForView(&group2, view2.id(), 1); + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group2, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + std::vector<AnimationRunner::ViewAndAnimationPair> pairs; + pairs.push_back(std::make_pair(&view1, &group1)); + pairs.push_back(std::make_pair(&view2, &group2)); + + const uint32_t animation_id = runner_.Schedule(pairs, initial_time_); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + runner_observer_.clear_changes(); + + EXPECT_TRUE(runner_.HasAnimations()); + EXPECT_TRUE(runner_.IsAnimating(animation_id)); + + // Properties should be at the initial value. + EXPECT_EQ(1.f, view1.opacity()); + EXPECT_TRUE(view2.transform().IsIdentity()); + + // Animate 250ms in, which is quarter way for opacity and half way for + // transform. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250)); + EXPECT_EQ(.875f, view1.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view2.transform()); + std::set<ServerView*> views_animating( + runner_.GetViewsAnimating(animation_id)); + EXPECT_EQ(2u, views_animating.size()); + EXPECT_EQ(1u, views_animating.count(&view1)); + EXPECT_EQ(1u, views_animating.count(&view2)); + + // Animate 750ms in, view1 should be done 3/4 done, and view2 done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.625, view1.opacity()); + EXPECT_EQ(done_transform, view2.transform()); + views_animating = runner_.GetViewsAnimating(animation_id); + EXPECT_EQ(1u, views_animating.size()); + EXPECT_EQ(1u, views_animating.count(&view1)); + EXPECT_TRUE(runner_.HasAnimations()); + EXPECT_TRUE(runner_.IsAnimating(animation_id)); + + // Animate to end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.5, view1.opacity()); + EXPECT_EQ(done_transform, view2.transform()); + views_animating = runner_.GetViewsAnimating(animation_id); + EXPECT_TRUE(views_animating.empty()); + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_FALSE(runner_.IsAnimating(animation_id)); +} + +TEST_F(AnimationRunnerTest, Reschedule) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + // Animation from 1-0 over 1ms and in parallel transform to 2x,4x over 1ms. + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(0)); + group.sequences.push_back(AnimationSequence::New()); + group.sequences[1]->cycle_count = 1; + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + const uint32_t animation_id1 = + ScheduleForSingleView(&view, &group, initial_time_); + + // Animate half way in. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.5f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + runner_observer_.clear_changes(); + + // Schedule the same view animating opacity to 1. + AnimationGroup group2; + InitGroupForView(&group2, view.id(), 1); + AddOpacityElement(&group2, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + const uint32_t animation_id2 = ScheduleForSingleView( + &view, &group2, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Opacity should remain at .5, but transform should go to end state. + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(2u, runner_observer_.changes()->size()); + EXPECT_EQ("interrupted", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id1, runner_observer_.change_ids()->at(0)); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(1)); + EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(1)); + runner_observer_.clear_changes(); + + // Animate half way through new sequence. Opacity should be the only thing + // changing. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + ASSERT_EQ(0u, runner_observer_.changes()->size()); + + // Animate to end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(0)); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/client_connection.cc b/mojo/services/view_manager/client_connection.cc new file mode 100644 index 0000000..41c2c59 --- /dev/null +++ b/mojo/services/view_manager/client_connection.cc @@ -0,0 +1,39 @@ +// 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 "mojo/services/view_manager/client_connection.h" + +#include "mojo/services/view_manager/connection_manager.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" + +namespace view_manager { + +ClientConnection::ClientConnection(scoped_ptr<ViewManagerServiceImpl> service, + mojo::ViewManagerClient* client) + : service_(service.Pass()), client_(client) { +} + +ClientConnection::~ClientConnection() { +} + +DefaultClientConnection::DefaultClientConnection( + scoped_ptr<ViewManagerServiceImpl> service_impl, + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ViewManagerClientPtr client) + : ClientConnection(service_impl.Pass(), client.get()), + connection_manager_(connection_manager), + binding_(service(), service_request.Pass()), + client_(client.Pass()) { + binding_.set_error_handler(this); +} + +DefaultClientConnection::~DefaultClientConnection() { +} + +void DefaultClientConnection::OnConnectionError() { + connection_manager_->OnConnectionError(this); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/client_connection.h b/mojo/services/view_manager/client_connection.h new file mode 100644 index 0000000..83fa864 --- /dev/null +++ b/mojo/services/view_manager/client_connection.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_ +#define SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace view_manager { + +class ConnectionManager; +class ViewManagerServiceImpl; + +// ClientConnection encapsulates the state needed for a single client connected +// to the view manager. +class ClientConnection { + public: + ClientConnection(scoped_ptr<ViewManagerServiceImpl> service, + mojo::ViewManagerClient* client); + virtual ~ClientConnection(); + + ViewManagerServiceImpl* service() { return service_.get(); } + const ViewManagerServiceImpl* service() const { return service_.get(); } + + mojo::ViewManagerClient* client() { return client_; } + + private: + scoped_ptr<ViewManagerServiceImpl> service_; + mojo::ViewManagerClient* client_; + + DISALLOW_COPY_AND_ASSIGN(ClientConnection); +}; + +// Bindings implementation of ClientConnection. +class DefaultClientConnection : public ClientConnection, + public mojo::ErrorHandler { + public: + DefaultClientConnection( + scoped_ptr<ViewManagerServiceImpl> service_impl, + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ViewManagerClientPtr client); + ~DefaultClientConnection() override; + + private: + // ErrorHandler: + void OnConnectionError() override; + + ConnectionManager* connection_manager_; + mojo::Binding<mojo::ViewManagerService> binding_; + mojo::ViewManagerClientPtr client_; + + DISALLOW_COPY_AND_ASSIGN(DefaultClientConnection); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_CLIENT_CONNECTION_H_ diff --git a/mojo/services/view_manager/connection_manager.cc b/mojo/services/view_manager/connection_manager.cc new file mode 100644 index 0000000..768d0ff --- /dev/null +++ b/mojo/services/view_manager/connection_manager.cc @@ -0,0 +1,494 @@ +// 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 "mojo/services/view_manager/connection_manager.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/services/view_manager/client_connection.h" +#include "mojo/services/view_manager/connection_manager_delegate.h" +#include "mojo/services/view_manager/display_manager.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/view_coordinate_conversions.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" + +using mojo::ConnectionSpecificId; + +namespace view_manager { +namespace { + +// Creates a copy of |view|. The copied view has |delegate| as its delegate. +// This does not recurse. +ServerView* CloneView(const ServerView* view, ServerViewDelegate* delegate) { + ServerView* clone = new ServerView(delegate, ClonedViewId()); + clone->SetBounds(view->bounds()); + clone->SetSurfaceId(view->surface_id()); + clone->SetOpacity(view->opacity()); + return clone; +} + +// Creates copies of all the visible children of |parent|. Newly cloned views +// are added to |cloned_parent| and have |delegate| as their delegate. The +// stacking order of the cloned views is preseved. +void CloneViewTree(const ServerView* parent, + ServerView* cloned_parent, + ServerViewDelegate* delegate) { + DCHECK(parent->visible()); + for (const ServerView* to_clone : parent->GetChildren()) { + if (to_clone->visible()) { + ServerView* cloned = CloneView(to_clone, delegate); + cloned_parent->Add(cloned); + CloneViewTree(to_clone, cloned, delegate); + } + } +} + +// Recurses through all the children of |view| moving any cloned views to +// |new_parent| stacked above |stack_above|. |stack_above| is updated as views +// are moved. +void ReparentClonedViews(ServerView* new_parent, + ServerView** stack_above, + ServerView* view) { + if (view->id() == ClonedViewId()) { + const gfx::Rect new_bounds(ConvertRectBetweenViews( + view, new_parent, gfx::Rect(view->bounds().size()))); + new_parent->Add(view); + new_parent->Reorder(view, *stack_above, mojo::ORDER_DIRECTION_ABOVE); + view->SetBounds(new_bounds); + *stack_above = view; + return; + } + + for (ServerView* child : view->GetChildren()) + ReparentClonedViews(new_parent, stack_above, child); +} + +// Deletes |view| and all its descendants. +void DeleteViewTree(ServerView* view) { + for (ServerView* child : view->GetChildren()) + DeleteViewTree(child); + + delete view; +} + +// TODO(sky): nuke, proof of concept. +bool DecrementAnimatingViewsOpacity(ServerView* view) { + if (view->id() == ClonedViewId()) { + const float new_opacity = view->opacity() - .05f; + if (new_opacity <= 0) + DeleteViewTree(view); + else + view->SetOpacity(new_opacity); + return true; + } + bool ret_value = false; + for (ServerView* child : view->GetChildren()) { + if (DecrementAnimatingViewsOpacity(child)) + ret_value = true; + } + return ret_value; +} + +} // namespace + +ConnectionManager::ScopedChange::ScopedChange( + ViewManagerServiceImpl* connection, + ConnectionManager* connection_manager, + bool is_delete_view) + : connection_manager_(connection_manager), + connection_id_(connection->id()), + is_delete_view_(is_delete_view) { + connection_manager_->PrepareForChange(this); +} + +ConnectionManager::ScopedChange::~ScopedChange() { + connection_manager_->FinishChange(); +} + +ConnectionManager::ConnectionManager(ConnectionManagerDelegate* delegate, + scoped_ptr<DisplayManager> display_manager, + mojo::WindowManagerInternal* wm_internal) + : delegate_(delegate), + window_manager_client_connection_(nullptr), + next_connection_id_(1), + display_manager_(display_manager.Pass()), + root_(new ServerView(this, RootViewId())), + wm_internal_(wm_internal), + current_change_(nullptr), + in_destructor_(false), + animation_runner_(base::TimeTicks::Now()) { + root_->SetBounds(gfx::Rect(800, 600)); + root_->SetVisible(true); + display_manager_->Init(this); +} + +ConnectionManager::~ConnectionManager() { + in_destructor_ = true; + + STLDeleteValues(&connection_map_); + // All the connections should have been destroyed. + DCHECK(connection_map_.empty()); + root_.reset(); +} + +ConnectionSpecificId ConnectionManager::GetAndAdvanceNextConnectionId() { + const ConnectionSpecificId id = next_connection_id_++; + DCHECK_LT(id, next_connection_id_); + return id; +} + +void ConnectionManager::OnConnectionError(ClientConnection* connection) { + if (connection == window_manager_client_connection_) { + window_manager_client_connection_ = nullptr; + delegate_->OnLostConnectionToWindowManager(); + // Assume we've been destroyed. + return; + } + + scoped_ptr<ClientConnection> connection_owner(connection); + + connection_map_.erase(connection->service()->id()); + + // Notify remaining connections so that they can cleanup. + for (auto& pair : connection_map_) { + pair.second->service()->OnWillDestroyViewManagerServiceImpl( + connection->service()); + } +} + +void ConnectionManager::EmbedAtView( + ConnectionSpecificId creator_id, + const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + std::string creator_url; + ConnectionMap::const_iterator it = connection_map_.find(creator_id); + if (it != connection_map_.end()) + creator_url = it->second->service()->url(); + + mojo::ViewManagerServicePtr service_ptr; + ClientConnection* client_connection = + delegate_->CreateClientConnectionForEmbedAtView( + this, GetProxy(&service_ptr), creator_id, creator_url, url, view_id); + AddConnection(client_connection); + client_connection->service()->Init(client_connection->client(), + service_ptr.Pass(), services.Pass(), + exposed_services.Pass()); + OnConnectionMessagedClient(client_connection->service()->id()); +} + +void ConnectionManager::EmbedAtView(mojo::ConnectionSpecificId creator_id, + const ViewId& view_id, + mojo::ViewManagerClientPtr client) { + std::string creator_url; + ConnectionMap::const_iterator it = connection_map_.find(creator_id); + if (it != connection_map_.end()) + creator_url = it->second->service()->url(); + + mojo::ViewManagerServicePtr service_ptr; + ClientConnection* client_connection = + delegate_->CreateClientConnectionForEmbedAtView( + this, GetProxy(&service_ptr), creator_id, creator_url, view_id, + client.Pass()); + AddConnection(client_connection); + client_connection->service()->Init(client_connection->client(), + service_ptr.Pass(), nullptr, nullptr); + OnConnectionMessagedClient(client_connection->service()->id()); +} + +ViewManagerServiceImpl* ConnectionManager::GetConnection( + ConnectionSpecificId connection_id) { + ConnectionMap::iterator i = connection_map_.find(connection_id); + return i == connection_map_.end() ? nullptr : i->second->service(); +} + +ServerView* ConnectionManager::GetView(const ViewId& id) { + if (id == root_->id()) + return root_.get(); + ViewManagerServiceImpl* service = GetConnection(id.connection_id); + return service ? service->GetView(id) : nullptr; +} + +void ConnectionManager::OnConnectionMessagedClient(ConnectionSpecificId id) { + if (current_change_) + current_change_->MarkConnectionAsMessaged(id); +} + +bool ConnectionManager::DidConnectionMessageClient( + ConnectionSpecificId id) const { + return current_change_ && current_change_->DidMessageConnection(id); +} + +const ViewManagerServiceImpl* ConnectionManager::GetConnectionWithRoot( + const ViewId& id) const { + for (auto& pair : connection_map_) { + if (pair.second->service()->IsRoot(id)) + return pair.second->service(); + } + return nullptr; +} + +void ConnectionManager::SetWindowManagerClientConnection( + scoped_ptr<ClientConnection> connection) { + CHECK(!window_manager_client_connection_); + window_manager_client_connection_ = connection.release(); + AddConnection(window_manager_client_connection_); + window_manager_client_connection_->service()->Init( + window_manager_client_connection_->client(), nullptr, nullptr, nullptr); +} + +mojo::ViewManagerClient* +ConnectionManager::GetWindowManagerViewManagerClient() { + CHECK(window_manager_client_connection_); + return window_manager_client_connection_->client(); +} + +bool ConnectionManager::CloneAndAnimate(const ViewId& view_id) { + ServerView* view = GetView(view_id); + if (!view || !view->IsDrawn(root_.get()) || view == root_.get()) + return false; + if (!animation_timer_.IsRunning()) { + animation_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(100), + this, &ConnectionManager::DoAnimation); + } + ServerView* clone = CloneView(view, this); + CloneViewTree(view, clone, this); + view->parent()->Add(clone); + view->parent()->Reorder(clone, view, mojo::ORDER_DIRECTION_ABOVE); + return true; +} + +void ConnectionManager::ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewBoundsChanged( + view, old_bounds, new_bounds, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewportMetricsChanged( + const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewportMetricsChanged( + old_metrics, new_metrics, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessWillChangeViewHierarchy( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessWillChangeViewHierarchy( + view, new_parent, old_parent, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewHierarchyChanged( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewHierarchyChanged( + view, new_parent, old_parent, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewReorder( + const ServerView* view, + const ServerView* relative_view, + const mojo::OrderDirection direction) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewReorder(view, relative_view, direction, + IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewDeleted(const ViewId& view) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewDeleted(view, + IsChangeSource(pair.first)); + } +} + +void ConnectionManager::PrepareForChange(ScopedChange* change) { + // Should only ever have one change in flight. + CHECK(!current_change_); + current_change_ = change; +} + +void ConnectionManager::FinishChange() { + // PrepareForChange/FinishChange should be balanced. + CHECK(current_change_); + current_change_ = NULL; +} + +void ConnectionManager::DoAnimation() { + if (!DecrementAnimatingViewsOpacity(root())) + animation_timer_.Stop(); +} + +void ConnectionManager::AddConnection(ClientConnection* connection) { + DCHECK_EQ(0u, connection_map_.count(connection->service()->id())); + connection_map_[connection->service()->id()] = connection; +} + +void ConnectionManager::OnWillDestroyView(ServerView* view) { + if (!in_destructor_ && root_->Contains(view) && view != root_.get() && + view->id() != ClonedViewId()) { + // We're about to destroy a view. Any cloned views need to be reparented + // else the animation would no longer be visible. By moving to a visible + // view, view->parent(), we ensure the animation is still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::OnViewDestroyed(const ServerView* view) { + if (!in_destructor_) + ProcessViewDeleted(view->id()); +} + +void ConnectionManager::OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { + if (view->id() == ClonedViewId() || in_destructor_) + return; + + if (root_->Contains(view) && view != root_.get()) { + // We're about to reparent a view. Any cloned views need to be reparented + // else the animation may be effected in unusual ways. For example, the view + // could move to a new location such that the animation is entirely clipped. + // By moving to view->parent() we ensure the animation is still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + ProcessWillChangeViewHierarchy(view, new_parent, old_parent); + + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::OnViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { + if (in_destructor_) + return; + + ProcessViewHierarchyChanged(view, new_parent, old_parent); + + // TODO(beng): optimize. + if (old_parent) { + display_manager_->SchedulePaint(old_parent, + gfx::Rect(old_parent->bounds().size())); + } + if (new_parent) { + display_manager_->SchedulePaint(new_parent, + gfx::Rect(new_parent->bounds().size())); + } +} + +void ConnectionManager::OnViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + if (in_destructor_) + return; + + ProcessViewBoundsChanged(view, old_bounds, new_bounds); + if (!view->parent()) + return; + + // TODO(sky): optimize this. + display_manager_->SchedulePaint(view->parent(), old_bounds); + display_manager_->SchedulePaint(view->parent(), new_bounds); +} + +void ConnectionManager::OnViewSurfaceIdChanged(const ServerView* view) { + if (!in_destructor_) + display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size())); +} + +void ConnectionManager::OnViewReordered(const ServerView* view, + const ServerView* relative, + mojo::OrderDirection direction) { + if (!in_destructor_) + display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size())); +} + +void ConnectionManager::OnWillChangeViewVisibility(ServerView* view) { + if (in_destructor_) + return; + + // Need to repaint if the view was drawn (which means it'll in the process of + // hiding) or the view is transitioning to drawn. + if (view->IsDrawn(root_.get()) || (!view->visible() && view->parent() && + view->parent()->IsDrawn(root_.get()))) { + display_manager_->SchedulePaint(view->parent(), view->bounds()); + } + + if (view != root_.get() && view->id() != ClonedViewId() && + root_->Contains(view) && view->IsDrawn(root_.get())) { + // We're about to hide |view|, this would implicitly make any cloned views + // hide to. Reparent so that animations are still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + for (auto& pair : connection_map_) { + pair.second->service()->ProcessWillChangeViewVisibility( + view, IsChangeSource(pair.first)); + } + + const bool is_parent_drawn = + view->parent() && view->parent()->IsDrawn(root_.get()); + if (!is_parent_drawn || !view->visible()) + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::OnViewSharedPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewPropertyChanged( + view, name, new_data, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::OnScheduleViewPaint(const ServerView* view) { + if (!in_destructor_) + display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size())); +} + +void ConnectionManager::DispatchInputEventToView(mojo::Id transport_view_id, + mojo::EventPtr event) { + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + + ViewManagerServiceImpl* connection = GetConnectionWithRoot(view_id); + if (!connection) + connection = GetConnection(view_id.connection_id); + if (connection) { + connection->client()->OnViewInputEvent( + transport_view_id, event.Pass(), base::Bind(&base::DoNothing)); + } +} + +void ConnectionManager::SetViewportSize(mojo::SizePtr size) { + gfx::Size new_size = size.To<gfx::Size>(); + display_manager_->SetViewportSize(new_size); +} + +void ConnectionManager::CloneAndAnimate(mojo::Id transport_view_id) { + CloneAndAnimate(ViewIdFromTransportId(transport_view_id)); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/connection_manager.h b/mojo/services/view_manager/connection_manager.h new file mode 100644 index 0000000..73f74a0 --- /dev/null +++ b/mojo/services/view_manager/connection_manager.h @@ -0,0 +1,241 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_ +#define SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer/timer.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/services/view_manager/animation_runner.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/server_view_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +namespace view_manager { + +class ClientConnection; +class ConnectionManagerDelegate; +class DisplayManager; +class ServerView; +class ViewManagerServiceImpl; + +// ConnectionManager manages the set of connections to the ViewManager (all the +// ViewManagerServiceImpls) as well as providing the root of the hierarchy. +class ConnectionManager : public ServerViewDelegate, + public mojo::WindowManagerInternalClient { + public: + // Create when a ViewManagerServiceImpl is about to make a change. Ensures + // clients are notified correctly. + class ScopedChange { + public: + ScopedChange(ViewManagerServiceImpl* connection, + ConnectionManager* connection_manager, + bool is_delete_view); + ~ScopedChange(); + + mojo::ConnectionSpecificId connection_id() const { return connection_id_; } + bool is_delete_view() const { return is_delete_view_; } + + // Marks the connection with the specified id as having seen a message. + void MarkConnectionAsMessaged(mojo::ConnectionSpecificId connection_id) { + message_ids_.insert(connection_id); + } + + // Returns true if MarkConnectionAsMessaged(connection_id) was invoked. + bool DidMessageConnection(mojo::ConnectionSpecificId connection_id) const { + return message_ids_.count(connection_id) > 0; + } + + private: + ConnectionManager* connection_manager_; + const mojo::ConnectionSpecificId connection_id_; + const bool is_delete_view_; + + // See description of MarkConnectionAsMessaged/DidMessageConnection. + std::set<mojo::ConnectionSpecificId> message_ids_; + + DISALLOW_COPY_AND_ASSIGN(ScopedChange); + }; + + ConnectionManager(ConnectionManagerDelegate* delegate, + scoped_ptr<DisplayManager> display_manager, + mojo::WindowManagerInternal* wm_internal); + ~ConnectionManager() override; + + // Returns the id for the next ViewManagerServiceImpl. + mojo::ConnectionSpecificId GetAndAdvanceNextConnectionId(); + + // Invoked when a ViewManagerServiceImpl's connection encounters an error. + void OnConnectionError(ClientConnection* connection); + + // See description of ViewManagerService::Embed() for details. This assumes + // |transport_view_id| is valid. + void EmbedAtView(mojo::ConnectionSpecificId creator_id, + const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + void EmbedAtView(mojo::ConnectionSpecificId creator_id, + const ViewId& view_id, + mojo::ViewManagerClientPtr client); + + // Returns the connection by id. + ViewManagerServiceImpl* GetConnection( + mojo::ConnectionSpecificId connection_id); + + // Returns the View identified by |id|. + ServerView* GetView(const ViewId& id); + + ServerView* root() { return root_.get(); } + DisplayManager* display_manager() { return display_manager_.get(); } + + bool IsProcessingChange() const { return current_change_ != NULL; } + + bool is_processing_delete_view() const { + return current_change_ && current_change_->is_delete_view(); + } + + // Invoked when a connection messages a client about the change. This is used + // to avoid sending ServerChangeIdAdvanced() unnecessarily. + void OnConnectionMessagedClient(mojo::ConnectionSpecificId id); + + // Returns true if OnConnectionMessagedClient() was invoked for id. + bool DidConnectionMessageClient(mojo::ConnectionSpecificId id) const; + + // Returns the ViewManagerServiceImpl that has |id| as a root. + ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) { + return const_cast<ViewManagerServiceImpl*>( + const_cast<const ConnectionManager*>(this)->GetConnectionWithRoot(id)); + } + const ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) const; + + mojo::WindowManagerInternal* wm_internal() { return wm_internal_; } + + void SetWindowManagerClientConnection( + scoped_ptr<ClientConnection> connection); + bool has_window_manager_client_connection() const { + return window_manager_client_connection_ != nullptr; + } + + mojo::ViewManagerClient* GetWindowManagerViewManagerClient(); + + // WindowManagerInternalClient implementation helper; see mojom for details. + bool CloneAndAnimate(const ViewId& view_id); + + // These functions trivially delegate to all ViewManagerServiceImpls, which in + // term notify their clients. + void ProcessViewDestroyed(ServerView* view); + void ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds); + void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics); + void ProcessWillChangeViewHierarchy(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent); + void ProcessViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent); + void ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + const mojo::OrderDirection direction); + void ProcessViewDeleted(const ViewId& view); + + private: + typedef std::map<mojo::ConnectionSpecificId, ClientConnection*> ConnectionMap; + + // Invoked when a connection is about to make a change. Subsequently followed + // by FinishChange() once the change is done. + // + // Changes should never nest, meaning each PrepareForChange() must be + // balanced with a call to FinishChange() with no PrepareForChange() + // in between. + void PrepareForChange(ScopedChange* change); + + // Balances a call to PrepareForChange(). + void FinishChange(); + + // Returns true if the specified connection originated the current change. + bool IsChangeSource(mojo::ConnectionSpecificId connection_id) const { + return current_change_ && current_change_->connection_id() == connection_id; + } + + // Adds |connection| to internal maps. + void AddConnection(ClientConnection* connection); + + // Callback from animation timer. + // TODO(sky): make this real (move to a different class). + void DoAnimation(); + + // Overridden from ServerViewDelegate: + void OnWillDestroyView(ServerView* view) override; + void OnViewDestroyed(const ServerView* view) override; + void OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void OnViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) override; + void OnViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnViewSurfaceIdChanged(const ServerView* view) override; + void OnViewReordered(const ServerView* view, + const ServerView* relative, + mojo::OrderDirection direction) override; + void OnWillChangeViewVisibility(ServerView* view) override; + void OnViewSharedPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) override; + void OnScheduleViewPaint(const ServerView* view) override; + + // WindowManagerInternalClient: + void DispatchInputEventToView(mojo::Id transport_view_id, + mojo::EventPtr event) override; + void SetViewportSize(mojo::SizePtr size) override; + void CloneAndAnimate(mojo::Id transport_view_id) override; + + ConnectionManagerDelegate* delegate_; + + // The ClientConnection containing the ViewManagerService implementation + // provided to the initial connection (the WindowManager). + // NOTE: |window_manager_client_connection_| is also in |connection_map_|. + ClientConnection* window_manager_client_connection_; + + // ID to use for next ViewManagerServiceImpl. + mojo::ConnectionSpecificId next_connection_id_; + + // Set of ViewManagerServiceImpls. + ConnectionMap connection_map_; + + scoped_ptr<DisplayManager> display_manager_; + + scoped_ptr<ServerView> root_; + + mojo::WindowManagerInternal* wm_internal_; + + // If non-null we're processing a change. The ScopedChange is not owned by us + // (it's created on the stack by ViewManagerServiceImpl). + ScopedChange* current_change_; + + bool in_destructor_; + + // TODO(sky): nuke! Just a proof of concept until get real animation api. + base::RepeatingTimer<ConnectionManager> animation_timer_; + + AnimationRunner animation_runner_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionManager); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_H_ diff --git a/mojo/services/view_manager/connection_manager_delegate.h b/mojo/services/view_manager/connection_manager_delegate.h new file mode 100644 index 0000000..22504f2 --- /dev/null +++ b/mojo/services/view_manager/connection_manager_delegate.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ +#define SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ + +#include <string> + +#include "mojo/public/cpp/bindings/interface_request.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" + +namespace mojo { +class ViewManagerService; +} + +namespace view_manager { + +class ClientConnection; +class ConnectionManager; +struct ViewId; + +class ConnectionManagerDelegate { + public: + virtual void OnLostConnectionToWindowManager() = 0; + + // Creates a ClientConnection in response to Embed() calls on the + // ConnectionManager. + virtual ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) = 0; + virtual ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) = 0; + + protected: + virtual ~ConnectionManagerDelegate() {} +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ diff --git a/mojo/services/view_manager/default_access_policy.cc b/mojo/services/view_manager/default_access_policy.cc new file mode 100644 index 0000000..64da06d --- /dev/null +++ b/mojo/services/view_manager/default_access_policy.cc @@ -0,0 +1,112 @@ +// 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 "mojo/services/view_manager/default_access_policy.h" + +#include "mojo/services/view_manager/access_policy_delegate.h" +#include "mojo/services/view_manager/server_view.h" + +namespace view_manager { + +DefaultAccessPolicy::DefaultAccessPolicy( + mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate) + : connection_id_(connection_id), delegate_(delegate) { +} + +DefaultAccessPolicy::~DefaultAccessPolicy() { +} + +bool DefaultAccessPolicy::CanRemoveViewFromParent( + const ServerView* view) const { + if (!WasCreatedByThisConnection(view)) + return false; // Can only unparent views we created. + + return delegate_->IsRootForAccessPolicy(view->parent()->id()) || + WasCreatedByThisConnection(view->parent()); +} + +bool DefaultAccessPolicy::CanAddView(const ServerView* parent, + const ServerView* child) const { + return WasCreatedByThisConnection(child) && + (delegate_->IsRootForAccessPolicy(parent->id()) || + (WasCreatedByThisConnection(parent) && + !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(parent))); +} + +bool DefaultAccessPolicy::CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const { + return WasCreatedByThisConnection(view) && + WasCreatedByThisConnection(relative_view); +} + +bool DefaultAccessPolicy::CanDeleteView(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanGetViewTree(const ServerView* view) const { + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanDescendIntoViewForViewTree( + const ServerView* view) const { + return (WasCreatedByThisConnection(view) && + !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanEmbed(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanChangeViewVisibility( + const ServerView* view) const { + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanSetViewSurfaceId(const ServerView* view) const { + // Once a view embeds another app, the embedder app is no longer able to + // call SetViewSurfaceId() - this ability is transferred to the embedded app. + if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) + return false; + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanSetViewBounds(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanSetViewProperties(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const { + if (!WasCreatedByThisConnection(view)) + return false; + + if (*new_parent && !WasCreatedByThisConnection(*new_parent) && + !delegate_->IsRootForAccessPolicy((*new_parent)->id())) { + *new_parent = NULL; + } + + if (*old_parent && !WasCreatedByThisConnection(*old_parent) && + !delegate_->IsRootForAccessPolicy((*old_parent)->id())) { + *old_parent = NULL; + } + return true; +} + +bool DefaultAccessPolicy::WasCreatedByThisConnection( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/default_access_policy.h b/mojo/services/view_manager/default_access_policy.h new file mode 100644 index 0000000..92ab4f8 --- /dev/null +++ b/mojo/services/view_manager/default_access_policy.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ +#define SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ + +#include "base/basictypes.h" +#include "mojo/services/view_manager/access_policy.h" + +namespace view_manager { + +class AccessPolicyDelegate; + +// AccessPolicy for all connections, except the window manager. +class DefaultAccessPolicy : public AccessPolicy { + public: + DefaultAccessPolicy(mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate); + ~DefaultAccessPolicy() override; + + // AccessPolicy: + bool CanRemoveViewFromParent(const ServerView* view) const override; + bool CanAddView(const ServerView* parent, + const ServerView* child) const override; + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const override; + bool CanDeleteView(const ServerView* view) const override; + bool CanGetViewTree(const ServerView* view) const override; + bool CanDescendIntoViewForViewTree(const ServerView* view) const override; + bool CanEmbed(const ServerView* view) const override; + bool CanChangeViewVisibility(const ServerView* view) const override; + bool CanSetViewSurfaceId(const ServerView* view) const override; + bool CanSetViewBounds(const ServerView* view) const override; + bool CanSetViewProperties(const ServerView* view) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const override; + + private: + bool WasCreatedByThisConnection(const ServerView* view) const; + + const mojo::ConnectionSpecificId connection_id_; + AccessPolicyDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(DefaultAccessPolicy); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ diff --git a/mojo/services/view_manager/display_manager.cc b/mojo/services/view_manager/display_manager.cc new file mode 100644 index 0000000..48422cf --- /dev/null +++ b/mojo/services/view_manager/display_manager.cc @@ -0,0 +1,187 @@ +// 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 "mojo/services/view_manager/display_manager.h" + +#include "base/numerics/safe_conversions.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/view_manager/connection_manager.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/view_coordinate_conversions.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/cpp/surfaces_utils.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/quads.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h" + +using mojo::Rect; +using mojo::Size; + +namespace view_manager { +namespace { + +void DrawViewTree(mojo::Pass* pass, + const ServerView* view, + const gfx::Vector2d& parent_to_root_origin_offset, + float opacity) { + if (!view->visible()) + return; + + const gfx::Rect absolute_bounds = + view->bounds() + parent_to_root_origin_offset; + std::vector<const ServerView*> children(view->GetChildren()); + const float combined_opacity = opacity * view->opacity(); + for (std::vector<const ServerView*>::reverse_iterator it = children.rbegin(); + it != children.rend(); + ++it) { + DrawViewTree(pass, *it, absolute_bounds.OffsetFromOrigin(), + combined_opacity); + } + + cc::SurfaceId node_id = view->surface_id(); + + auto surface_quad_state = mojo::SurfaceQuadState::New(); + surface_quad_state->surface = mojo::SurfaceId::From(node_id); + + gfx::Transform node_transform; + node_transform.Translate(absolute_bounds.x(), absolute_bounds.y()); + + const gfx::Rect bounds_at_origin(view->bounds().size()); + auto surface_quad = mojo::Quad::New(); + surface_quad->material = mojo::Material::MATERIAL_SURFACE_CONTENT; + surface_quad->rect = Rect::From(bounds_at_origin); + surface_quad->opaque_rect = Rect::From(bounds_at_origin); + surface_quad->visible_rect = Rect::From(bounds_at_origin); + surface_quad->needs_blending = true; + surface_quad->shared_quad_state_index = + base::saturated_cast<int32_t>(pass->shared_quad_states.size()); + surface_quad->surface_quad_state = surface_quad_state.Pass(); + + auto sqs = CreateDefaultSQS(*Size::From(view->bounds().size())); + sqs->blend_mode = mojo::SK_XFERMODE_kSrcOver_Mode; + sqs->opacity = combined_opacity; + sqs->content_to_target_transform = mojo::Transform::From(node_transform); + + pass->quads.push_back(surface_quad.Pass()); + pass->shared_quad_states.push_back(sqs.Pass()); +} + +} // namespace + +DefaultDisplayManager::DefaultDisplayManager( + mojo::ApplicationImpl* app_impl, + mojo::ApplicationConnection* app_connection, + const mojo::Callback<void()>& native_viewport_closed_callback) + : app_impl_(app_impl), + app_connection_(app_connection), + connection_manager_(nullptr), + draw_timer_(false, false), + frame_pending_(false), + native_viewport_closed_callback_(native_viewport_closed_callback), + weak_factory_(this) { + metrics_.size = mojo::Size::New(); + metrics_.size->width = 800; + metrics_.size->height = 600; +} + +void DefaultDisplayManager::Init(ConnectionManager* connection_manager) { + connection_manager_ = connection_manager; + app_impl_->ConnectToService("mojo:native_viewport_service", + &native_viewport_); + native_viewport_.set_error_handler(this); + native_viewport_->Create(metrics_.size->Clone(), + base::Bind(&DefaultDisplayManager::OnMetricsChanged, + weak_factory_.GetWeakPtr())); + native_viewport_->Show(); + + mojo::ContextProviderPtr context_provider; + native_viewport_->GetContextProvider(GetProxy(&context_provider)); + mojo::DisplayFactoryPtr display_factory; + app_impl_->ConnectToService("mojo:surfaces_service", &display_factory); + display_factory->Create(context_provider.Pass(), + nullptr, // returner - we never submit resources. + GetProxy(&display_)); + + mojo::NativeViewportEventDispatcherPtr event_dispatcher; + app_connection_->ConnectToService(&event_dispatcher); + native_viewport_->SetEventDispatcher(event_dispatcher.Pass()); +} + +DefaultDisplayManager::~DefaultDisplayManager() { +} + +void DefaultDisplayManager::SchedulePaint(const ServerView* view, + const gfx::Rect& bounds) { + if (!view->IsDrawn(connection_manager_->root())) + return; + const gfx::Rect root_relative_rect = + ConvertRectBetweenViews(view, connection_manager_->root(), bounds); + if (root_relative_rect.IsEmpty()) + return; + dirty_rect_.Union(root_relative_rect); + WantToDraw(); +} + +void DefaultDisplayManager::SetViewportSize(const gfx::Size& size) { + native_viewport_->SetSize(Size::From(size)); +} + +const mojo::ViewportMetrics& DefaultDisplayManager::GetViewportMetrics() { + return metrics_; +} + +void DefaultDisplayManager::Draw() { + Rect rect; + rect.width = metrics_.size->width; + rect.height = metrics_.size->height; + auto pass = CreateDefaultPass(1, rect); + pass->damage_rect = Rect::From(dirty_rect_); + + DrawViewTree(pass.get(), connection_manager_->root(), gfx::Vector2d(), 1.0f); + + auto frame = mojo::Frame::New(); + frame->passes.push_back(pass.Pass()); + frame->resources.resize(0u); + frame_pending_ = true; + display_->SubmitFrame( + frame.Pass(), + base::Bind(&DefaultDisplayManager::DidDraw, base::Unretained(this))); + dirty_rect_ = gfx::Rect(); +} + +void DefaultDisplayManager::DidDraw() { + frame_pending_ = false; + if (!dirty_rect_.IsEmpty()) + WantToDraw(); +} + +void DefaultDisplayManager::WantToDraw() { + if (draw_timer_.IsRunning() || frame_pending_) + return; + + draw_timer_.Start( + FROM_HERE, base::TimeDelta(), + base::Bind(&DefaultDisplayManager::Draw, base::Unretained(this))); +} + +void DefaultDisplayManager::OnMetricsChanged(mojo::ViewportMetricsPtr metrics) { + metrics_.size = metrics->size.Clone(); + metrics_.device_pixel_ratio = metrics->device_pixel_ratio; + gfx::Rect bounds(metrics_.size.To<gfx::Size>()); + connection_manager_->root()->SetBounds(bounds); + connection_manager_->ProcessViewportMetricsChanged(metrics_, *metrics); + native_viewport_->RequestMetrics(base::Bind( + &DefaultDisplayManager::OnMetricsChanged, weak_factory_.GetWeakPtr())); +} + +void DefaultDisplayManager::OnConnectionError() { + // This is called when the native_viewport is torn down before + // ~DefaultDisplayManager may be called. + native_viewport_closed_callback_.Run(); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/display_manager.h b/mojo/services/view_manager/display_manager.h new file mode 100644 index 0000000..41f1978 --- /dev/null +++ b/mojo/services/view_manager/display_manager.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_ +#define SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "ui/gfx/geometry/rect.h" + +namespace cc { +class SurfaceIdAllocator; +} + +namespace mojo { +class ApplicationConnection; +class ApplicationImpl; +} + +namespace view_manager { + +class ConnectionManager; +class ServerView; + +// DisplayManager is used to connect the root ServerView to a display. +class DisplayManager { + public: + virtual ~DisplayManager() {} + + virtual void Init(ConnectionManager* connection_manager) = 0; + + // Schedules a paint for the specified region in the coordinates of |view|. + virtual void SchedulePaint(const ServerView* view, + const gfx::Rect& bounds) = 0; + + virtual void SetViewportSize(const gfx::Size& size) = 0; + + virtual const mojo::ViewportMetrics& GetViewportMetrics() = 0; +}; + +// DisplayManager implementation that connects to the services necessary to +// actually display. +class DefaultDisplayManager : public DisplayManager, + public mojo::ErrorHandler { + public: + DefaultDisplayManager( + mojo::ApplicationImpl* app_impl, + mojo::ApplicationConnection* app_connection, + const mojo::Callback<void()>& native_viewport_closed_callback); + ~DefaultDisplayManager() override; + + // DisplayManager: + void Init(ConnectionManager* connection_manager) override; + void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override; + void SetViewportSize(const gfx::Size& size) override; + const mojo::ViewportMetrics& GetViewportMetrics() override; + + private: + void WantToDraw(); + void Draw(); + void DidDraw(); + + void OnMetricsChanged(mojo::ViewportMetricsPtr metrics); + + // ErrorHandler: + void OnConnectionError() override; + + mojo::ApplicationImpl* app_impl_; + mojo::ApplicationConnection* app_connection_; + ConnectionManager* connection_manager_; + + mojo::ViewportMetrics metrics_; + gfx::Rect dirty_rect_; + base::Timer draw_timer_; + bool frame_pending_; + + mojo::DisplayPtr display_; + mojo::NativeViewportPtr native_viewport_; + mojo::Callback<void()> native_viewport_closed_callback_; + base::WeakPtrFactory<DefaultDisplayManager> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DefaultDisplayManager); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_DISPLAY_MANAGER_H_ diff --git a/mojo/services/view_manager/ids.h b/mojo/services/view_manager/ids.h new file mode 100644 index 0000000..90c3163 --- /dev/null +++ b/mojo/services/view_manager/ids.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_IDS_H_ +#define SERVICES_VIEW_MANAGER_IDS_H_ + +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" + +namespace view_manager { + +// Connection id is used to indicate no connection. That is, no +// ViewManagerServiceImpl ever gets this id. +const mojo::ConnectionSpecificId kInvalidConnectionId = 0; + +// Adds a bit of type safety to view ids. +struct ViewId { + ViewId(mojo::ConnectionSpecificId connection_id, + mojo::ConnectionSpecificId view_id) + : connection_id(connection_id), view_id(view_id) {} + ViewId() : connection_id(0), view_id(0) {} + + bool operator==(const ViewId& other) const { + return other.connection_id == connection_id && + other.view_id == view_id; + } + + bool operator!=(const ViewId& other) const { + return !(*this == other); + } + + mojo::ConnectionSpecificId connection_id; + mojo::ConnectionSpecificId view_id; +}; + +inline ViewId ViewIdFromTransportId(mojo::Id id) { + return ViewId(mojo::HiWord(id), mojo::LoWord(id)); +} + +inline mojo::Id ViewIdToTransportId(const ViewId& id) { + return (id.connection_id << 16) | id.view_id; +} + +inline ViewId RootViewId() { + return ViewId(kInvalidConnectionId, 1); +} + +// Returns a ViewId that is reserved to indicate no view. That is, no view will +// ever be created with this id. +inline ViewId InvalidViewId() { + return ViewId(kInvalidConnectionId, 0); +} + +// All cloned views use this id. +inline ViewId ClonedViewId() { + return ViewId(kInvalidConnectionId, 2); +} + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_IDS_H_ diff --git a/mojo/services/view_manager/main.cc b/mojo/services/view_manager/main.cc new file mode 100644 index 0000000..d3ced3d --- /dev/null +++ b/mojo/services/view_manager/main.cc @@ -0,0 +1,12 @@ +// 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 "mojo/application/application_runner_chromium.h" +#include "mojo/public/c/system/main.h" +#include "mojo/services/view_manager/view_manager_app.h" + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new view_manager::ViewManagerApp); + return runner.Run(shell_handle); +} diff --git a/mojo/services/view_manager/scheduled_animation_group.cc b/mojo/services/view_manager/scheduled_animation_group.cc new file mode 100644 index 0000000..0f27c42 --- /dev/null +++ b/mojo/services/view_manager/scheduled_animation_group.cc @@ -0,0 +1,352 @@ +// 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 "mojo/services/view_manager/scheduled_animation_group.h" + +#include <set> + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "mojo/services/view_manager/server_view.h" + +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::AnimationProperty; + +namespace view_manager { +namespace { + +using Sequences = std::vector<ScheduledAnimationSequence>; + +// Gets the value of |property| from |view| into |value|. +void GetValueFromView(const ServerView* view, + AnimationProperty property, + ScheduledAnimationValue* value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + NOTREACHED(); + break; + case ANIMATION_PROPERTY_OPACITY: + value->float_value = view->opacity(); + break; + case ANIMATION_PROPERTY_TRANSFORM: + value->transform = view->transform(); + break; + } +} + +// Sets the value of |property| from |value| into |view|. +void SetViewPropertyFromValue(ServerView* view, + AnimationProperty property, + const ScheduledAnimationValue& value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + break; + case ANIMATION_PROPERTY_OPACITY: + view->SetOpacity(value.float_value); + break; + case ANIMATION_PROPERTY_TRANSFORM: + view->SetTransform(value.transform); + break; + } +} + +// Sets the value of |property| into |view| between two points. +void SetViewPropertyFromValueBetween(ServerView* view, + AnimationProperty property, + double value, + gfx::Tween::Type tween_type, + const ScheduledAnimationValue& start, + const ScheduledAnimationValue& target) { + const double tween_value = gfx::Tween::CalculateValue(tween_type, value); + switch (property) { + case ANIMATION_PROPERTY_NONE: + break; + case ANIMATION_PROPERTY_OPACITY: + view->SetOpacity(gfx::Tween::FloatValueBetween( + tween_value, start.float_value, target.float_value)); + break; + case ANIMATION_PROPERTY_TRANSFORM: + view->SetTransform(gfx::Tween::TransformValueBetween( + tween_value, start.transform, target.transform)); + break; + } +} + +gfx::Tween::Type AnimationTypeToTweenType(mojo::AnimationTweenType type) { + switch (type) { + case mojo::ANIMATION_TWEEN_TYPE_LINEAR: + return gfx::Tween::LINEAR; + case mojo::ANIMATION_TWEEN_TYPE_EASE_IN: + return gfx::Tween::EASE_IN; + case mojo::ANIMATION_TWEEN_TYPE_EASE_OUT: + return gfx::Tween::EASE_OUT; + case mojo::ANIMATION_TWEEN_TYPE_EASE_IN_OUT: + return gfx::Tween::EASE_IN_OUT; + } + return gfx::Tween::LINEAR; +} + +void ConvertToScheduledValue(const mojo::AnimationValue& transport_value, + ScheduledAnimationValue* value) { + value->float_value = transport_value.float_value; + value->transform = transport_value.transform.To<gfx::Transform>(); +} + +void ConvertToScheduledElement(const mojo::AnimationElement& transport_element, + ScheduledAnimationElement* element) { + element->property = transport_element.property; + element->duration = + base::TimeDelta::FromMicroseconds(transport_element.duration); + element->tween_type = AnimationTypeToTweenType(transport_element.tween_type); + if (transport_element.property != ANIMATION_PROPERTY_NONE) { + if (transport_element.start_value.get()) { + element->is_start_valid = true; + ConvertToScheduledValue(*transport_element.start_value, + &(element->start_value)); + } else { + element->is_start_valid = false; + } + ConvertToScheduledValue(*transport_element.target_value, + &(element->target_value)); + } +} + +bool IsAnimationValueValid(AnimationProperty property, + const mojo::AnimationValue& value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + NOTREACHED(); + return false; + case ANIMATION_PROPERTY_OPACITY: + return value.float_value >= 0.f && value.float_value <= 1.f; + case ANIMATION_PROPERTY_TRANSFORM: + return value.transform.get() && value.transform->matrix.size() == 16u; + } + return false; +} + +bool IsAnimationElementValid(const mojo::AnimationElement& element) { + if (element.property == ANIMATION_PROPERTY_NONE) + return true; // None is a pause and doesn't need any values. + if (element.start_value.get() && + !IsAnimationValueValid(element.property, *element.start_value)) + return false; + // For all other properties we require a target. + return element.target_value.get() && + IsAnimationValueValid(element.property, *element.target_value); +} + +bool IsAnimationSequenceValid(const mojo::AnimationSequence& sequence) { + if (sequence.elements.size() == 0u) + return false; + + for (size_t i = 0; i < sequence.elements.size(); ++i) { + if (!IsAnimationElementValid(*sequence.elements[i])) + return false; + } + return true; +} + +bool IsAnimationGroupValid(const mojo::AnimationGroup& transport_group) { + if (transport_group.sequences.size() == 0u) + return false; + for (size_t i = 0; i < transport_group.sequences.size(); ++i) { + if (!IsAnimationSequenceValid(*transport_group.sequences[i])) + return false; + } + return true; +} + +// If the start value for |element| isn't valid, the value for the property +// is obtained from |view| and placed into |element|. +void GetStartValueFromViewIfNecessary(const ServerView* view, + ScheduledAnimationElement* element) { + if (element->property != ANIMATION_PROPERTY_NONE && + !element->is_start_valid) { + GetValueFromView(view, element->property, &(element->start_value)); + } +} + +void GetScheduledAnimationProperties(const Sequences& sequences, + std::set<AnimationProperty>* properties) { + for (const ScheduledAnimationSequence& sequence : sequences) { + for (const ScheduledAnimationElement& element : sequence.elements) + properties->insert(element.property); + } +} + +void SetPropertyToTargetProperty(ServerView* view, + mojo::AnimationProperty property, + const Sequences& sequences) { + // NOTE: this doesn't deal with |cycle_count| quite right, but I'm honestly + // not sure we really want to support the same property in multiple sequences + // animating at once so I'm not dealing. + base::TimeDelta max_end_duration; + scoped_ptr<ScheduledAnimationValue> value; + for (const ScheduledAnimationSequence& sequence : sequences) { + base::TimeDelta duration; + for (const ScheduledAnimationElement& element : sequence.elements) { + if (element.property != property) + continue; + + duration += element.duration; + if (duration > max_end_duration) { + max_end_duration = duration; + value.reset(new ScheduledAnimationValue(element.target_value)); + } + } + } + if (value.get()) + SetViewPropertyFromValue(view, property, *value); +} + +void ConvertSequenceToScheduled( + const mojo::AnimationSequence& transport_sequence, + base::TimeTicks now, + ScheduledAnimationSequence* sequence) { + sequence->run_until_stopped = transport_sequence.cycle_count == 0u; + sequence->cycle_count = transport_sequence.cycle_count; + DCHECK_NE(0u, transport_sequence.elements.size()); + sequence->elements.resize(transport_sequence.elements.size()); + + base::TimeTicks element_start_time = now; + for (size_t i = 0; i < transport_sequence.elements.size(); ++i) { + ConvertToScheduledElement(*(transport_sequence.elements[i].get()), + &(sequence->elements[i])); + sequence->elements[i].start_time = element_start_time; + sequence->duration += sequence->elements[i].duration; + element_start_time += sequence->elements[i].duration; + } +} + +bool AdvanceSequence(ServerView* view, + ScheduledAnimationSequence* sequence, + base::TimeTicks now) { + ScheduledAnimationElement* element = + &(sequence->elements[sequence->current_index]); + while (element->start_time + element->duration < now) { + SetViewPropertyFromValue(view, element->property, element->target_value); + if (++sequence->current_index == sequence->elements.size()) { + if (!sequence->run_until_stopped && --sequence->cycle_count == 0) { + SetViewPropertyFromValue(view, element->property, + element->target_value); + return false; + } + + sequence->current_index = 0; + } + sequence->elements[sequence->current_index].start_time = + element->start_time + element->duration; + element = &(sequence->elements[sequence->current_index]); + GetStartValueFromViewIfNecessary(view, element); + + // It's possible for the delta between now and |last_tick_time_| to be very + // big (could happen if machine sleeps and is woken up much later). Normally + // the repeat count is smallish, so we don't bother optimizing it. OTOH if + // a sequence repeats forever we optimize it lest we get stuck in this loop + // for a very long time. + if (sequence->run_until_stopped && sequence->current_index == 0) { + element->start_time = + now - base::TimeDelta::FromMicroseconds( + (now - element->start_time).InMicroseconds() % + sequence->duration.InMicroseconds()); + } + } + return true; +} + +} // namespace + +ScheduledAnimationValue::ScheduledAnimationValue() { +} +ScheduledAnimationValue::~ScheduledAnimationValue() { +} + +ScheduledAnimationElement::ScheduledAnimationElement() + : property(ANIMATION_PROPERTY_OPACITY), + tween_type(gfx::Tween::EASE_IN), + is_start_valid(false) { +} +ScheduledAnimationElement::~ScheduledAnimationElement() { +} + +ScheduledAnimationSequence::ScheduledAnimationSequence() + : run_until_stopped(false), cycle_count(0), current_index(0u) { +} +ScheduledAnimationSequence::~ScheduledAnimationSequence() { +} + +ScheduledAnimationGroup::~ScheduledAnimationGroup() { +} + +// static +scoped_ptr<ScheduledAnimationGroup> ScheduledAnimationGroup::Create( + ServerView* view, + base::TimeTicks now, + uint32_t id, + const mojo::AnimationGroup& transport_group) { + if (!IsAnimationGroupValid(transport_group)) + return nullptr; + + scoped_ptr<ScheduledAnimationGroup> group( + new ScheduledAnimationGroup(view, id, now)); + group->sequences_.resize(transport_group.sequences.size()); + for (size_t i = 0; i < transport_group.sequences.size(); ++i) { + const mojo::AnimationSequence& transport_sequence( + *(transport_group.sequences[i])); + DCHECK_NE(0u, transport_sequence.elements.size()); + ConvertSequenceToScheduled(transport_sequence, now, &group->sequences_[i]); + } + return group.Pass(); +} + +void ScheduledAnimationGroup::ObtainStartValues() { + for (ScheduledAnimationSequence& sequence : sequences_) + GetStartValueFromViewIfNecessary(view_, &(sequence.elements[0])); +} + +void ScheduledAnimationGroup::SetValuesToTargetValuesForPropertiesNotIn( + const ScheduledAnimationGroup& other) { + std::set<AnimationProperty> our_properties; + GetScheduledAnimationProperties(sequences_, &our_properties); + + std::set<AnimationProperty> other_properties; + GetScheduledAnimationProperties(other.sequences_, &other_properties); + + for (AnimationProperty property : our_properties) { + if (other_properties.count(property) == 0 && + property != ANIMATION_PROPERTY_NONE) { + SetPropertyToTargetProperty(view_, property, sequences_); + } + } +} + +bool ScheduledAnimationGroup::Tick(base::TimeTicks time) { + for (Sequences::iterator i = sequences_.begin(); i != sequences_.end();) { + if (!AdvanceSequence(view_, &(*i), time)) { + i = sequences_.erase(i); + continue; + } + const ScheduledAnimationElement& active_element( + i->elements[i->current_index]); + const double percent = + (time - active_element.start_time).InMillisecondsF() / + active_element.duration.InMillisecondsF(); + SetViewPropertyFromValueBetween( + view_, active_element.property, percent, active_element.tween_type, + active_element.start_value, active_element.target_value); + ++i; + } + return sequences_.empty(); +} + +ScheduledAnimationGroup::ScheduledAnimationGroup(ServerView* view, + uint32_t id, + base::TimeTicks time_scheduled) + : view_(view), id_(id), time_scheduled_(time_scheduled) { +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/scheduled_animation_group.h b/mojo/services/view_manager/scheduled_animation_group.h new file mode 100644 index 0000000..63aa899 --- /dev/null +++ b/mojo/services/view_manager/scheduled_animation_group.h @@ -0,0 +1,110 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ +#define SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/transform.h" + +namespace view_manager { + +class ServerView; + +struct ScheduledAnimationValue { + ScheduledAnimationValue(); + ~ScheduledAnimationValue(); + + float float_value; + gfx::Transform transform; +}; + +struct ScheduledAnimationElement { + ScheduledAnimationElement(); + ~ScheduledAnimationElement(); + + mojo::AnimationProperty property; + base::TimeDelta duration; + gfx::Tween::Type tween_type; + bool is_start_valid; + ScheduledAnimationValue start_value; + ScheduledAnimationValue target_value; + // Start time is based on scheduled time and relative to any other elements + // in the sequence. + base::TimeTicks start_time; +}; + +struct ScheduledAnimationSequence { + ScheduledAnimationSequence(); + ~ScheduledAnimationSequence(); + + bool run_until_stopped; + std::vector<ScheduledAnimationElement> elements; + + // Sum of the duration of all elements. This does not take into account + // |cycle_count|. + base::TimeDelta duration; + + // The following values are updated as the animation progresses. + + // Number of cycles remaining. This is only used if |run_until_stopped| is + // false. + uint32_t cycle_count; + + // Index into |elements| of the element currently animating. + size_t current_index; +}; + +// Corresponds to a mojo::AnimationGroup and is responsible for running the +// actual animation. +class ScheduledAnimationGroup { + public: + ~ScheduledAnimationGroup(); + + // Returns a new ScheduledAnimationGroup from the supplied parameters, or + // null if |transport_group| isn't valid. + static scoped_ptr<ScheduledAnimationGroup> Create( + ServerView* view, + base::TimeTicks now, + uint32_t id, + const mojo::AnimationGroup& transport_group); + + uint32_t id() const { return id_; } + + // Gets the start value for any elements that don't have an explicit start. + // value. + void ObtainStartValues(); + + // Sets the values of any properties that are not in |other| to their final + // value. + void SetValuesToTargetValuesForPropertiesNotIn( + const ScheduledAnimationGroup& other); + + // Advances the group. |time| is the current time. Returns true if the group + // is done (nothing left to animate). + bool Tick(base::TimeTicks time); + + ServerView* view() { return view_; } + + private: + ScheduledAnimationGroup(ServerView* view, + uint32_t id, + base::TimeTicks time_scheduled); + + ServerView* view_; + const uint32_t id_; + base::TimeTicks time_scheduled_; + std::vector<ScheduledAnimationSequence> sequences_; + + DISALLOW_COPY_AND_ASSIGN(ScheduledAnimationGroup); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ diff --git a/mojo/services/view_manager/scheduled_animation_group_unittest.cc b/mojo/services/view_manager/scheduled_animation_group_unittest.cc new file mode 100644 index 0000000..bdef45a --- /dev/null +++ b/mojo/services/view_manager/scheduled_animation_group_unittest.cc @@ -0,0 +1,97 @@ +// 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 "mojo/services/view_manager/scheduled_animation_group.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h" + +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::ANIMATION_TWEEN_TYPE_LINEAR; +using mojo::AnimationGroup; +using mojo::AnimationSequence; +using mojo::AnimationElement; +using mojo::AnimationValue; + +namespace view_manager { +namespace { + +bool IsAnimationGroupValid(const AnimationGroup& transport_group) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create( + &view, base::TimeTicks::Now(), 1, transport_group)); + return group.get() != nullptr; +} + +} // namespace + +TEST(ScheduledAnimationGroupTest, IsAnimationGroupValid) { + AnimationGroup group; + + // AnimationGroup with no sequences is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + group.sequences.push_back(AnimationSequence::New()); + + // Sequence with no elements is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + AnimationSequence& sequence = *(group.sequences[0]); + sequence.elements.push_back(AnimationElement::New()); + AnimationElement& element = *(sequence.elements[0]); + element.property = ANIMATION_PROPERTY_OPACITY; + element.tween_type = ANIMATION_TWEEN_TYPE_LINEAR; + + // Element with no target_value is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + // Opacity must be between 0 and 1. + element.target_value = AnimationValue::New(); + element.target_value->float_value = 2.5f; + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element.target_value->float_value = .5f; + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Bogus start value. + element.start_value = AnimationValue::New(); + element.start_value->float_value = 2.5f; + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element.start_value->float_value = .5f; + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Bogus transform. + element.property = ANIMATION_PROPERTY_TRANSFORM; + EXPECT_FALSE(IsAnimationGroupValid(group)); + element.start_value->transform = mojo::Transform::From(gfx::Transform()); + EXPECT_FALSE(IsAnimationGroupValid(group)); + element.target_value->transform = mojo::Transform::From(gfx::Transform()); + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Add another empty sequence, should be invalid again. + group.sequences.push_back(AnimationSequence::New()); + EXPECT_FALSE(IsAnimationGroupValid(group)); + + AnimationSequence& sequence2 = *(group.sequences[1]); + sequence2.elements.push_back(AnimationElement::New()); + AnimationElement& element2 = *(sequence2.elements[0]); + element2.property = ANIMATION_PROPERTY_OPACITY; + element2.tween_type = ANIMATION_TWEEN_TYPE_LINEAR; + + // Element with no target_value is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element2.property = ANIMATION_PROPERTY_NONE; + EXPECT_TRUE(IsAnimationGroupValid(group)); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/server_view.cc b/mojo/services/view_manager/server_view.cc new file mode 100644 index 0000000..6459f9a --- /dev/null +++ b/mojo/services/view_manager/server_view.cc @@ -0,0 +1,206 @@ +// 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 "mojo/services/view_manager/server_view.h" + +#include <inttypes.h> + +#include "base/strings/stringprintf.h" +#include "mojo/services/view_manager/server_view_delegate.h" + +namespace view_manager { + +ServerView::ServerView(ServerViewDelegate* delegate, const ViewId& id) + : delegate_(delegate), + id_(id), + parent_(nullptr), + visible_(false), + opacity_(1) { + DCHECK(delegate); // Must provide a delegate. +} + +ServerView::~ServerView() { + delegate_->OnWillDestroyView(this); + + while (!children_.empty()) + children_.front()->parent()->Remove(children_.front()); + + if (parent_) + parent_->Remove(this); + + delegate_->OnViewDestroyed(this); +} + +void ServerView::Add(ServerView* child) { + // We assume validation checks happened already. + DCHECK(child); + DCHECK(child != this); + DCHECK(!child->Contains(this)); + if (child->parent() == this) { + if (children_.size() == 1) + return; // Already in the right position. + Reorder(child, children_.back(), mojo::ORDER_DIRECTION_ABOVE); + return; + } + + ServerView* old_parent = child->parent(); + child->delegate_->OnWillChangeViewHierarchy(child, this, old_parent); + if (child->parent()) + child->parent()->RemoveImpl(child); + + child->parent_ = this; + children_.push_back(child); + child->delegate_->OnViewHierarchyChanged(child, this, old_parent); +} + +void ServerView::Remove(ServerView* child) { + // We assume validation checks happened else where. + DCHECK(child); + DCHECK(child != this); + DCHECK(child->parent() == this); + + child->delegate_->OnWillChangeViewHierarchy(child, NULL, this); + RemoveImpl(child); + child->delegate_->OnViewHierarchyChanged(child, NULL, this); +} + +void ServerView::Reorder(ServerView* child, + ServerView* relative, + mojo::OrderDirection direction) { + // We assume validation checks happened else where. + DCHECK(child); + DCHECK(child->parent() == this); + DCHECK_GT(children_.size(), 1u); + children_.erase(std::find(children_.begin(), children_.end(), child)); + Views::iterator i = std::find(children_.begin(), children_.end(), relative); + if (direction == mojo::ORDER_DIRECTION_ABOVE) { + DCHECK(i != children_.end()); + children_.insert(++i, child); + } else if (direction == mojo::ORDER_DIRECTION_BELOW) { + DCHECK(i != children_.end()); + children_.insert(i, child); + } + delegate_->OnViewReordered(this, relative, direction); +} + +void ServerView::SetBounds(const gfx::Rect& bounds) { + if (bounds_ == bounds) + return; + + const gfx::Rect old_bounds = bounds_; + bounds_ = bounds; + delegate_->OnViewBoundsChanged(this, old_bounds, bounds); +} + +const ServerView* ServerView::GetRoot() const { + const ServerView* view = this; + while (view && view->parent()) + view = view->parent(); + return view; +} + +std::vector<const ServerView*> ServerView::GetChildren() const { + std::vector<const ServerView*> children; + children.reserve(children_.size()); + for (size_t i = 0; i < children_.size(); ++i) + children.push_back(children_[i]); + return children; +} + +std::vector<ServerView*> ServerView::GetChildren() { + // TODO(sky): rename to children() and fix return type. + return children_; +} + +bool ServerView::Contains(const ServerView* view) const { + for (const ServerView* parent = view; parent; parent = parent->parent_) { + if (parent == this) + return true; + } + return false; +} + +void ServerView::SetVisible(bool value) { + if (visible_ == value) + return; + + delegate_->OnWillChangeViewVisibility(this); + visible_ = value; +} + +void ServerView::SetOpacity(float value) { + if (value == opacity_) + return; + opacity_ = value; + delegate_->OnScheduleViewPaint(this); +} + +void ServerView::SetTransform(const gfx::Transform& transform) { + if (transform_ == transform) + return; + + transform_ = transform; + delegate_->OnScheduleViewPaint(this); +} + +void ServerView::SetProperty(const std::string& name, + const std::vector<uint8_t>* value) { + auto it = properties_.find(name); + if (it != properties_.end()) { + if (value && it->second == *value) + return; + } else if (!value) { + // This property isn't set in |properties_| and |value| is NULL, so there's + // no change. + return; + } + + if (value) { + properties_[name] = *value; + } else if (it != properties_.end()) { + properties_.erase(it); + } + + delegate_->OnViewSharedPropertyChanged(this, name, value); +} + +bool ServerView::IsDrawn(const ServerView* root) const { + if (!root->visible_) + return false; + const ServerView* view = this; + while (view && view != root && view->visible_) + view = view->parent_; + return view == root; +} + +void ServerView::SetSurfaceId(cc::SurfaceId surface_id) { + surface_id_ = surface_id; + delegate_->OnViewSurfaceIdChanged(this); +} + +#if !defined(NDEBUG) +std::string ServerView::GetDebugWindowHierarchy() const { + std::string result; + BuildDebugInfo(std::string(), &result); + return result; +} + +void ServerView::BuildDebugInfo(const std::string& depth, + std::string* result) const { + *result += base::StringPrintf( + "%sid=%d,%d visible=%s bounds=%d,%d %dx%d surface_id=%" PRIu64 "\n", + depth.c_str(), static_cast<int>(id_.connection_id), + static_cast<int>(id_.view_id), visible_ ? "true" : "false", bounds_.x(), + bounds_.y(), bounds_.width(), bounds_.height(), surface_id_.id); + for (const ServerView* child : children_) + child->BuildDebugInfo(depth + " ", result); +} +#endif + +void ServerView::RemoveImpl(ServerView* view) { + view->parent_ = NULL; + children_.erase(std::find(children_.begin(), children_.end(), view)); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/server_view.h b/mojo/services/view_manager/server_view.h new file mode 100644 index 0000000..05d000c --- /dev/null +++ b/mojo/services/view_manager/server_view.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_SERVER_VIEW_H_ +#define SERVICES_VIEW_MANAGER_SERVER_VIEW_H_ + +#include <vector> + +#include "base/logging.h" +#include "cc/surfaces/surface_id.h" +#include "mojo/services/view_manager/ids.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/transform.h" + +namespace view_manager { + +class ServerViewDelegate; + +// Server side representation of a view. Delegate is informed of interesting +// events. +// +// It is assumed that all functions that mutate the tree have validated the +// mutation is possible before hand. For example, Reorder() assumes the supplied +// view is a child and not already in position. +class ServerView { + public: + ServerView(ServerViewDelegate* delegate, const ViewId& id); + virtual ~ServerView(); + + const ViewId& id() const { return id_; } + + void Add(ServerView* child); + void Remove(ServerView* child); + void Reorder(ServerView* child, + ServerView* relative, + mojo::OrderDirection direction); + + const gfx::Rect& bounds() const { return bounds_; } + void SetBounds(const gfx::Rect& bounds); + + const ServerView* parent() const { return parent_; } + ServerView* parent() { return parent_; } + + const ServerView* GetRoot() const; + ServerView* GetRoot() { + return const_cast<ServerView*>( + const_cast<const ServerView*>(this)->GetRoot()); + } + + std::vector<const ServerView*> GetChildren() const; + std::vector<ServerView*> GetChildren(); + + // Returns true if this contains |view| or is |view|. + bool Contains(const ServerView* view) const; + + // Returns true if the window is visible. This does not consider visibility + // of any ancestors. + bool visible() const { return visible_; } + void SetVisible(bool value); + + float opacity() const { return opacity_; } + void SetOpacity(float value); + + const gfx::Transform& transform() const { return transform_; } + void SetTransform(const gfx::Transform& transform); + + const std::map<std::string, std::vector<uint8_t>>& properties() const { + return properties_; + } + void SetProperty(const std::string& name, const std::vector<uint8_t>* value); + + // Returns true if this view is attached to |root| and all ancestors are + // visible. + bool IsDrawn(const ServerView* root) const; + + void SetSurfaceId(cc::SurfaceId surface_id); + const cc::SurfaceId& surface_id() const { return surface_id_; } + +#if !defined(NDEBUG) + std::string GetDebugWindowHierarchy() const; + void BuildDebugInfo(const std::string& depth, std::string* result) const; +#endif + + private: + typedef std::vector<ServerView*> Views; + + // Implementation of removing a view. Doesn't send any notification. + void RemoveImpl(ServerView* view); + + ServerViewDelegate* delegate_; + const ViewId id_; + ServerView* parent_; + Views children_; + bool visible_; + gfx::Rect bounds_; + cc::SurfaceId surface_id_; + float opacity_; + gfx::Transform transform_; + + std::map<std::string, std::vector<uint8_t>> properties_; + + DISALLOW_COPY_AND_ASSIGN(ServerView); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_SERVER_VIEW_H_ diff --git a/mojo/services/view_manager/server_view_delegate.h b/mojo/services/view_manager/server_view_delegate.h new file mode 100644 index 0000000..beca8d2 --- /dev/null +++ b/mojo/services/view_manager/server_view_delegate.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ +#define SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ + +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +namespace gfx { +class Rect; +} + +namespace mojo { +class ViewportMetrics; +} + +namespace view_manager { + +class ServerView; + +class ServerViewDelegate { + public: + // Invoked when a view is about to be destroyed; before any of the children + // have been removed and before the view has been removed from its parent. + virtual void OnWillDestroyView(ServerView* view) = 0; + + // Invoked at the end of the View's destructor (after it has been removed from + // the hierarchy). + virtual void OnViewDestroyed(const ServerView* view) = 0; + + virtual void OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) = 0; + + virtual void OnViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) = 0; + + virtual void OnViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) = 0; + + virtual void OnViewSurfaceIdChanged(const ServerView* view) = 0; + + virtual void OnViewReordered(const ServerView* view, + const ServerView* relative, + mojo::OrderDirection direction) = 0; + + virtual void OnWillChangeViewVisibility(ServerView* view) = 0; + + virtual void OnViewSharedPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) = 0; + + virtual void OnScheduleViewPaint(const ServerView* view) = 0; + + protected: + virtual ~ServerViewDelegate() {} +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ diff --git a/mojo/services/view_manager/test_change_tracker.cc b/mojo/services/view_manager/test_change_tracker.cc new file mode 100644 index 0000000..3e31aa4 --- /dev/null +++ b/mojo/services/view_manager/test_change_tracker.cc @@ -0,0 +1,314 @@ +// 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 "mojo/services/view_manager/test_change_tracker.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/common/common_type_converters.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" + +using mojo::Array; +using mojo::Id; +using mojo::ViewDataPtr; +using mojo::String; + +namespace view_manager { + +std::string ViewIdToString(Id id) { + return (id == 0) ? "null" : base::StringPrintf("%d,%d", mojo::HiWord(id), + mojo::LoWord(id)); +} + +namespace { + +std::string RectToString(const mojo::Rect& rect) { + return base::StringPrintf("%d,%d %dx%d", rect.x, rect.y, rect.width, + rect.height); +} + +std::string DirectionToString(mojo::OrderDirection direction) { + return direction == mojo::ORDER_DIRECTION_ABOVE ? "above" : "below"; +} + +std::string ChangeToDescription1(const Change& change) { + switch (change.type) { + case CHANGE_TYPE_EMBED: + return base::StringPrintf("OnEmbed creator=%s", + change.creator_url.data()); + + case CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED: + return base::StringPrintf("OnEmbeddedAppDisconnected view=%s", + ViewIdToString(change.view_id).c_str()); + + case CHANGE_TYPE_NODE_BOUNDS_CHANGED: + return base::StringPrintf( + "BoundsChanged view=%s old_bounds=%s new_bounds=%s", + ViewIdToString(change.view_id).c_str(), + RectToString(change.bounds).c_str(), + RectToString(change.bounds2).c_str()); + + case CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED: + // TODO(sky): Not implemented. + return "ViewportMetricsChanged"; + + case CHANGE_TYPE_NODE_HIERARCHY_CHANGED: + return base::StringPrintf( + "HierarchyChanged view=%s new_parent=%s old_parent=%s", + ViewIdToString(change.view_id).c_str(), + ViewIdToString(change.view_id2).c_str(), + ViewIdToString(change.view_id3).c_str()); + + case CHANGE_TYPE_NODE_REORDERED: + return base::StringPrintf("Reordered view=%s relative=%s direction=%s", + ViewIdToString(change.view_id).c_str(), + ViewIdToString(change.view_id2).c_str(), + DirectionToString(change.direction).c_str()); + + case CHANGE_TYPE_NODE_DELETED: + return base::StringPrintf("ViewDeleted view=%s", + ViewIdToString(change.view_id).c_str()); + + case CHANGE_TYPE_NODE_VISIBILITY_CHANGED: + return base::StringPrintf("VisibilityChanged view=%s visible=%s", + ViewIdToString(change.view_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED: + return base::StringPrintf("DrawnStateChanged view=%s drawn=%s", + ViewIdToString(change.view_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_INPUT_EVENT: + return base::StringPrintf("InputEvent view=%s event_action=%d", + ViewIdToString(change.view_id).c_str(), + change.event_action); + + case CHANGE_TYPE_PROPERTY_CHANGED: + return base::StringPrintf("PropertyChanged view=%s key=%s value=%s", + ViewIdToString(change.view_id).c_str(), + change.property_key.c_str(), + change.property_value.c_str()); + + case CHANGE_TYPE_DELEGATE_EMBED: + return base::StringPrintf("DelegateEmbed url=%s", + change.embed_url.data()); + } + return std::string(); +} + +} // namespace + +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes) { + std::vector<std::string> strings(changes.size()); + for (size_t i = 0; i < changes.size(); ++i) + strings[i] = ChangeToDescription1(changes[i]); + return strings; +} + +std::string SingleChangeToDescription(const std::vector<Change>& changes) { + if (changes.size() != 1u) + return std::string(); + return ChangeToDescription1(changes[0]); +} + +std::string SingleViewDescription(const std::vector<TestView>& views) { + if (views.size() != 1u) + return std::string(); + return views[0].ToString(); +} + +std::string ChangeViewDescription(const std::vector<Change>& changes) { + if (changes.size() != 1) + return std::string(); + std::vector<std::string> view_strings(changes[0].views.size()); + for (size_t i = 0; i < changes[0].views.size(); ++i) + view_strings[i] = "[" + changes[0].views[i].ToString() + "]"; + return JoinString(view_strings, ','); +} + +TestView ViewDataToTestView(const ViewDataPtr& data) { + TestView view; + view.parent_id = data->parent_id; + view.view_id = data->view_id; + view.visible = data->visible; + view.drawn = data->drawn; + view.properties = + data->properties.To<std::map<std::string, std::vector<uint8_t>>>(); + return view; +} + +void ViewDatasToTestViews(const Array<ViewDataPtr>& data, + std::vector<TestView>* test_views) { + for (size_t i = 0; i < data.size(); ++i) + test_views->push_back(ViewDataToTestView(data[i])); +} + +Change::Change() + : type(CHANGE_TYPE_EMBED), + connection_id(0), + view_id(0), + view_id2(0), + view_id3(0), + event_action(0), + direction(mojo::ORDER_DIRECTION_ABOVE), + bool_value(false) { +} + +Change::~Change() { +} + +TestChangeTracker::TestChangeTracker() + : delegate_(NULL) { +} + +TestChangeTracker::~TestChangeTracker() { +} + +void TestChangeTracker::OnEmbed(mojo::ConnectionSpecificId connection_id, + const String& creator_url, + ViewDataPtr root) { + Change change; + change.type = CHANGE_TYPE_EMBED; + change.connection_id = connection_id; + change.creator_url = creator_url; + change.views.push_back(ViewDataToTestView(root)); + AddChange(change); +} + +void TestChangeTracker::OnEmbeddedAppDisconnected(Id view_id) { + Change change; + change.type = CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED; + change.view_id = view_id; + AddChange(change); +} + +void TestChangeTracker::OnViewBoundsChanged(Id view_id, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds) { + Change change; + change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED; + change.view_id = view_id; + change.bounds.x = old_bounds->x; + change.bounds.y = old_bounds->y; + change.bounds.width = old_bounds->width; + change.bounds.height = old_bounds->height; + change.bounds2.x = new_bounds->x; + change.bounds2.y = new_bounds->y; + change.bounds2.width = new_bounds->width; + change.bounds2.height = new_bounds->height; + AddChange(change); +} + +void TestChangeTracker::OnViewViewportMetricsChanged( + mojo::ViewportMetricsPtr old_metrics, + mojo::ViewportMetricsPtr new_metrics) { + Change change; + change.type = CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED; + // NOT IMPLEMENTED + AddChange(change); +} + +void TestChangeTracker::OnViewHierarchyChanged(Id view_id, + Id new_parent_id, + Id old_parent_id, + Array<ViewDataPtr> views) { + Change change; + change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED; + change.view_id = view_id; + change.view_id2 = new_parent_id; + change.view_id3 = old_parent_id; + ViewDatasToTestViews(views, &change.views); + AddChange(change); +} + +void TestChangeTracker::OnViewReordered(Id view_id, + Id relative_view_id, + mojo::OrderDirection direction) { + Change change; + change.type = CHANGE_TYPE_NODE_REORDERED; + change.view_id = view_id; + change.view_id2 = relative_view_id; + change.direction = direction; + AddChange(change); +} + +void TestChangeTracker::OnViewDeleted(Id view_id) { + Change change; + change.type = CHANGE_TYPE_NODE_DELETED; + change.view_id = view_id; + AddChange(change); +} + +void TestChangeTracker::OnViewVisibilityChanged(Id view_id, bool visible) { + Change change; + change.type = CHANGE_TYPE_NODE_VISIBILITY_CHANGED; + change.view_id = view_id; + change.bool_value = visible; + AddChange(change); +} + +void TestChangeTracker::OnViewDrawnStateChanged(Id view_id, bool drawn) { + Change change; + change.type = CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED; + change.view_id = view_id; + change.bool_value = drawn; + AddChange(change); +} + +void TestChangeTracker::OnViewInputEvent(Id view_id, mojo::EventPtr event) { + Change change; + change.type = CHANGE_TYPE_INPUT_EVENT; + change.view_id = view_id; + change.event_action = event->action; + AddChange(change); +} + +void TestChangeTracker::OnViewSharedPropertyChanged(Id view_id, + String name, + Array<uint8_t> data) { + Change change; + change.type = CHANGE_TYPE_PROPERTY_CHANGED; + change.view_id = view_id; + change.property_key = name; + if (data.is_null()) + change.property_value = "NULL"; + else + change.property_value = data.To<std::string>(); + AddChange(change); +} + +void TestChangeTracker::DelegateEmbed(const String& url) { + Change change; + change.type = CHANGE_TYPE_DELEGATE_EMBED; + change.embed_url = url; + AddChange(change); +} + +void TestChangeTracker::AddChange(const Change& change) { + changes_.push_back(change); + if (delegate_) + delegate_->OnChangeAdded(); +} + +TestView::TestView() {} + +TestView::~TestView() {} + +std::string TestView::ToString() const { + return base::StringPrintf("view=%s parent=%s", + ViewIdToString(view_id).c_str(), + ViewIdToString(parent_id).c_str()); +} + +std::string TestView::ToString2() const { + return base::StringPrintf("view=%s parent=%s visible=%s drawn=%s", + ViewIdToString(view_id).c_str(), + ViewIdToString(parent_id).c_str(), + visible ? "true" : "false", + drawn ? "true" : "false"); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/test_change_tracker.h b/mojo/services/view_manager/test_change_tracker.h new file mode 100644 index 0000000..822efad --- /dev/null +++ b/mojo/services/view_manager/test_change_tracker.h @@ -0,0 +1,157 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ +#define SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "mojo/public/cpp/bindings/array.h" +#include "third_party/mojo_services/src/geometry/public/interfaces/geometry.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace view_manager { + +enum ChangeType { + CHANGE_TYPE_EMBED, + CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED, + // TODO(sky): NODE->VIEW. + CHANGE_TYPE_NODE_BOUNDS_CHANGED, + CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED, + CHANGE_TYPE_NODE_HIERARCHY_CHANGED, + CHANGE_TYPE_NODE_REORDERED, + CHANGE_TYPE_NODE_VISIBILITY_CHANGED, + CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED, + CHANGE_TYPE_NODE_DELETED, + CHANGE_TYPE_INPUT_EVENT, + CHANGE_TYPE_PROPERTY_CHANGED, + CHANGE_TYPE_DELEGATE_EMBED, +}; + +// TODO(sky): consider nuking and converting directly to ViewData. +struct TestView { + TestView(); + ~TestView(); + + // Returns a string description of this. + std::string ToString() const; + + // Returns a string description that includes visible and drawn. + std::string ToString2() const; + + mojo::Id parent_id; + mojo::Id view_id; + bool visible; + bool drawn; + std::map<std::string, std::vector<uint8_t>> properties; +}; + +// Tracks a call to ViewManagerClient. See the individual functions for the +// fields that are used. +struct Change { + Change(); + ~Change(); + + ChangeType type; + mojo::ConnectionSpecificId connection_id; + std::vector<TestView> views; + mojo::Id view_id; + mojo::Id view_id2; + mojo::Id view_id3; + mojo::Rect bounds; + mojo::Rect bounds2; + int32_t event_action; + mojo::String creator_url; + mojo::String embed_url; + mojo::OrderDirection direction; + bool bool_value; + std::string property_key; + std::string property_value; +}; + +// Converts Changes to string descriptions. +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes); + +// Convenience for returning the description of the first item in |changes|. +// Returns an empty string if |changes| has something other than one entry. +std::string SingleChangeToDescription(const std::vector<Change>& changes); + +// Convenience for returning the description of the first item in |views|. +// Returns an empty string if |views| has something other than one entry. +std::string SingleViewDescription(const std::vector<TestView>& views); + +// Returns a string description of |changes[0].views|. Returns an empty string +// if change.size() != 1. +std::string ChangeViewDescription(const std::vector<Change>& changes); + +// Converts ViewDatas to TestViews. +void ViewDatasToTestViews(const mojo::Array<mojo::ViewDataPtr>& data, + std::vector<TestView>* test_views); + +// TestChangeTracker is used to record ViewManagerClient functions. It notifies +// a delegate any time a change is added. +class TestChangeTracker { + public: + // Used to notify the delegate when a change is added. A change corresponds to + // a single ViewManagerClient function. + class Delegate { + public: + virtual void OnChangeAdded() = 0; + + protected: + virtual ~Delegate() {} + }; + + TestChangeTracker(); + ~TestChangeTracker(); + + void set_delegate(Delegate* delegate) { + delegate_ = delegate; + } + + std::vector<Change>* changes() { return &changes_; } + + // Each of these functions generate a Change. There is one per + // ViewManagerClient function. + void OnEmbed(mojo::ConnectionSpecificId connection_id, + const mojo::String& creator_url, + mojo::ViewDataPtr root); + void OnEmbeddedAppDisconnected(mojo::Id view_id); + void OnViewBoundsChanged(mojo::Id view_id, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds); + void OnViewViewportMetricsChanged(mojo::ViewportMetricsPtr old_bounds, + mojo::ViewportMetricsPtr new_bounds); + void OnViewHierarchyChanged(mojo::Id view_id, + mojo::Id new_parent_id, + mojo::Id old_parent_id, + mojo::Array<mojo::ViewDataPtr> views); + void OnViewReordered(mojo::Id view_id, + mojo::Id relative_view_id, + mojo::OrderDirection direction); + void OnViewDeleted(mojo::Id view_id); + void OnViewVisibilityChanged(mojo::Id view_id, bool visible); + void OnViewDrawnStateChanged(mojo::Id view_id, bool drawn); + void OnViewInputEvent(mojo::Id view_id, mojo::EventPtr event); + void OnViewSharedPropertyChanged(mojo::Id view_id, + mojo::String name, + mojo::Array<uint8_t> data); + void DelegateEmbed(const mojo::String& url); + + private: + void AddChange(const Change& change); + + Delegate* delegate_; + std::vector<Change> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestChangeTracker); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ diff --git a/mojo/services/view_manager/test_server_view_delegate.cc b/mojo/services/view_manager/test_server_view_delegate.cc new file mode 100644 index 0000000..c7b8a97 --- /dev/null +++ b/mojo/services/view_manager/test_server_view_delegate.cc @@ -0,0 +1,57 @@ +// 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 "mojo/services/view_manager/test_server_view_delegate.h" + +namespace view_manager { + +TestServerViewDelegate::TestServerViewDelegate() { +} + +TestServerViewDelegate::~TestServerViewDelegate() { +} + +void TestServerViewDelegate::OnWillDestroyView(ServerView* view) { +} + +void TestServerViewDelegate::OnViewDestroyed(const ServerView* view) { +} + +void TestServerViewDelegate::OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { +} + +void TestServerViewDelegate::OnViewHierarchyChanged( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { +} + +void TestServerViewDelegate::OnViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { +} + +void TestServerViewDelegate::OnViewSurfaceIdChanged(const ServerView* view) { +} + +void TestServerViewDelegate::OnViewReordered(const ServerView* view, + const ServerView* relative, + mojo::OrderDirection direction) { +} + +void TestServerViewDelegate::OnWillChangeViewVisibility(ServerView* view) { +} + +void TestServerViewDelegate::OnViewSharedPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) { +} + +void TestServerViewDelegate::OnScheduleViewPaint(const ServerView* view) { +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/test_server_view_delegate.h b/mojo/services/view_manager/test_server_view_delegate.h new file mode 100644 index 0000000..3f6725e --- /dev/null +++ b/mojo/services/view_manager/test_server_view_delegate.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ +#define SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ + +#include "base/basictypes.h" +#include "mojo/services/view_manager/server_view_delegate.h" + +namespace view_manager { + +class TestServerViewDelegate : public ServerViewDelegate { + public: + TestServerViewDelegate(); + ~TestServerViewDelegate() override; + + private: + // ServerViewDelegate: + void OnWillDestroyView(ServerView* view) override; + void OnViewDestroyed(const ServerView* view) override; + void OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void OnViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) override; + void OnViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnViewSurfaceIdChanged(const ServerView* view) override; + void OnViewReordered(const ServerView* view, + const ServerView* relative, + mojo::OrderDirection direction) override; + void OnWillChangeViewVisibility(ServerView* view) override; + void OnViewSharedPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) override; + void OnScheduleViewPaint(const ServerView* view) override; + + DISALLOW_COPY_AND_ASSIGN(TestServerViewDelegate); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ diff --git a/mojo/services/view_manager/view_coordinate_conversions.cc b/mojo/services/view_manager/view_coordinate_conversions.cc new file mode 100644 index 0000000..2532ee1 --- /dev/null +++ b/mojo/services/view_manager/view_coordinate_conversions.cc @@ -0,0 +1,42 @@ +// 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 "mojo/services/view_manager/view_coordinate_conversions.h" + +#include "mojo/services/view_manager/server_view.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace view_manager { + +namespace { + +gfx::Vector2d CalculateOffsetToAncestor(const ServerView* view, + const ServerView* ancestor) { + DCHECK(ancestor->Contains(view)); + gfx::Vector2d result; + for (const ServerView* v = view; v != ancestor; v = v->parent()) + result += v->bounds().OffsetFromOrigin(); + return result; +} + +} // namespace + +gfx::Rect ConvertRectBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Rect& rect) { + DCHECK(from); + if (from == to) + return rect; + + if (from->Contains(to)) { + const gfx::Vector2d offset(CalculateOffsetToAncestor(to, from)); + return gfx::Rect(rect.origin() - offset, rect.size()); + } + DCHECK(to->Contains(from)); + const gfx::Vector2d offset(CalculateOffsetToAncestor(from, to)); + return gfx::Rect(rect.origin() + offset, rect.size()); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/view_coordinate_conversions.h b/mojo/services/view_manager/view_coordinate_conversions.h new file mode 100644 index 0000000..5c34183 --- /dev/null +++ b/mojo/services/view_manager/view_coordinate_conversions.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ +#define SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ + +namespace gfx { +class Rect; +} + +namespace view_manager { + +class ServerView; + +// Converts |rect| from the coordinates of |from| to the coordinates of |to|. +// |from| and |to| must be an ancestors or descendants of each other. +gfx::Rect ConvertRectBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Rect& rect); + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ diff --git a/mojo/services/view_manager/view_coordinate_conversions_unittest.cc b/mojo/services/view_manager/view_coordinate_conversions_unittest.cc new file mode 100644 index 0000000..2a9a018 --- /dev/null +++ b/mojo/services/view_manager/view_coordinate_conversions_unittest.cc @@ -0,0 +1,33 @@ +// 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 "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/server_view_delegate.h" +#include "mojo/services/view_manager/test_server_view_delegate.h" +#include "mojo/services/view_manager/view_coordinate_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace view_manager { + +using ViewCoordinateConversionsTest = testing::Test; + +TEST_F(ViewCoordinateConversionsTest, ConvertRectBetweenViews) { + TestServerViewDelegate d1, d2, d3; + ServerView v1(&d1, ViewId()), v2(&d2, ViewId()), v3(&d3, ViewId()); + v1.SetBounds(gfx::Rect(1, 2, 100, 100)); + v2.SetBounds(gfx::Rect(3, 4, 100, 100)); + v3.SetBounds(gfx::Rect(5, 6, 100, 100)); + v1.Add(&v2); + v2.Add(&v3); + + EXPECT_EQ(gfx::Rect(2, 1, 8, 9), + ConvertRectBetweenViews(&v1, &v3, gfx::Rect(10, 11, 8, 9))); + + EXPECT_EQ(gfx::Rect(18, 21, 8, 9), + ConvertRectBetweenViews(&v3, &v1, gfx::Rect(10, 11, 8, 9))); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/view_manager_app.cc b/mojo/services/view_manager/view_manager_app.cc new file mode 100644 index 0000000..22292a3 --- /dev/null +++ b/mojo/services/view_manager/view_manager_app.cc @@ -0,0 +1,137 @@ +// 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 "mojo/services/view_manager/view_manager_app.h" + +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/view_manager/client_connection.h" +#include "mojo/services/view_manager/connection_manager.h" +#include "mojo/services/view_manager/display_manager.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" + +using mojo::ApplicationConnection; +using mojo::ApplicationImpl; +using mojo::InterfaceRequest; +using mojo::ViewManagerService; +using mojo::WindowManagerInternalClient; + +namespace view_manager { + +ViewManagerApp::ViewManagerApp() + : app_impl_(nullptr), wm_app_connection_(nullptr) { +} + +ViewManagerApp::~ViewManagerApp() {} + +void ViewManagerApp::Initialize(ApplicationImpl* app) { + app_impl_ = app; + tracing_.Initialize(app); +} + +bool ViewManagerApp::ConfigureIncomingConnection( + ApplicationConnection* connection) { + if (connection_manager_.get()) { + VLOG(1) << "ViewManager allows only one window manager connection."; + return false; + } + wm_app_connection_ = connection; + // |connection| originates from the WindowManager. Let it connect directly + // to the ViewManager and WindowManagerInternalClient. + connection->AddService<ViewManagerService>(this); + connection->AddService<WindowManagerInternalClient>(this); + connection->ConnectToService(&wm_internal_); + // If no ServiceProvider has been sent, refuse the connection. + if (!wm_internal_) + return false; + wm_internal_.set_error_handler(this); + + scoped_ptr<DefaultDisplayManager> display_manager(new DefaultDisplayManager( + app_impl_, connection, + base::Bind(&ViewManagerApp::OnLostConnectionToWindowManager, + base::Unretained(this)))); + connection_manager_.reset( + new ConnectionManager(this, display_manager.Pass(), wm_internal_.get())); + return true; +} + +void ViewManagerApp::OnLostConnectionToWindowManager() { + ApplicationImpl::Terminate(); +} + +ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) { + mojo::ViewManagerClientPtr client; + app_impl_->ConnectToService(url, &client); + + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, url, root_id)); + return new DefaultClientConnection(service.Pass(), connection_manager, + service_request.Pass(), client.Pass()); +} + +ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) { + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, std::string(), root_id)); + return new DefaultClientConnection(service.Pass(), connection_manager, + service_request.Pass(), + view_manager_client.Pass()); +} + +void ViewManagerApp::Create(ApplicationConnection* connection, + InterfaceRequest<ViewManagerService> request) { + if (connection_manager_->has_window_manager_client_connection()) { + VLOG(1) << "ViewManager interface requested more than once."; + return; + } + + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager_.get(), kInvalidConnectionId, std::string(), + std::string("mojo:window_manager"), RootViewId())); + mojo::ViewManagerClientPtr client; + wm_internal_client_request_ = GetProxy(&client); + scoped_ptr<ClientConnection> client_connection( + new DefaultClientConnection(service.Pass(), connection_manager_.get(), + request.Pass(), client.Pass())); + connection_manager_->SetWindowManagerClientConnection( + client_connection.Pass()); +} + +void ViewManagerApp::Create( + ApplicationConnection* connection, + InterfaceRequest<WindowManagerInternalClient> request) { + if (wm_internal_client_binding_.get()) { + VLOG(1) << "WindowManagerInternalClient requested more than once."; + return; + } + + // ConfigureIncomingConnection() must have been called before getting here. + DCHECK(connection_manager_.get()); + wm_internal_client_binding_.reset( + new mojo::Binding<WindowManagerInternalClient>(connection_manager_.get(), + request.Pass())); + wm_internal_client_binding_->set_error_handler(this); + wm_internal_->SetViewManagerClient( + wm_internal_client_request_.PassMessagePipe()); +} + +void ViewManagerApp::OnConnectionError() { + ApplicationImpl::Terminate(); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/view_manager_app.h b/mojo/services/view_manager/view_manager_app.h new file mode 100644 index 0000000..83f7961 --- /dev/null +++ b/mojo/services/view_manager/view_manager_app.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_ +#define SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_ + +#include "base/memory/scoped_ptr.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/services/view_manager/connection_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +namespace mojo { +class ApplicationImpl; +} + +namespace view_manager { + +class ConnectionManager; + +class ViewManagerApp + : public mojo::ApplicationDelegate, + public ConnectionManagerDelegate, + public mojo::ErrorHandler, + public mojo::InterfaceFactory<mojo::ViewManagerService>, + public mojo::InterfaceFactory<mojo::WindowManagerInternalClient> { + public: + ViewManagerApp(); + ~ViewManagerApp() override; + + private: + // ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* app) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + + // ConnectionManagerDelegate: + void OnLostConnectionToWindowManager() override; + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) override; + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) override; + + // mojo::InterfaceFactory<mojo::ViewManagerService>: + void Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::ViewManagerService> request) override; + + // mojo::InterfaceFactory<mojo::WindowManagerInternalClient>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternalClient> request) + override; + + // ErrorHandler (for |wm_internal_| and |wm_internal_client_binding_|). + void OnConnectionError() override; + + mojo::ApplicationImpl* app_impl_; + mojo::ApplicationConnection* wm_app_connection_; + scoped_ptr<mojo::Binding<mojo::WindowManagerInternalClient>> + wm_internal_client_binding_; + mojo::InterfaceRequest<mojo::ViewManagerClient> wm_internal_client_request_; + mojo::WindowManagerInternalPtr wm_internal_; + scoped_ptr<ConnectionManager> connection_manager_; + mojo::TracingImpl tracing_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerApp); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_VIEW_MANAGER_APP_H_ diff --git a/mojo/services/view_manager/view_manager_client_apptest.cc b/mojo/services/view_manager/view_manager_client_apptest.cc new file mode 100644 index 0000000..9d441d7 --- /dev/null +++ b/mojo/services/view_manager/view_manager_client_apptest.cc @@ -0,0 +1,613 @@ +// 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 "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/test_timeouts.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "third_party/mojo_services/src/geometry/public/cpp/geometry_util.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_manager_client_impl.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_context.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" + +namespace mojo { + +namespace { + +base::RunLoop* current_run_loop = nullptr; + +void TimeoutRunLoop(const base::Closure& timeout_task, bool* timeout) { + CHECK(current_run_loop); + *timeout = true; + timeout_task.Run(); +} + +bool DoRunLoopWithTimeout() { + if (current_run_loop != nullptr) + return false; + + bool timeout = false; + base::RunLoop run_loop; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(&TimeoutRunLoop, run_loop.QuitClosure(), &timeout), + TestTimeouts::action_timeout()); + + current_run_loop = &run_loop; + current_run_loop->Run(); + current_run_loop = nullptr; + return !timeout; +} + +void QuitRunLoop() { + current_run_loop->Quit(); + current_run_loop = nullptr; +} + +class BoundsChangeObserver : public ViewObserver { + public: + explicit BoundsChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~BoundsChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewBoundsChanged(View* view, + const Rect& old_bounds, + const Rect& new_bounds) override { + DCHECK_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver); +}; + +// Wait until the bounds of the supplied view change; returns false on timeout. +bool WaitForBoundsToChange(View* view) { + BoundsChangeObserver observer(view); + return DoRunLoopWithTimeout(); +} + +// Spins a run loop until the tree beginning at |root| has |tree_size| views +// (including |root|). +class TreeSizeMatchesObserver : public ViewObserver { + public: + TreeSizeMatchesObserver(View* tree, size_t tree_size) + : tree_(tree), tree_size_(tree_size) { + tree_->AddObserver(this); + } + ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); } + + bool IsTreeCorrectSize() { return CountViews(tree_) == tree_size_; } + + private: + // Overridden from ViewObserver: + void OnTreeChanged(const TreeChangeParams& params) override { + if (IsTreeCorrectSize()) + QuitRunLoop(); + } + + size_t CountViews(const View* view) const { + size_t count = 1; + View::Children::const_iterator it = view->children().begin(); + for (; it != view->children().end(); ++it) + count += CountViews(*it); + return count; + } + + View* tree_; + size_t tree_size_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver); +}; + +// Wait until |view|'s tree size matches |tree_size|; returns false on timeout. +bool WaitForTreeSizeToMatch(View* view, size_t tree_size) { + TreeSizeMatchesObserver observer(view, tree_size); + return observer.IsTreeCorrectSize() || DoRunLoopWithTimeout(); +} + +class OrderChangeObserver : public ViewObserver { + public: + OrderChangeObserver(View* view) : view_(view) { view_->AddObserver(this); } + ~OrderChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewReordered(View* view, + View* relative_view, + OrderDirection direction) override { + DCHECK_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver); +}; + +// Wait until |view|'s tree size matches |tree_size|; returns false on timeout. +bool WaitForOrderChange(ViewManager* view_manager, View* view) { + OrderChangeObserver observer(view); + return DoRunLoopWithTimeout(); +} + +// Tracks a view's destruction. Query is_valid() for current state. +class ViewTracker : public ViewObserver { + public: + explicit ViewTracker(View* view) : view_(view) { view_->AddObserver(this); } + ~ViewTracker() override { + if (view_) + view_->RemoveObserver(this); + } + + bool is_valid() const { return !!view_; } + + private: + // Overridden from ViewObserver: + void OnViewDestroyed(View* view) override { + DCHECK_EQ(view, view_); + view_ = nullptr; + } + + int id_; + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker); +}; + +} // namespace + +// ViewManager ----------------------------------------------------------------- + +// These tests model synchronization of two peer connections to the view manager +// service, that are given access to some root view. + +class ViewManagerTest : public test::ApplicationTestBase, + public ApplicationDelegate, + public ViewManagerDelegate { + public: + ViewManagerTest() + : most_recent_view_manager_(nullptr), window_manager_(nullptr) {} + + // Overridden from ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new ViewManagerClientFactory(app->shell(), this)); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + ViewManager* window_manager() { return window_manager_; } + + // Embeds another version of the test app @ view; returns nullptr on timeout. + ViewManager* Embed(ViewManager* view_manager, View* view) { + DCHECK_EQ(view_manager, view->view_manager()); + most_recent_view_manager_ = nullptr; + view->Embed(application_impl()->url()); + if (!DoRunLoopWithTimeout()) + return nullptr; + ViewManager* vm = nullptr; + std::swap(vm, most_recent_view_manager_); + return vm; + } + + ApplicationDelegate* GetApplicationDelegate() override { return this; } + + // Overridden from ViewManagerDelegate: + void OnEmbed(View* root, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override { + most_recent_view_manager_ = root->view_manager(); + QuitRunLoop(); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + private: + // Overridden from testing::Test: + void SetUp() override { + ApplicationTestBase::SetUp(); + + view_manager_context_.reset(new ViewManagerContext(application_impl())); + view_manager_context_->Embed(application_impl()->url()); + ASSERT_TRUE(DoRunLoopWithTimeout()); + std::swap(window_manager_, most_recent_view_manager_); + } + + // Overridden from testing::Test: + void TearDown() override { ApplicationTestBase::TearDown(); } + + scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; + + scoped_ptr<ViewManagerContext> view_manager_context_; + + // Used to receive the most recent view manager loaded by an embed action. + ViewManager* most_recent_view_manager_; + // The View Manager connection held by the window manager (app running at the + // root view). + ViewManager* window_manager_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); +}; + +TEST_F(ViewManagerTest, RootView) { + ASSERT_NE(nullptr, window_manager()); + EXPECT_NE(nullptr, window_manager()->GetRoot()); + EXPECT_EQ("mojo:window_manager", window_manager()->GetEmbedderURL()); +} + +TEST_F(ViewManagerTest, Embed) { + View* view = window_manager()->CreateView(); + ASSERT_NE(nullptr, view); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetRoot(); + ASSERT_NE(nullptr, view_in_embedded); + EXPECT_EQ(view->id(), view_in_embedded->id()); + EXPECT_EQ(nullptr, view_in_embedded->parent()); + EXPECT_TRUE(view_in_embedded->children().empty()); +} + +// Window manager has two views, N1 and N11. Embeds A at N1. A should not see +// N11. +TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) { + View* view = window_manager()->CreateView(); + ASSERT_NE(nullptr, view); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + View* nested = window_manager()->CreateView(); + ASSERT_NE(nullptr, nested); + nested->SetVisible(true); + view->AddChild(nested); + + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + View* view_in_embedded = embedded->GetRoot(); + EXPECT_EQ(view->id(), view_in_embedded->id()); + EXPECT_EQ(nullptr, view_in_embedded->parent()); + EXPECT_TRUE(view_in_embedded->children().empty()); +} + +// TODO(beng): write a replacement test for the one that once existed here: +// This test validates the following scenario: +// - a view originating from one connection +// - a view originating from a second connection +// + the connection originating the view is destroyed +// -> the view should still exist (since the second connection is live) but +// should be disconnected from any views. +// http://crbug.com/396300 +// +// TODO(beng): The new test should validate the scenario as described above +// except that the second connection still has a valid tree. + +// Verifies that bounds changes applied to a view hierarchy in one connection +// are reflected to another. +TEST_F(ViewManagerTest, SetBounds) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); + + Rect rect; + rect.width = rect.height = 100; + view->SetBounds(rect); + ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); +} + +// Verifies that bounds changes applied to a view owned by a different +// connection are refused. +TEST_F(ViewManagerTest, SetBoundsSecurity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + Rect rect; + rect.width = 800; + rect.height = 600; + view->SetBounds(rect); + ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); + + rect.width = 1024; + rect.height = 768; + view_in_embedded->SetBounds(rect); + // Bounds change should have been rejected. + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); +} + +// Verifies that a view can only be destroyed by the connection that created it. +TEST_F(ViewManagerTest, DestroySecurity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + + ViewTracker tracker2(view_in_embedded); + view_in_embedded->Destroy(); + // View should not have been destroyed. + EXPECT_TRUE(tracker2.is_valid()); + + ViewTracker tracker1(view); + view->Destroy(); + EXPECT_FALSE(tracker1.is_valid()); +} + +TEST_F(ViewManagerTest, MultiRoots) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + View* view2 = window_manager()->CreateView(); + view2->SetVisible(true); + window_manager()->GetRoot()->AddChild(view2); + ViewManager* embedded1 = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded1); + ViewManager* embedded2 = Embed(window_manager(), view2); + ASSERT_NE(nullptr, embedded2); + EXPECT_NE(embedded1, embedded2); +} + +TEST_F(ViewManagerTest, EmbeddingIdentity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + EXPECT_EQ(application_impl()->url(), embedded->GetEmbedderURL()); +} + +// TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change. +// Debug and re-enable this. +TEST_F(ViewManagerTest, DISABLED_Reorder) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + + View* view11 = embedded->CreateView(); + view11->SetVisible(true); + embedded->GetRoot()->AddChild(view11); + View* view12 = embedded->CreateView(); + view12->SetVisible(true); + embedded->GetRoot()->AddChild(view12); + + View* root_in_embedded = embedded->GetRoot(); + + { + ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u)); + view11->MoveToFront(); + ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded)); + + EXPECT_EQ(root_in_embedded->children().front(), + embedded->GetViewById(view12->id())); + EXPECT_EQ(root_in_embedded->children().back(), + embedded->GetViewById(view11->id())); + } + + { + view11->MoveToBack(); + ASSERT_TRUE(WaitForOrderChange(embedded, + embedded->GetViewById(view11->id()))); + + EXPECT_EQ(root_in_embedded->children().front(), + embedded->GetViewById(view11->id())); + EXPECT_EQ(root_in_embedded->children().back(), + embedded->GetViewById(view12->id())); + } +} + +namespace { + +class VisibilityChangeObserver : public ViewObserver { + public: + explicit VisibilityChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~VisibilityChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewVisibilityChanged(View* view) override { + EXPECT_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Visible) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + // Embed another app and verify initial state. + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, embedded->GetRoot()); + View* embedded_root = embedded->GetRoot(); + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); + + // Change the visible state from the first connection and verify its mirrored + // correctly to the embedded app. + { + VisibilityChangeObserver observer(embedded_root); + view1->SetVisible(false); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_FALSE(view1->visible()); + EXPECT_FALSE(view1->IsDrawn()); + + EXPECT_FALSE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); + + // Make the node visible again. + { + VisibilityChangeObserver observer(embedded_root); + view1->SetVisible(true); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(view1->visible()); + EXPECT_TRUE(view1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); +} + +namespace { + +class DrawnChangeObserver : public ViewObserver { + public: + explicit DrawnChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~DrawnChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewDrawnChanged(View* view) override { + EXPECT_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Drawn) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + // Embed another app and verify initial state. + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, embedded->GetRoot()); + View* embedded_root = embedded->GetRoot(); + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); + + // Change the visibility of the root, this should propagate a drawn state + // change to |embedded|. + { + DrawnChangeObserver observer(embedded_root); + window_manager()->GetRoot()->SetVisible(false); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(view1->visible()); + EXPECT_FALSE(view1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); +} + +// TODO(beng): tests for view event dispatcher. +// - verify that we see events for all views. + +namespace { + +class FocusChangeObserver : public ViewObserver { + public: + explicit FocusChangeObserver(View* view) + : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) { + view_->AddObserver(this); + } + ~FocusChangeObserver() override { view_->RemoveObserver(this); } + + View* last_gained_focus() { return last_gained_focus_; } + + View* last_lost_focus() { return last_lost_focus_; } + + private: + // Overridden from ViewObserver. + void OnViewFocusChanged(View* gained_focus, View* lost_focus) override { + last_gained_focus_ = gained_focus; + last_lost_focus_ = lost_focus; + QuitRunLoop(); + } + + View* view_; + View* last_gained_focus_; + View* last_lost_focus_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Focus) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + View* view11 = embedded->CreateView(); + view11->SetVisible(true); + embedded->GetRoot()->AddChild(view11); + + // TODO(alhaad): Figure out why switching focus between views from different + // connections is causing the tests to crash and add tests for that. + { + View* embedded_root = embedded->GetRoot(); + FocusChangeObserver observer(embedded_root); + embedded_root->SetFocus(); + ASSERT_TRUE(DoRunLoopWithTimeout()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id()); + } + { + FocusChangeObserver observer(view11); + view11->SetFocus(); + ASSERT_TRUE(DoRunLoopWithTimeout()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + ASSERT_NE(nullptr, observer.last_lost_focus()); + EXPECT_EQ(view11->id(), observer.last_gained_focus()->id()); + EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id()); + } +} + +} // namespace mojo diff --git a/mojo/services/view_manager/view_manager_service_apptest.cc b/mojo/services/view_manager/view_manager_service_apptest.cc new file mode 100644 index 0000000..1237cc8 --- /dev/null +++ b/mojo/services/view_manager/view_manager_service_apptest.cc @@ -0,0 +1,1499 @@ +// 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 "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/test_change_tracker.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +using mojo::ApplicationConnection; +using mojo::ApplicationDelegate; +using mojo::Array; +using mojo::Callback; +using mojo::ConnectionSpecificId; +using mojo::ERROR_CODE_NONE; +using mojo::ErrorCode; +using mojo::EventPtr; +using mojo::Id; +using mojo::InterfaceRequest; +using mojo::ORDER_DIRECTION_ABOVE; +using mojo::ORDER_DIRECTION_BELOW; +using mojo::OrderDirection; +using mojo::RectPtr; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; +using mojo::ViewManagerClient; +using mojo::ViewManagerService; +using mojo::ViewportMetricsPtr; + +namespace view_manager { + +// Creates an id used for transport from the specified parameters. +Id BuildViewId(ConnectionSpecificId connection_id, + ConnectionSpecificId view_id) { + return (connection_id << 16) | view_id; +} + +// Callback function from ViewManagerService functions. ------------------------ + +void BoolResultCallback(base::RunLoop* run_loop, + bool* result_cache, + bool result) { + *result_cache = result; + run_loop->Quit(); +} + +void ErrorCodeResultCallback(base::RunLoop* run_loop, + ErrorCode* result_cache, + ErrorCode result) { + *result_cache = result; + run_loop->Quit(); +} + +void ViewTreeResultCallback(base::RunLoop* run_loop, + std::vector<TestView>* views, + Array<ViewDataPtr> results) { + ViewDatasToTestViews(results, views); + run_loop->Quit(); +} + +// ----------------------------------------------------------------------------- + +// The following functions call through to the supplied ViewManagerService. They +// block until call completes and return the result. +bool CreateView(ViewManagerService* vm, Id view_id) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(view_id, + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result == ERROR_CODE_NONE; +} + +bool EmbedUrl(ViewManagerService* vm, const String& url, Id root_id) { + bool result = false; + base::RunLoop run_loop; + { + vm->EmbedUrl(url, root_id, nullptr, nullptr, + base::Bind(&BoolResultCallback, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +bool Embed(ViewManagerService* vm, + Id root_id, + mojo::ViewManagerClientPtr client) { + bool result = false; + base::RunLoop run_loop; + { + vm->Embed(root_id, client.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +ErrorCode CreateViewWithErrorCode(ViewManagerService* vm, Id view_id) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(view_id, + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool AddView(ViewManagerService* vm, Id parent, Id child) { + bool result = false; + base::RunLoop run_loop; + vm->AddView(parent, child, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool RemoveViewFromParent(ViewManagerService* vm, Id view_id) { + bool result = false; + base::RunLoop run_loop; + vm->RemoveViewFromParent(view_id, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool ReorderView(ViewManagerService* vm, + Id view_id, + Id relative_view_id, + OrderDirection direction) { + bool result = false; + base::RunLoop run_loop; + vm->ReorderView(view_id, relative_view_id, direction, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +void GetViewTree(ViewManagerService* vm, + Id view_id, + std::vector<TestView>* views) { + base::RunLoop run_loop; + vm->GetViewTree(view_id, + base::Bind(&ViewTreeResultCallback, &run_loop, views)); + run_loop.Run(); +} + +bool DeleteView(ViewManagerService* vm, Id view_id) { + base::RunLoop run_loop; + bool result = false; + vm->DeleteView(view_id, base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewBounds(ViewManagerService* vm, + Id view_id, + int x, + int y, + int w, + int h) { + base::RunLoop run_loop; + bool result = false; + RectPtr rect(mojo::Rect::New()); + rect->x = x; + rect->y = y; + rect->width = w; + rect->height = h; + vm->SetViewBounds(view_id, rect.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewVisibility(ViewManagerService* vm, Id view_id, bool visible) { + base::RunLoop run_loop; + bool result = false; + vm->SetViewVisibility(view_id, visible, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewProperty(ViewManagerService* vm, + Id view_id, + const std::string& name, + const std::vector<uint8_t>* data) { + base::RunLoop run_loop; + bool result = false; + Array<uint8_t> mojo_data; + if (data) + mojo_data = Array<uint8_t>::From(*data); + vm->SetViewProperty(view_id, name, mojo_data.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +// Utility functions ----------------------------------------------------------- + +// Waits for all messages to be received by |vm|. This is done by attempting to +// create a bogus view. When we get the response we know all messages have been +// processed. +bool WaitForAllMessages(ViewManagerService* vm) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(ViewIdToTransportId(InvalidViewId()), + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result != ERROR_CODE_NONE; +} + +bool HasClonedView(const std::vector<TestView>& views) { + for (size_t i = 0; i < views.size(); ++i) + if (views[i].view_id == ViewIdToTransportId(ClonedViewId())) + return true; + return false; +} + +// ----------------------------------------------------------------------------- + +// A ViewManagerClient implementation that logs all changes to a tracker. +class ViewManagerClientImpl : public mojo::ViewManagerClient, + public TestChangeTracker::Delegate { + public: + ViewManagerClientImpl() : binding_(this) { tracker_.set_delegate(this); } + + void Bind(mojo::InterfaceRequest<mojo::ViewManagerClient> request) { + binding_.Bind(request.Pass()); + } + + mojo::ViewManagerService* service() { return service_.get(); } + TestChangeTracker* tracker() { return &tracker_; } + + // Runs a nested MessageLoop until |count| changes (calls to + // ViewManagerClient functions) have been received. + void WaitForChangeCount(size_t count) { + if (count == tracker_.changes()->size()) + return; + + ASSERT_TRUE(wait_state_.get() == nullptr); + wait_state_.reset(new WaitState); + wait_state_->change_count = count; + wait_state_->run_loop.Run(); + wait_state_.reset(); + } + + // Runs a nested MessageLoop until OnEmbed() has been encountered. + void WaitForOnEmbed() { + if (service_) + return; + embed_run_loop_.reset(new base::RunLoop); + embed_run_loop_->Run(); + embed_run_loop_.reset(); + } + + bool WaitForIncomingMethodCall() { + return binding_.WaitForIncomingMethodCall(); + } + + private: + // Used when running a nested MessageLoop. + struct WaitState { + WaitState() : change_count(0) {} + + // Number of changes waiting for. + size_t change_count; + base::RunLoop run_loop; + }; + + // TestChangeTracker::Delegate: + void OnChangeAdded() override { + if (wait_state_.get() && + wait_state_->change_count == tracker_.changes()->size()) { + wait_state_->run_loop.Quit(); + } + } + + // ViewManagerClient: + void OnEmbed(ConnectionSpecificId connection_id, + const String& creator_url, + ViewDataPtr root, + mojo::ViewManagerServicePtr view_manager_service, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + mojo::ScopedMessagePipeHandle window_manager_pipe) override { + service_ = view_manager_service.Pass(); + tracker()->OnEmbed(connection_id, creator_url, root.Pass()); + if (embed_run_loop_) + embed_run_loop_->Quit(); + } + void OnEmbeddedAppDisconnected(Id view_id) override { + tracker()->OnEmbeddedAppDisconnected(view_id); + } + void OnViewBoundsChanged(Id view_id, + RectPtr old_bounds, + RectPtr new_bounds) override { + tracker()->OnViewBoundsChanged(view_id, old_bounds.Pass(), + new_bounds.Pass()); + } + void OnViewViewportMetricsChanged(ViewportMetricsPtr old_metrics, + ViewportMetricsPtr new_metrics) override { + tracker()->OnViewViewportMetricsChanged(old_metrics.Pass(), + new_metrics.Pass()); + } + void OnViewHierarchyChanged(Id view, + Id new_parent, + Id old_parent, + Array<ViewDataPtr> views) override { + tracker()->OnViewHierarchyChanged(view, new_parent, old_parent, + views.Pass()); + } + void OnViewReordered(Id view_id, + Id relative_view_id, + OrderDirection direction) override { + tracker()->OnViewReordered(view_id, relative_view_id, direction); + } + void OnViewDeleted(Id view) override { tracker()->OnViewDeleted(view); } + void OnViewVisibilityChanged(uint32_t view, bool visible) override { + tracker()->OnViewVisibilityChanged(view, visible); + } + void OnViewDrawnStateChanged(uint32_t view, bool drawn) override { + tracker()->OnViewDrawnStateChanged(view, drawn); + } + void OnViewInputEvent(Id view_id, + EventPtr event, + const Callback<void()>& callback) override { + tracker()->OnViewInputEvent(view_id, event.Pass()); + callback.Run(); + } + void OnViewSharedPropertyChanged(uint32_t view, + const String& name, + Array<uint8_t> new_data) override { + tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass()); + } + void OnPerformAction(uint32_t view, + const String& name, + const Callback<void(bool)>& callback) override {} + + TestChangeTracker tracker_; + + mojo::ViewManagerServicePtr service_; + + // If non-null we're waiting for OnEmbed() using this RunLoop. + scoped_ptr<base::RunLoop> embed_run_loop_; + + // If non-null we're waiting for a certain number of change notifications to + // be encountered. + scoped_ptr<WaitState> wait_state_; + + mojo::Binding<ViewManagerClient> binding_; + DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl); +}; + +// ----------------------------------------------------------------------------- + +// InterfaceFactory for vending ViewManagerClientImpls. +class ViewManagerClientFactory + : public mojo::InterfaceFactory<ViewManagerClient> { + public: + ViewManagerClientFactory() {} + ~ViewManagerClientFactory() override {} + + // Runs a nested MessageLoop until a new instance has been created. + scoped_ptr<ViewManagerClientImpl> WaitForInstance() { + if (!client_impl_.get()) { + DCHECK(!run_loop_.get()); + run_loop_.reset(new base::RunLoop); + run_loop_->Run(); + run_loop_.reset(); + } + return client_impl_.Pass(); + } + + private: + // InterfaceFactory<ViewManagerClient>: + void Create(ApplicationConnection* connection, + InterfaceRequest<ViewManagerClient> request) override { + client_impl_.reset(new ViewManagerClientImpl); + client_impl_->Bind(request.Pass()); + if (run_loop_.get()) + run_loop_->Quit(); + } + + scoped_ptr<ViewManagerClientImpl> client_impl_; + scoped_ptr<base::RunLoop> run_loop_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerClientFactory); +}; + +class ViewManagerServiceAppTest + : public mojo::test::ApplicationTestBase, + public ApplicationDelegate, + public mojo::InterfaceFactory<mojo::WindowManagerInternal>, + public mojo::WindowManagerInternal { + public: + ViewManagerServiceAppTest() : wm_internal_binding_(this) {} + ~ViewManagerServiceAppTest() override {} + + protected: + // Returns the changes from the various connections. + std::vector<Change>* changes1() { return vm_client1_.tracker()->changes(); } + std::vector<Change>* changes2() { return vm_client2_->tracker()->changes(); } + std::vector<Change>* changes3() { return vm_client3_->tracker()->changes(); } + + // Various connections. |vm1()|, being the first connection, has special + // permissions (it's treated as the window manager). + ViewManagerService* vm1() { return vm1_.get(); } + ViewManagerService* vm2() { return vm_client2_->service(); } + ViewManagerService* vm3() { return vm_client3_->service(); } + + void EstablishSecondConnectionWithRoot(Id root_id) { + ASSERT_TRUE(vm_client2_.get() == nullptr); + vm_client2_ = EstablishConnectionViaEmbed(vm1(), root_id); + ASSERT_TRUE(vm_client2_.get() != nullptr); + } + + void EstablishSecondConnection(bool create_initial_view) { + if (create_initial_view) + ASSERT_TRUE(CreateView(vm1_.get(), BuildViewId(1, 1))); + ASSERT_NO_FATAL_FAILURE( + EstablishSecondConnectionWithRoot(BuildViewId(1, 1))); + + if (create_initial_view) + EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2())); + } + + void EstablishThirdConnection(ViewManagerService* owner, Id root_id) { + ASSERT_TRUE(vm_client3_.get() == nullptr); + vm_client3_ = EstablishConnectionViaEmbed(owner, root_id); + ASSERT_TRUE(vm_client3_.get() != nullptr); + } + + // Establishes a new connection by way of Embed() on the specified + // ViewManagerService. + scoped_ptr<ViewManagerClientImpl> EstablishConnectionViaEmbed( + ViewManagerService* owner, + Id root_id) { + if (!EmbedUrl(owner, application_impl()->url(), root_id)) { + ADD_FAILURE() << "Embed() failed"; + return nullptr; + } + scoped_ptr<ViewManagerClientImpl> client = + client_factory_.WaitForInstance(); + if (!client.get()) { + ADD_FAILURE() << "WaitForInstance failed"; + return nullptr; + } + client->WaitForOnEmbed(); + + const std::string expected_creator = + owner == vm1() ? "mojo:window_manager" : application_impl()->url(); + EXPECT_EQ("OnEmbed creator=" + expected_creator, + SingleChangeToDescription(*client->tracker()->changes())); + return client.Pass(); + } + + // ApplicationTestBase: + ApplicationDelegate* GetApplicationDelegate() override { return this; } + void SetUp() override { + ApplicationTestBase::SetUp(); + ApplicationConnection* vm_connection = + application_impl()->ConnectToApplication("mojo:view_manager"); + vm_connection->AddService(this); + vm_connection->ConnectToService(&vm1_); + vm_connection->ConnectToService(&wm_internal_client_); + // Spin a run loop until the view manager service sends us the + // ViewManagerClient pipe to use for the "window manager" connection. + view_manager_setup_run_loop_.reset(new base::RunLoop); + view_manager_setup_run_loop_->Run(); + view_manager_setup_run_loop_ = nullptr; + // Next we should get an embed call on the "window manager" client. + vm_client1_.WaitForIncomingMethodCall(); + ASSERT_EQ(1u, changes1()->size()); + EXPECT_EQ(CHANGE_TYPE_EMBED, (*changes1())[0].type); + // All these tests assume 1 for the client id. The only real assertion here + // is the client id is not zero, but adding this as rest of code here + // assumes 1. + ASSERT_EQ(1, (*changes1())[0].connection_id); + changes1()->clear(); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(&client_factory_); + return true; + } + + // mojo::InterfaceFactory<mojo::WindowManagerInternal> implementation. + void Create( + ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override { + DCHECK(!wm_internal_binding_.is_bound()); + wm_internal_binding_.Bind(request.Pass()); + } + + // mojo::WindowManagerInternal implementation. + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override {} + void SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) override { + auto typed_request = mojo::MakeRequest<mojo::ViewManagerClient>( + view_manager_client_request.Pass()); + vm_client1_.Bind(typed_request.Pass()); + view_manager_setup_run_loop_->Quit(); + } + + mojo::Binding<mojo::WindowManagerInternal> wm_internal_binding_; + mojo::WindowManagerInternalClientPtr wm_internal_client_; + ViewManagerClientImpl vm_client1_; + scoped_ptr<ViewManagerClientImpl> vm_client2_; + scoped_ptr<ViewManagerClientImpl> vm_client3_; + + private: + mojo::ViewManagerServicePtr vm1_; + ViewManagerClientFactory client_factory_; + scoped_ptr<base::RunLoop> view_manager_setup_run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceAppTest); +}; + +// Verifies two clients/connections get different ids. +TEST_F(ViewManagerServiceAppTest, TwoClientsGetDifferentConnectionIds) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // It isn't strictly necessary that the second connection gets 2, but these + // tests are written assuming that is the case. The key thing is the + // connection ids of |connection_| and |connection2_| differ. + ASSERT_EQ(1u, changes2()->size()); + ASSERT_EQ(2, (*changes2())[0].connection_id); +} + +// Verifies when Embed() is invoked any child views are removed. +TEST_F(ViewManagerServiceAppTest, ViewsRemovedWhenEmbedding) { + // Two views 1 and 2. 2 is parented to 1. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2())); + + // Embed() removed view 2. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 2), &views); + EXPECT_EQ("view=1,2 parent=null", SingleViewDescription(views)); + } + + // vm2 should not see view 2. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_EQ("view=1,1 parent=null", SingleViewDescription(views)); + } + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 2), &views); + EXPECT_TRUE(views.empty()); + } + + // Views 3 and 4 in connection 2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 4))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 3), BuildViewId(2, 4))); + + // Connection 3 rooted at 2. + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3))); + + // View 4 should no longer have a parent. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(2, 3), &views); + EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views)); + + views.clear(); + GetViewTree(vm2(), BuildViewId(2, 4), &views); + EXPECT_EQ("view=2,4 parent=null", SingleViewDescription(views)); + } + + // And view 4 should not be visible to connection 3. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(2, 3), &views); + EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views)); + } +} + +// Verifies once Embed() has been invoked the parent connection can't see any +// children. +TEST_F(ViewManagerServiceAppTest, CantAccessChildrenOfEmbeddedView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3))); + + // Even though 3 is a child of 2 connection 2 can't see 3 as it's from a + // different connection. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(2, 2), &views); + EXPECT_EQ("view=2,2 parent=1,1", SingleViewDescription(views)); + } + + // Connection 2 shouldn't be able to get view 3 at all. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(3, 3), &views); + EXPECT_TRUE(views.empty()); + } + + // Connection 1 should be able to see it all (its the root). + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString()); + EXPECT_EQ("view=3,3 parent=2,2", views[2].ToString()); + } +} + +// Verifies once Embed() has been invoked the parent can't mutate the children. +TEST_F(ViewManagerServiceAppTest, CantModifyChildrenOfEmbeddedView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + // Connection 2 shouldn't be able to add anything to the view anymore. + ASSERT_FALSE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + + // Create view 3 in connection 3 and add it to view 3. + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3))); + + // Connection 2 shouldn't be able to remove view 3. + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(3, 3))); +} + +// Verifies client gets a valid id. +TEST_F(ViewManagerServiceAppTest, CreateView) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a view with the same id. + ASSERT_EQ(mojo::ERROR_CODE_VALUE_IN_USE, + CreateViewWithErrorCode(vm1(), BuildViewId(1, 1))); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a view with a bogus connection id. + EXPECT_EQ(mojo::ERROR_CODE_ILLEGAL_ARGUMENT, + CreateViewWithErrorCode(vm1(), BuildViewId(2, 1))); + EXPECT_TRUE(changes1()->empty()); +} + +// Verifies AddView fails when view is already in position. +TEST_F(ViewManagerServiceAppTest, AddViewWithNoChange) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); + + // Try again, this should fail. + EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); +} + +// Verifies AddView fails when view is already in position. +TEST_F(ViewManagerServiceAppTest, AddAncestorFails) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); + + // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3. + EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 3), BuildViewId(1, 2))); +} + +// Verifies adding to root sends right notifications. +TEST_F(ViewManagerServiceAppTest, AddToRoot) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 21))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + // Make 3 a child of 21. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 21), BuildViewId(1, 3))); + + // Make 21 a child of 1. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 21))); + + // Connection 2 should not be told anything (because the view is from a + // different connection). Create a view to ensure we got a response from + // the server. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 100))); + EXPECT_TRUE(changes2()->empty()); +} + +// Verifies HierarchyChanged is correctly sent for various adds/removes. +TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedViews) { + // 1,2->1,11. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 11), true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 11))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + + ASSERT_TRUE(WaitForAllMessages(vm2())); + changes2()->clear(); + + // 1,1->1,2->1,11 + { + // Client 2 should not get anything (1,2 is from another connection). + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + ASSERT_TRUE(WaitForAllMessages(vm2())); + EXPECT_TRUE(changes2()->empty()); + } + + // 0,1->1,1->1,2->1,11. + { + // Client 2 is now connected to the root, so it should have gotten a drawn + // notification. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1u); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true", + SingleChangeToDescription(*changes2())); + } + + // 1,1->1,2->1,11. + { + // Client 2 is no longer connected to the root, should get drawn state + // changed. + changes2()->clear(); + ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=false", + SingleChangeToDescription(*changes2())); + } + + // 1,1->1,2->1,11->1,111. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 111))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 111), true)); + { + changes2()->clear(); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 11), BuildViewId(1, 111))); + ASSERT_TRUE(WaitForAllMessages(vm2())); + EXPECT_TRUE(changes2()->empty()); + } + + // 0,1->1,1->1,2->1,11->1,111 + { + changes2()->clear(); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedAddingKnownToUnknown) { + // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no + // parent). + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 21))); + + // Set up the hierarchy. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 11))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 21))); + + // Remove 11, should result in a hierarchy change for the root. + { + changes1()->clear(); + ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,11 new_parent=null old_parent=1,1", + SingleChangeToDescription(*changes1())); + } + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ( + "[view=2,2 parent=1,1]," + "[view=2,21 parent=2,2]", + ChangeViewDescription(*changes1())); + } +} + +TEST_F(ViewManagerServiceAppTest, ReorderView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + Id view1_id = BuildViewId(2, 1); + Id view2_id = BuildViewId(2, 2); + Id view3_id = BuildViewId(2, 3); + Id view4_id = BuildViewId(1, 4); // Peer to 1,1 + Id view5_id = BuildViewId(1, 5); // Peer to 1,1 + Id view6_id = BuildViewId(2, 6); // Child of 1,2. + Id view7_id = BuildViewId(2, 7); // Unparented. + Id view8_id = BuildViewId(2, 8); // Unparented. + ASSERT_TRUE(CreateView(vm2(), view1_id)); + ASSERT_TRUE(CreateView(vm2(), view2_id)); + ASSERT_TRUE(CreateView(vm2(), view3_id)); + ASSERT_TRUE(CreateView(vm1(), view4_id)); + ASSERT_TRUE(CreateView(vm1(), view5_id)); + ASSERT_TRUE(CreateView(vm2(), view6_id)); + ASSERT_TRUE(CreateView(vm2(), view7_id)); + ASSERT_TRUE(CreateView(vm2(), view8_id)); + ASSERT_TRUE(AddView(vm2(), view1_id, view2_id)); + ASSERT_TRUE(AddView(vm2(), view2_id, view6_id)); + ASSERT_TRUE(AddView(vm2(), view1_id, view3_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view4_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view5_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view1_id)); + + { + changes1()->clear(); + ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_ABOVE)); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=above", + SingleChangeToDescription(*changes1())); + } + + { + changes1()->clear(); + ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW)); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=below", + SingleChangeToDescription(*changes1())); + } + + // view2 is already below view3. + EXPECT_FALSE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW)); + + // view4 & 5 are unknown to connection2_. + EXPECT_FALSE(ReorderView(vm2(), view4_id, view5_id, ORDER_DIRECTION_ABOVE)); + + // view6 & view3 have different parents. + EXPECT_FALSE(ReorderView(vm1(), view3_id, view6_id, ORDER_DIRECTION_ABOVE)); + + // Non-existent view-ids + EXPECT_FALSE(ReorderView(vm1(), BuildViewId(1, 27), BuildViewId(1, 28), + ORDER_DIRECTION_ABOVE)); + + // view7 & view8 are un-parented. + EXPECT_FALSE(ReorderView(vm1(), view7_id, view8_id, ORDER_DIRECTION_ABOVE)); +} + +// Verifies DeleteView works. +TEST_F(ViewManagerServiceAppTest, DeleteView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + + // Make 2 a child of 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + changes2()->clear(); + ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2))); + EXPECT_TRUE(changes2()->empty()); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1())); + } +} + +// Verifies DeleteView isn't allowed from a separate connection. +TEST_F(ViewManagerServiceAppTest, DeleteViewFromAnotherConnectionDisallowed) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + EXPECT_FALSE(DeleteView(vm2(), BuildViewId(1, 1))); +} + +// Verifies if a view was deleted and then reused that other clients are +// properly notified. +TEST_F(ViewManagerServiceAppTest, ReuseDeletedViewId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1())); + } + + // Create 2 again, and add it back to 1. Should get the same notification. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1())); + } +} + +// Assertions for GetViewTree. +TEST_F(ViewManagerServiceAppTest, GetViewTree) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Create 11 in first connection and make it a child of 1. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 11))); + + // Create two views in second connection, 2 and 3, both children of 1. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 3))); + + // Verifies GetViewTree() on the root. The root connection sees all. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(5u, views.size()); + EXPECT_EQ("view=0,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString()); + EXPECT_EQ("view=1,11 parent=1,1", views[2].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[3].ToString()); + EXPECT_EQ("view=2,3 parent=1,1", views[4].ToString()); + } + + // Verifies GetViewTree() on the view 1,1 from vm2(). vm2() sees 1,1 as 1,1 + // is vm2()'s root and vm2() sees all the views it created. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString()); + EXPECT_EQ("view=2,3 parent=1,1", views[2].ToString()); + } + + // Connection 2 shouldn't be able to get the root tree. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(0, 1), &views); + ASSERT_EQ(0u, views.size()); + } +} + +TEST_F(ViewManagerServiceAppTest, SetViewBounds) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + changes2()->clear(); + ASSERT_TRUE(SetViewBounds(vm1(), BuildViewId(1, 1), 0, 0, 100, 100)); + + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("BoundsChanged view=1,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100", + SingleChangeToDescription(*changes2())); + + // Should not be possible to change the bounds of a view created by another + // connection. + ASSERT_FALSE(SetViewBounds(vm2(), BuildViewId(1, 1), 0, 0, 0, 0)); +} + +// Verify AddView fails when trying to manipulate views in other roots. +TEST_F(ViewManagerServiceAppTest, CantMoveViewsFromOtherRoot) { + // Create 1 and 2 in the first connection. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Try to move 2 to be a child of 1 from connection 2. This should fail as 2 + // should not be able to access 1. + ASSERT_FALSE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(1, 2))); + + // Try to reparent 1 to the root. A connection is not allowed to reparent its + // roots. + ASSERT_FALSE(AddView(vm2(), BuildViewId(0, 1), BuildViewId(1, 1))); +} + +// Verify RemoveViewFromParent fails for views that are descendants of the +// roots. +TEST_F(ViewManagerServiceAppTest, CantRemoveViewsInOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2))); + + // Establish the second connection and give it the root 1. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Connection 2 should not be able to remove view 2 or 1 from its parent. + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 2))); + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 1))); + + // Create views 10 and 11 in 2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 10))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11))); + + // Parent 11 to 10. + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 10), BuildViewId(2, 11))); + // Remove 11 from 10. + ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11))); + + // Verify nothing was actually removed. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=0,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString()); + EXPECT_EQ("view=1,2 parent=0,1", views[2].ToString()); + } +} + +// Verify GetViewTree fails for views that are not descendants of the roots. +TEST_F(ViewManagerServiceAppTest, CantGetViewTreeOfOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + std::vector<TestView> views; + + // Should get nothing for the root. + GetViewTree(vm2(), BuildViewId(0, 1), &views); + ASSERT_TRUE(views.empty()); + + // Should get nothing for view 2. + GetViewTree(vm2(), BuildViewId(1, 2), &views); + ASSERT_TRUE(views.empty()); + + // Should get view 1 if asked for. + GetViewTree(vm2(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); +} + +TEST_F(ViewManagerServiceAppTest, OnViewInputEvent) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + // Dispatch an event to the view and verify it's received. + { + EventPtr event(mojo::Event::New()); + event->action = static_cast<mojo::EventType>(1); + wm_internal_client_->DispatchInputEventToView(BuildViewId(1, 1), + event.Pass()); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("InputEvent view=1,1 event_action=1", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1))); + + // Connection2 should have been told the view was deleted. + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes2())); + } + + // Connection2 has no root. Verify it can't see view 1,1 anymore. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_TRUE(views.empty()); + } +} + +TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId2) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1))); + + // Connection2 should have been told the view was deleted. + vm_client2_->WaitForChangeCount(1); + changes2()->clear(); + + // Create a view in the third connection and parent it to the root. + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 1))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(1, 1), BuildViewId(3, 1))); + + // Connection 1 should have been told about the add (it owns the view). + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=3,1 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + } + + // Embed 1,1 again. + { + changes3()->clear(); + + // We should get a new connection for the new embedding. + scoped_ptr<ViewManagerClientImpl> connection4( + EstablishConnectionViaEmbed(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(connection4.get()); + EXPECT_EQ("[view=1,1 parent=null]", + ChangeViewDescription(*connection4->tracker()->changes())); + + // And 3 should get a delete. + vm_client3_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes3())); + } + + // vm3() has no root. Verify it can't see view 1,1 anymore. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(1, 1), &views); + EXPECT_TRUE(views.empty()); + } + + // Verify 3,1 is no longer parented to 1,1. We have to do this from 1,1 as + // vm3() can no longer see 1,1. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + } + + // Verify vm3() can still see the view it created 3,1. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(3, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=3,1 parent=null", views[0].ToString()); + } +} + +// Assertions for SetViewVisibility. +TEST_F(ViewManagerServiceAppTest, SetViewVisibility) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=0,1 parent=null visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[1].ToString2()); + } + + // Show all the views. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=0,1 parent=null visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true", + views[1].ToString2()); + } + + // Hide 1. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[0].ToString2()); + } + + // Attach 2 to 1. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[0].ToString2()); + EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=false", + views[1].ToString2()); + } + + // Show 1. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=true", + views[1].ToString2()); + } +} + +// Assertions for SetViewVisibility sending notifications. +TEST_F(ViewManagerServiceAppTest, SetViewVisibilityNotifications) { + // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + + // Establish the second connection at 1,2. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnectionWithRoot(BuildViewId(1, 2))); + + // Add 2,3 as a child of 1,2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), true)); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 2), BuildViewId(2, 3))); + WaitForAllMessages(vm1()); + + changes2()->clear(); + // Hide 1,2 from connection 1. Connection 2 should see this. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), false)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=1,2 visible=false", + SingleChangeToDescription(*changes2())); + } + + changes1()->clear(); + // Show 1,2 from connection 2, connection 1 should be notified. + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(1, 2), true)); + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=1,2 visible=true", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Hide 1,1, connection 2 should be told the draw state changed. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Show 1,1 from connection 1. Connection 2 should see this. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true", + SingleChangeToDescription(*changes2())); + } + + // Change visibility of 2,3, connection 1 should see this. + changes1()->clear(); + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), false)); + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=2,3 visible=false", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Remove 1,1 from the root, connection 2 should see drawn state changed. + ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1))); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Add 1,1 back to the root, connection 2 should see drawn state changed. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, SetViewProperty) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + changes2()->clear(); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ(BuildViewId(0, 1), views[0].view_id); + EXPECT_EQ(BuildViewId(1, 1), views[1].view_id); + ASSERT_EQ(0u, views[1].properties.size()); + } + + // Set properties on 1. + changes2()->clear(); + std::vector<uint8_t> one(1, '1'); + ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", &one)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("PropertyChanged view=1,1 key=one value=1", + SingleChangeToDescription(*changes2())); + } + + // Test that our properties exist in the view tree + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + ASSERT_EQ(1u, views[0].properties.size()); + EXPECT_EQ(one, views[0].properties["one"]); + } + + changes2()->clear(); + // Set back to null. + ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", NULL)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("PropertyChanged view=1,1 key=one value=NULL", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, OnEmbeddedAppDisconnected) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + // Close connection 3. Connection 2 (which had previously embedded 3) should + // be notified of this. + vm_client3_.reset(); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected view=2,2", + SingleChangeToDescription(*changes2())); +} + +// Verifies when the parent of an Embed() is destroyed the embedded app gets +// a ViewDeleted (and doesn't trigger a DCHECK). +TEST_F(ViewManagerServiceAppTest, OnParentOfEmbedDisconnects) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3))); + changes3()->clear(); + + // Close connection 2. Connection 3 should get a delete (for its root). + vm_client2_.reset(); + vm_client3_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,3", SingleChangeToDescription(*changes3())); +} + +// Verifies ViewManagerServiceImpl doesn't incorrectly erase from its internal +// map when a view from another connection with the same view_id is removed. +TEST_F(ViewManagerServiceAppTest, DontCleanMapOnDestroy) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 1))); + changes1()->clear(); + vm_client2_.reset(); + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected view=1,1", + SingleChangeToDescription(*changes1())); + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + EXPECT_FALSE(views.empty()); +} + +TEST_F(ViewManagerServiceAppTest, CloneAndAnimate) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + changes2()->clear(); + + ASSERT_TRUE(WaitForAllMessages(vm1())); + changes1()->clear(); + + wm_internal_client_->CloneAndAnimate(BuildViewId(2, 3)); + ASSERT_TRUE(WaitForAllMessages(vm1())); + + ASSERT_TRUE(WaitForAllMessages(vm1())); + ASSERT_TRUE(WaitForAllMessages(vm2())); + + // No messages should have been received. + EXPECT_TRUE(changes1()->empty()); + EXPECT_TRUE(changes2()->empty()); + + // No one should be able to see the cloned tree. + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + EXPECT_FALSE(HasClonedView(views)); + views.clear(); + + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_FALSE(HasClonedView(views)); +} + +// Verifies Embed() works when supplying a ViewManagerClient. +TEST_F(ViewManagerServiceAppTest, EmbedSupplyingViewManagerClient) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + + ViewManagerClientImpl client2; + mojo::ViewManagerClientPtr client2_ptr; + mojo::Binding<ViewManagerClient> client2_binding(&client2, &client2_ptr); + ASSERT_TRUE(Embed(vm1(), BuildViewId(1, 1), client2_ptr.Pass())); + client2.WaitForOnEmbed(); + EXPECT_EQ("OnEmbed creator=mojo:window_manager", + SingleChangeToDescription(*client2.tracker()->changes())); +} + +// TODO(sky): need to better track changes to initial connection. For example, +// that SetBounsdViews/AddView and the like don't result in messages to the +// originating connection. + +// TODO(sky): make sure coverage of what was +// ViewManagerTest.SecondEmbedRoot_InitService and +// ViewManagerTest.MultipleEmbedRootsBeforeWTHReady gets added to window manager +// tests. + +} // namespace view_manager diff --git a/mojo/services/view_manager/view_manager_service_impl.cc b/mojo/services/view_manager/view_manager_service_impl.cc new file mode 100644 index 0000000..3b045b9 --- /dev/null +++ b/mojo/services/view_manager/view_manager_service_impl.cc @@ -0,0 +1,654 @@ +// 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 "mojo/services/view_manager/view_manager_service_impl.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "mojo/services/view_manager/connection_manager.h" +#include "mojo/services/view_manager/default_access_policy.h" +#include "mojo/services/view_manager/display_manager.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/window_manager_access_policy.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +using mojo::Array; +using mojo::Callback; +using mojo::Id; +using mojo::InterfaceRequest; +using mojo::OrderDirection; +using mojo::Rect; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; + +namespace view_manager { + +ViewManagerServiceImpl::ViewManagerServiceImpl( + ConnectionManager* connection_manager, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) + : connection_manager_(connection_manager), + id_(connection_manager_->GetAndAdvanceNextConnectionId()), + url_(url), + creator_id_(creator_id), + creator_url_(creator_url), + client_(nullptr) { + CHECK(GetView(root_id)); + root_.reset(new ViewId(root_id)); + if (root_id == RootViewId()) + access_policy_.reset(new WindowManagerAccessPolicy(id_, this)); + else + access_policy_.reset(new DefaultAccessPolicy(id_, this)); +} + +ViewManagerServiceImpl::~ViewManagerServiceImpl() { + DestroyViews(); +} + +void ViewManagerServiceImpl::Init(mojo::ViewManagerClient* client, + mojo::ViewManagerServicePtr service_ptr, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + DCHECK(!client_); + client_ = client; + std::vector<const ServerView*> to_send; + if (root_.get()) + GetUnknownViewsFrom(GetView(*root_), &to_send); + + mojo::MessagePipe pipe; + connection_manager_->wm_internal()->CreateWindowManagerForViewManagerClient( + id_, pipe.handle1.Pass()); + client->OnEmbed(id_, creator_url_, ViewToViewData(to_send.front()), + service_ptr.Pass(), services.Pass(), exposed_services.Pass(), + pipe.handle0.Pass()); +} + +const ServerView* ViewManagerServiceImpl::GetView(const ViewId& id) const { + if (id_ == id.connection_id) { + ViewMap::const_iterator i = view_map_.find(id.view_id); + return i == view_map_.end() ? NULL : i->second; + } + return connection_manager_->GetView(id); +} + +bool ViewManagerServiceImpl::IsRoot(const ViewId& id) const { + return root_.get() && *root_ == id; +} + +void ViewManagerServiceImpl::OnWillDestroyViewManagerServiceImpl( + ViewManagerServiceImpl* connection) { + if (creator_id_ == connection->id()) + creator_id_ = kInvalidConnectionId; + if (connection->root_ && connection->root_->connection_id == id_ && + view_map_.count(connection->root_->view_id) > 0) { + client()->OnEmbeddedAppDisconnected( + ViewIdToTransportId(*connection->root_)); + } + if (root_.get() && root_->connection_id == connection->id()) + root_.reset(); +} + +mojo::ErrorCode ViewManagerServiceImpl::CreateView(const ViewId& view_id) { + if (view_id.connection_id != id_) + return mojo::ERROR_CODE_ILLEGAL_ARGUMENT; + if (view_map_.find(view_id.view_id) != view_map_.end()) + return mojo::ERROR_CODE_VALUE_IN_USE; + view_map_[view_id.view_id] = new ServerView(connection_manager_, view_id); + known_views_.insert(ViewIdToTransportId(view_id)); + return mojo::ERROR_CODE_NONE; +} + +bool ViewManagerServiceImpl::AddView(const ViewId& parent_id, + const ViewId& child_id) { + ServerView* parent = GetView(parent_id); + ServerView* child = GetView(child_id); + if (parent && child && child->parent() != parent && + !child->Contains(parent) && access_policy_->CanAddView(parent, child)) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + parent->Add(child); + return true; + } + return false; +} + +std::vector<const ServerView*> ViewManagerServiceImpl::GetViewTree( + const ViewId& view_id) const { + const ServerView* view = GetView(view_id); + std::vector<const ServerView*> views; + if (view) + GetViewTreeImpl(view, &views); + return views; +} + +bool ViewManagerServiceImpl::SetViewVisibility(const ViewId& view_id, + bool visible) { + ServerView* view = GetView(view_id); + if (!view || view->visible() == visible || + !access_policy_->CanChangeViewVisibility(view)) { + return false; + } + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->SetVisible(visible); + return true; +} + +bool ViewManagerServiceImpl::EmbedUrl( + const std::string& url, + const ViewId& view_id, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + if (!PrepareForEmbed(view_id)) + return false; + connection_manager_->EmbedAtView(id_, url, view_id, services.Pass(), + exposed_services.Pass()); + return true; +} + +bool ViewManagerServiceImpl::Embed(const ViewId& view_id, + mojo::ViewManagerClientPtr client) { + if (!client.get() || !PrepareForEmbed(view_id)) + return false; + connection_manager_->EmbedAtView(id_, view_id, client.Pass()); + return true; +} + +void ViewManagerServiceImpl::ProcessViewBoundsChanged( + const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change) { + if (originated_change || !IsViewKnown(view)) + return; + client()->OnViewBoundsChanged(ViewIdToTransportId(view->id()), + Rect::From(old_bounds), + Rect::From(new_bounds)); +} + +void ViewManagerServiceImpl::ProcessViewportMetricsChanged( + const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics, + bool originated_change) { + client()->OnViewViewportMetricsChanged(old_metrics.Clone(), + new_metrics.Clone()); +} + +void ViewManagerServiceImpl::ProcessWillChangeViewHierarchy( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change) { + if (originated_change) + return; + + const bool old_drawn = view->IsDrawn(connection_manager_->root()); + const bool new_drawn = view->visible() && new_parent && + new_parent->IsDrawn(connection_manager_->root()); + if (old_drawn == new_drawn) + return; + + NotifyDrawnStateChanged(view, new_drawn); +} + +void ViewManagerServiceImpl::ProcessViewPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change) { + if (originated_change) + return; + + Array<uint8_t> data; + if (new_data) + data = Array<uint8_t>::From(*new_data); + + client()->OnViewSharedPropertyChanged(ViewIdToTransportId(view->id()), + String(name), data.Pass()); +} + +void ViewManagerServiceImpl::ProcessViewHierarchyChanged( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change) { + if (originated_change && !IsViewKnown(view) && new_parent && + IsViewKnown(new_parent)) { + std::vector<const ServerView*> unused; + GetUnknownViewsFrom(view, &unused); + } + if (originated_change || connection_manager_->is_processing_delete_view() || + connection_manager_->DidConnectionMessageClient(id_)) { + return; + } + + if (!access_policy_->ShouldNotifyOnHierarchyChange( + view, &new_parent, &old_parent)) { + return; + } + // Inform the client of any new views and update the set of views we know + // about. + std::vector<const ServerView*> to_send; + if (!IsViewKnown(view)) + GetUnknownViewsFrom(view, &to_send); + const ViewId new_parent_id(new_parent ? new_parent->id() : ViewId()); + const ViewId old_parent_id(old_parent ? old_parent->id() : ViewId()); + client()->OnViewHierarchyChanged(ViewIdToTransportId(view->id()), + ViewIdToTransportId(new_parent_id), + ViewIdToTransportId(old_parent_id), + ViewsToViewDatas(to_send)); + connection_manager_->OnConnectionMessagedClient(id_); +} + +void ViewManagerServiceImpl::ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + OrderDirection direction, + bool originated_change) { + if (originated_change || !IsViewKnown(view) || !IsViewKnown(relative_view)) + return; + + client()->OnViewReordered(ViewIdToTransportId(view->id()), + ViewIdToTransportId(relative_view->id()), + direction); +} + +void ViewManagerServiceImpl::ProcessViewDeleted(const ViewId& view, + bool originated_change) { + if (view.connection_id == id_) + view_map_.erase(view.view_id); + + const bool in_known = known_views_.erase(ViewIdToTransportId(view)) > 0; + + if (IsRoot(view)) + root_.reset(); + + if (originated_change) + return; + + if (in_known) { + client()->OnViewDeleted(ViewIdToTransportId(view)); + connection_manager_->OnConnectionMessagedClient(id_); + } +} + +void ViewManagerServiceImpl::ProcessWillChangeViewVisibility( + const ServerView* view, + bool originated_change) { + if (originated_change) + return; + + if (IsViewKnown(view)) { + client()->OnViewVisibilityChanged(ViewIdToTransportId(view->id()), + !view->visible()); + return; + } + + bool view_target_drawn_state; + if (view->visible()) { + // View is being hidden, won't be drawn. + view_target_drawn_state = false; + } else { + // View is being shown. View will be drawn if its parent is drawn. + view_target_drawn_state = + view->parent() && view->parent()->IsDrawn(connection_manager_->root()); + } + + NotifyDrawnStateChanged(view, view_target_drawn_state); +} + +bool ViewManagerServiceImpl::IsViewKnown(const ServerView* view) const { + return known_views_.count(ViewIdToTransportId(view->id())) > 0; +} + +bool ViewManagerServiceImpl::CanReorderView(const ServerView* view, + const ServerView* relative_view, + OrderDirection direction) const { + if (!view || !relative_view) + return false; + + if (!view->parent() || view->parent() != relative_view->parent()) + return false; + + if (!access_policy_->CanReorderView(view, relative_view, direction)) + return false; + + std::vector<const ServerView*> children = view->parent()->GetChildren(); + const size_t child_i = + std::find(children.begin(), children.end(), view) - children.begin(); + const size_t target_i = + std::find(children.begin(), children.end(), relative_view) - + children.begin(); + if ((direction == mojo::ORDER_DIRECTION_ABOVE && child_i == target_i + 1) || + (direction == mojo::ORDER_DIRECTION_BELOW && child_i + 1 == target_i)) { + return false; + } + + return true; +} + +bool ViewManagerServiceImpl::DeleteViewImpl(ViewManagerServiceImpl* source, + ServerView* view) { + DCHECK(view); + DCHECK_EQ(view->id().connection_id, id_); + ConnectionManager::ScopedChange change(source, connection_manager_, true); + delete view; + return true; +} + +void ViewManagerServiceImpl::GetUnknownViewsFrom( + const ServerView* view, + std::vector<const ServerView*>* views) { + if (IsViewKnown(view) || !access_policy_->CanGetViewTree(view)) + return; + views->push_back(view); + known_views_.insert(ViewIdToTransportId(view->id())); + if (!access_policy_->CanDescendIntoViewForViewTree(view)) + return; + std::vector<const ServerView*> children(view->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetUnknownViewsFrom(children[i], views); +} + +void ViewManagerServiceImpl::RemoveFromKnown( + const ServerView* view, + std::vector<ServerView*>* local_views) { + if (view->id().connection_id == id_) { + if (local_views) + local_views->push_back(GetView(view->id())); + return; + } + known_views_.erase(ViewIdToTransportId(view->id())); + std::vector<const ServerView*> children = view->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + RemoveFromKnown(children[i], local_views); +} + +void ViewManagerServiceImpl::RemoveRoot() { + CHECK(root_.get()); + const ViewId root_id(*root_); + root_.reset(); + // No need to do anything if we created the view. + if (root_id.connection_id == id_) + return; + + client()->OnViewDeleted(ViewIdToTransportId(root_id)); + connection_manager_->OnConnectionMessagedClient(id_); + + // This connection no longer knows about the view. Unparent any views that + // were parented to views in the root. + std::vector<ServerView*> local_views; + RemoveFromKnown(GetView(root_id), &local_views); + for (size_t i = 0; i < local_views.size(); ++i) + local_views[i]->parent()->Remove(local_views[i]); +} + +void ViewManagerServiceImpl::RemoveChildrenAsPartOfEmbed( + const ViewId& view_id) { + ServerView* view = GetView(view_id); + CHECK(view); + CHECK(view->id().connection_id == view_id.connection_id); + std::vector<ServerView*> children = view->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + view->Remove(children[i]); +} + +Array<ViewDataPtr> ViewManagerServiceImpl::ViewsToViewDatas( + const std::vector<const ServerView*>& views) { + Array<ViewDataPtr> array(views.size()); + for (size_t i = 0; i < views.size(); ++i) + array[i] = ViewToViewData(views[i]).Pass(); + return array.Pass(); +} + +ViewDataPtr ViewManagerServiceImpl::ViewToViewData(const ServerView* view) { + DCHECK(IsViewKnown(view)); + const ServerView* parent = view->parent(); + // If the parent isn't known, it means the parent is not visible to us (not + // in roots), and should not be sent over. + if (parent && !IsViewKnown(parent)) + parent = NULL; + ViewDataPtr view_data(mojo::ViewData::New()); + view_data->parent_id = ViewIdToTransportId(parent ? parent->id() : ViewId()); + view_data->view_id = ViewIdToTransportId(view->id()); + view_data->bounds = Rect::From(view->bounds()); + view_data->properties = + mojo::Map<String, Array<uint8_t>>::From(view->properties()); + view_data->visible = view->visible(); + view_data->drawn = view->IsDrawn(connection_manager_->root()); + view_data->viewport_metrics = + connection_manager_->display_manager()->GetViewportMetrics().Clone(); + return view_data.Pass(); +} + +void ViewManagerServiceImpl::GetViewTreeImpl( + const ServerView* view, + std::vector<const ServerView*>* views) const { + DCHECK(view); + + if (!access_policy_->CanGetViewTree(view)) + return; + + views->push_back(view); + + if (!access_policy_->CanDescendIntoViewForViewTree(view)) + return; + + std::vector<const ServerView*> children(view->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetViewTreeImpl(children[i], views); +} + +void ViewManagerServiceImpl::NotifyDrawnStateChanged(const ServerView* view, + bool new_drawn_value) { + // Even though we don't know about view, it may be an ancestor of our root, in + // which case the change may effect our roots drawn state. + if (!root_.get()) + return; + + const ServerView* root = GetView(*root_); + DCHECK(root); + if (view->Contains(root) && + (new_drawn_value != root->IsDrawn(connection_manager_->root()))) { + client()->OnViewDrawnStateChanged(ViewIdToTransportId(root->id()), + new_drawn_value); + } +} + +void ViewManagerServiceImpl::DestroyViews() { + if (!view_map_.empty()) { + ConnectionManager::ScopedChange change(this, connection_manager_, true); + // If we get here from the destructor we're not going to get + // ProcessViewDeleted(). Copy the map and delete from the copy so that we + // don't have to worry about whether |view_map_| changes or not. + ViewMap view_map_copy; + view_map_.swap(view_map_copy); + STLDeleteValues(&view_map_copy); + } +} + +bool ViewManagerServiceImpl::PrepareForEmbed(const ViewId& view_id) { + const ServerView* view = GetView(view_id); + if (!view || !access_policy_->CanEmbed(view)) + return false; + + // Only allow a node to be the root for one connection. + ViewManagerServiceImpl* existing_owner = + connection_manager_->GetConnectionWithRoot(view_id); + + ConnectionManager::ScopedChange change(this, connection_manager_, true); + RemoveChildrenAsPartOfEmbed(view_id); + if (existing_owner) { + // Never message the originating connection. + connection_manager_->OnConnectionMessagedClient(id_); + existing_owner->RemoveRoot(); + } + return true; +} + +void ViewManagerServiceImpl::CreateView( + Id transport_view_id, + const Callback<void(mojo::ErrorCode)>& callback) { + callback.Run(CreateView(ViewIdFromTransportId(transport_view_id))); +} + +void ViewManagerServiceImpl::DeleteView( + Id transport_view_id, + const Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(transport_view_id)); + bool success = false; + if (view && access_policy_->CanDeleteView(view)) { + ViewManagerServiceImpl* connection = + connection_manager_->GetConnection(view->id().connection_id); + success = connection && connection->DeleteViewImpl(this, view); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::AddView( + Id parent_id, + Id child_id, + const Callback<void(bool)>& callback) { + callback.Run(AddView(ViewIdFromTransportId(parent_id), + ViewIdFromTransportId(child_id))); +} + +void ViewManagerServiceImpl::RemoveViewFromParent( + Id view_id, + const Callback<void(bool)>& callback) { + bool success = false; + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + if (view && view->parent() && access_policy_->CanRemoveViewFromParent(view)) { + success = true; + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->parent()->Remove(view); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::ReorderView(Id view_id, + Id relative_view_id, + OrderDirection direction, + const Callback<void(bool)>& callback) { + bool success = false; + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + ServerView* relative_view = GetView(ViewIdFromTransportId(relative_view_id)); + if (CanReorderView(view, relative_view, direction)) { + success = true; + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->parent()->Reorder(view, relative_view, direction); + connection_manager_->ProcessViewReorder(view, relative_view, direction); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::GetViewTree( + Id view_id, + const Callback<void(Array<ViewDataPtr>)>& callback) { + std::vector<const ServerView*> views( + GetViewTree(ViewIdFromTransportId(view_id))); + callback.Run(ViewsToViewDatas(views)); +} + +void ViewManagerServiceImpl::SetViewSurfaceId( + Id view_id, + mojo::SurfaceIdPtr surface_id, + const Callback<void(bool)>& callback) { + // TODO(sky): add coverage of not being able to set for random node. + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + if (!view || !access_policy_->CanSetViewSurfaceId(view)) { + callback.Run(false); + return; + } + view->SetSurfaceId(surface_id.To<cc::SurfaceId>()); + callback.Run(true); +} + +void ViewManagerServiceImpl::SetViewBounds( + Id view_id, + mojo::RectPtr bounds, + const Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + const bool success = view && access_policy_->CanSetViewBounds(view); + if (success) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->SetBounds(bounds.To<gfx::Rect>()); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::SetViewVisibility( + Id transport_view_id, + bool visible, + const Callback<void(bool)>& callback) { + callback.Run( + SetViewVisibility(ViewIdFromTransportId(transport_view_id), visible)); +} + +void ViewManagerServiceImpl::SetViewProperty( + uint32_t view_id, + const mojo::String& name, + mojo::Array<uint8_t> value, + const mojo::Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + const bool success = view && access_policy_->CanSetViewProperties(view); + if (success) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + + if (value.is_null()) { + view->SetProperty(name, nullptr); + } else { + std::vector<uint8_t> data = value.To<std::vector<uint8_t>>(); + view->SetProperty(name, &data); + } + } + callback.Run(success); +} + +void ViewManagerServiceImpl::EmbedUrl( + const String& url, + Id transport_view_id, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const Callback<void(bool)>& callback) { + callback.Run(EmbedUrl(url.To<std::string>(), + ViewIdFromTransportId(transport_view_id), + services.Pass(), exposed_services.Pass())); +} + +void ViewManagerServiceImpl::Embed(mojo::Id transport_view_id, + mojo::ViewManagerClientPtr client, + const mojo::Callback<void(bool)>& callback) { + callback.Run(Embed(ViewIdFromTransportId(transport_view_id), client.Pass())); +} + +void ViewManagerServiceImpl::PerformAction( + mojo::Id transport_view_id, + const mojo::String& action, + const mojo::Callback<void(bool)>& callback) { + connection_manager_->GetWindowManagerViewManagerClient()->OnPerformAction( + transport_view_id, action, callback); +} + +bool ViewManagerServiceImpl::IsRootForAccessPolicy(const ViewId& id) const { + return IsRoot(id); +} + +bool ViewManagerServiceImpl::IsViewKnownForAccessPolicy( + const ServerView* view) const { + return IsViewKnown(view); +} + +bool ViewManagerServiceImpl::IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const { + ViewManagerServiceImpl* connection = + connection_manager_->GetConnectionWithRoot(view->id()); + return connection && connection != this; +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/view_manager_service_impl.h b/mojo/services/view_manager/view_manager_service_impl.h new file mode 100644 index 0000000..bf156c3 --- /dev/null +++ b/mojo/services/view_manager/view_manager_service_impl.h @@ -0,0 +1,260 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ +#define SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/services/view_manager/access_policy_delegate.h" +#include "mojo/services/view_manager/ids.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surface_id.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace gfx { +class Rect; +} + +namespace view_manager { + +class AccessPolicy; +class ConnectionManager; +class ServerView; + +// An instance of ViewManagerServiceImpl is created for every ViewManagerService +// request. ViewManagerServiceImpl tracks all the state and views created by a +// client. ViewManagerServiceImpl coordinates with ConnectionManager to update +// the client (and internal state) as necessary. +class ViewManagerServiceImpl : public mojo::ViewManagerService, + public AccessPolicyDelegate { + public: + using ViewIdSet = base::hash_set<mojo::Id>; + + ViewManagerServiceImpl(ConnectionManager* connection_manager, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id); + ~ViewManagerServiceImpl() override; + + // |services| and |exposed_services| are the ServiceProviders to pass to the + // client via OnEmbed(). + void Init(mojo::ViewManagerClient* client, + mojo::ViewManagerServicePtr service_ptr, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + + mojo::ConnectionSpecificId id() const { return id_; } + mojo::ConnectionSpecificId creator_id() const { return creator_id_; } + const std::string& url() const { return url_; } + + mojo::ViewManagerClient* client() { return client_; } + + // Returns the View with the specified id. + ServerView* GetView(const ViewId& id) { + return const_cast<ServerView*>( + const_cast<const ViewManagerServiceImpl*>(this)->GetView(id)); + } + const ServerView* GetView(const ViewId& id) const; + + // Returns true if this connection's root is |id|. + bool IsRoot(const ViewId& id) const; + + // Returns the id of the root node. This is null if the root has been + // destroyed but the connection is still valid. + const ViewId* root() const { return root_.get(); } + + // Invoked when a connection is about to be destroyed. + void OnWillDestroyViewManagerServiceImpl(ViewManagerServiceImpl* connection); + + // These functions are synchronous variants of those defined in the mojom. The + // ViewManagerService implementations all call into these. See the mojom for + // details. + mojo::ErrorCode CreateView(const ViewId& view_id); + bool AddView(const ViewId& parent_id, const ViewId& child_id); + std::vector<const ServerView*> GetViewTree(const ViewId& view_id) const; + bool SetViewVisibility(const ViewId& view_id, bool visible); + bool EmbedUrl(const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + bool Embed(const ViewId& view_id, mojo::ViewManagerClientPtr client); + + // The following methods are invoked after the corresponding change has been + // processed. They do the appropriate bookkeeping and update the client as + // necessary. + void ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change); + void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics, + bool originated_change); + void ProcessWillChangeViewHierarchy(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change); + void ProcessViewPropertyChanged(const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change); + void ProcessViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change); + void ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction, + bool originated_change); + void ProcessViewDeleted(const ViewId& view, bool originated_change); + void ProcessWillChangeViewVisibility(const ServerView* view, + bool originated_change); + void ProcessViewPropertiesChanged(const ServerView* view, + bool originated_change); + + private: + typedef std::map<mojo::ConnectionSpecificId, ServerView*> ViewMap; + + bool IsViewKnown(const ServerView* view) const; + + // These functions return true if the corresponding mojom function is allowed + // for this connection. + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const; + + // Deletes a view owned by this connection. Returns true on success. |source| + // is the connection that originated the change. + bool DeleteViewImpl(ViewManagerServiceImpl* source, ServerView* view); + + // If |view| is known (in |known_views_|) does nothing. Otherwise adds |view| + // to |views|, marks |view| as known and recurses. + void GetUnknownViewsFrom(const ServerView* view, + std::vector<const ServerView*>* views); + + // Removes |view| and all its descendants from |known_views_|. This does not + // recurse through views that were created by this connection. All views owned + // by this connection are added to |local_views|. + void RemoveFromKnown(const ServerView* view, + std::vector<ServerView*>* local_views); + + // Resets the root of this connection. + void RemoveRoot(); + + void RemoveChildrenAsPartOfEmbed(const ViewId& view_id); + + // Converts View(s) to ViewData(s) for transport. This assumes all the views + // are valid for the client. The parent of views the client is not allowed to + // see are set to NULL (in the returned ViewData(s)). + mojo::Array<mojo::ViewDataPtr> ViewsToViewDatas( + const std::vector<const ServerView*>& views); + mojo::ViewDataPtr ViewToViewData(const ServerView* view); + + // Implementation of GetViewTree(). Adds |view| to |views| and recurses if + // CanDescendIntoViewForViewTree() returns true. + void GetViewTreeImpl(const ServerView* view, + std::vector<const ServerView*>* views) const; + + // Notify the client if the drawn state of any of the roots changes. + // |view| is the view that is changing to the drawn state |new_drawn_value|. + void NotifyDrawnStateChanged(const ServerView* view, bool new_drawn_value); + + // Deletes all Views we own. + void DestroyViews(); + + bool PrepareForEmbed(const ViewId& view_id); + + // ViewManagerService: + void CreateView( + mojo::Id transport_view_id, + const mojo::Callback<void(mojo::ErrorCode)>& callback) override; + void DeleteView(mojo::Id transport_view_id, + const mojo::Callback<void(bool)>& callback) override; + void AddView(mojo::Id parent_id, + mojo::Id child_id, + const mojo::Callback<void(bool)>& callback) override; + void RemoveViewFromParent( + mojo::Id view_id, + const mojo::Callback<void(bool)>& callback) override; + void ReorderView(mojo::Id view_id, + mojo::Id relative_view_id, + mojo::OrderDirection direction, + const mojo::Callback<void(bool)>& callback) override; + void GetViewTree(mojo::Id view_id, + const mojo::Callback<void(mojo::Array<mojo::ViewDataPtr>)>& + callback) override; + void SetViewSurfaceId(mojo::Id view_id, + mojo::SurfaceIdPtr surface_id, + const mojo::Callback<void(bool)>& callback) override; + void SetViewBounds(mojo::Id view_id, + mojo::RectPtr bounds, + const mojo::Callback<void(bool)>& callback) override; + void SetViewVisibility(mojo::Id view_id, + bool visible, + const mojo::Callback<void(bool)>& callback) override; + void SetViewProperty(mojo::Id view_id, + const mojo::String& name, + mojo::Array<uint8_t> value, + const mojo::Callback<void(bool)>& callback) override; + void EmbedUrl(const mojo::String& url, + mojo::Id transport_view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services, + const mojo::Callback<void(bool)>& callback) override; + void Embed(mojo::Id transport_view_id, + mojo::ViewManagerClientPtr client, + const mojo::Callback<void(bool)>& callback) override; + void PerformAction(mojo::Id transport_view_id, + const mojo::String& action, + const mojo::Callback<void(bool)>& callback) override; + + // AccessPolicyDelegate: + bool IsRootForAccessPolicy(const ViewId& id) const override; + bool IsViewKnownForAccessPolicy(const ServerView* view) const override; + bool IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const override; + + ConnectionManager* connection_manager_; + + // Id of this connection as assigned by ConnectionManager. + const mojo::ConnectionSpecificId id_; + + // URL this connection was created for. + const std::string url_; + + // ID of the connection that created us. If 0 it indicates either we were + // created by the root, or the connection that created us has been destroyed. + mojo::ConnectionSpecificId creator_id_; + + // The URL of the app that embedded the app this connection was created for. + // NOTE: this is empty if the connection was created by way of directly + // supplying the ViewManagerClient. + const std::string creator_url_; + + mojo::ViewManagerClient* client_; + + scoped_ptr<AccessPolicy> access_policy_; + + // The views created by this connection. This connection owns these objects. + ViewMap view_map_; + + // The set of views that has been communicated to the client. + ViewIdSet known_views_; + + // The root of this connection. This is a scoped_ptr to reinforce the + // connection may have no root. A connection has no root if either the root + // is destroyed or Embed() is invoked on the root. + scoped_ptr<ViewId> root_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceImpl); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ diff --git a/mojo/services/view_manager/view_manager_service_unittest.cc b/mojo/services/view_manager/view_manager_service_unittest.cc new file mode 100644 index 0000000..45527e9 --- /dev/null +++ b/mojo/services/view_manager/view_manager_service_unittest.cc @@ -0,0 +1,471 @@ +// 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 <string> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/services/view_manager/client_connection.h" +#include "mojo/services/view_manager/connection_manager.h" +#include "mojo/services/view_manager/connection_manager_delegate.h" +#include "mojo/services/view_manager/display_manager.h" +#include "mojo/services/view_manager/ids.h" +#include "mojo/services/view_manager/server_view.h" +#include "mojo/services/view_manager/test_change_tracker.h" +#include "mojo/services/view_manager/view_manager_service_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" +#include "ui/gfx/geometry/rect.h" + +using mojo::Array; +using mojo::ERROR_CODE_NONE; +using mojo::InterfaceRequest; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; + +namespace view_manager { +namespace { + +// ----------------------------------------------------------------------------- + +// ViewManagerClient implementation that logs all calls to a TestChangeTracker. +// TODO(sky): refactor so both this and ViewManagerServiceAppTest share code. +class TestViewManagerClient : public mojo::ViewManagerClient { + public: + TestViewManagerClient() {} + ~TestViewManagerClient() override {} + + TestChangeTracker* tracker() { return &tracker_; } + + private: + // ViewManagerClient: + void OnEmbed(uint16_t connection_id, + const String& embedder_url, + ViewDataPtr root, + mojo::ViewManagerServicePtr view_manager_service, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + mojo::ScopedMessagePipeHandle window_manager_pipe) override { + tracker_.OnEmbed(connection_id, embedder_url, root.Pass()); + } + void OnEmbeddedAppDisconnected(uint32_t view) override { + tracker_.OnEmbeddedAppDisconnected(view); + } + void OnViewBoundsChanged(uint32_t view, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds) override { + tracker_.OnViewBoundsChanged(view, old_bounds.Pass(), new_bounds.Pass()); + } + void OnViewViewportMetricsChanged( + mojo::ViewportMetricsPtr old_metrics, + mojo::ViewportMetricsPtr new_metrics) override { + tracker_.OnViewViewportMetricsChanged(old_metrics.Pass(), + new_metrics.Pass()); + } + void OnViewHierarchyChanged(uint32_t view, + uint32_t new_parent, + uint32_t old_parent, + Array<ViewDataPtr> views) override { + tracker_.OnViewHierarchyChanged(view, new_parent, old_parent, views.Pass()); + } + void OnViewReordered(uint32_t view_id, + uint32_t relative_view_id, + mojo::OrderDirection direction) override { + tracker_.OnViewReordered(view_id, relative_view_id, direction); + } + void OnViewDeleted(uint32_t view) override { tracker_.OnViewDeleted(view); } + void OnViewVisibilityChanged(uint32_t view, bool visible) override { + tracker_.OnViewVisibilityChanged(view, visible); + } + void OnViewDrawnStateChanged(uint32_t view, bool drawn) override { + tracker_.OnViewDrawnStateChanged(view, drawn); + } + void OnViewSharedPropertyChanged(uint32_t view, + const String& name, + Array<uint8_t> new_data) override { + tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass()); + } + void OnViewInputEvent(uint32_t view, + mojo::EventPtr event, + const mojo::Callback<void()>& callback) override { + tracker_.OnViewInputEvent(view, event.Pass()); + } + void OnPerformAction(uint32_t view_id, + const String& name, + const mojo::Callback<void(bool)>& callback) override {} + + TestChangeTracker tracker_; + + DISALLOW_COPY_AND_ASSIGN(TestViewManagerClient); +}; + +// ----------------------------------------------------------------------------- + +// ClientConnection implementation that vends TestViewManagerClient. +class TestClientConnection : public ClientConnection { + public: + explicit TestClientConnection(scoped_ptr<ViewManagerServiceImpl> service_impl) + : ClientConnection(service_impl.Pass(), &client_) {} + ~TestClientConnection() override {} + + TestViewManagerClient* client() { return &client_; } + + private: + TestViewManagerClient client_; + + DISALLOW_COPY_AND_ASSIGN(TestClientConnection); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of ConnectionManagerDelegate. +class TestConnectionManagerDelegate : public ConnectionManagerDelegate { + public: + TestConnectionManagerDelegate() : last_connection_(nullptr) {} + ~TestConnectionManagerDelegate() override {} + + TestViewManagerClient* last_client() { + return last_connection_ ? last_connection_->client() : nullptr; + } + + TestClientConnection* last_connection() { return last_connection_; } + + private: + // ConnectionManagerDelegate: + void OnLostConnectionToWindowManager() override {} + + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) override { + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, url, root_id)); + last_connection_ = new TestClientConnection(service.Pass()); + return last_connection_; + } + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr client) override { + NOTIMPLEMENTED(); + return nullptr; + } + + TestClientConnection* last_connection_; + + DISALLOW_COPY_AND_ASSIGN(TestConnectionManagerDelegate); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of DisplayManager. +class TestDisplayManager : public DisplayManager { + public: + TestDisplayManager() {} + ~TestDisplayManager() override {} + + // DisplayManager: + void Init(ConnectionManager* connection_manager) override {} + void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override { + } + void SetViewportSize(const gfx::Size& size) override {} + const mojo::ViewportMetrics& GetViewportMetrics() override { + return display_metrices_; + } + + private: + mojo::ViewportMetrics display_metrices_; + + DISALLOW_COPY_AND_ASSIGN(TestDisplayManager); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of WindowManagerInternal. +class TestWindowManagerInternal : public mojo::WindowManagerInternal { + public: + TestWindowManagerInternal() {} + ~TestWindowManagerInternal() override {} + + // WindowManagerInternal: + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override {} + void SetViewManagerClient(mojo::ScopedMessagePipeHandle) override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerInternal); +}; + +} // namespace + +// ----------------------------------------------------------------------------- + +class ViewManagerServiceTest : public testing::Test { + public: + ViewManagerServiceTest() : wm_client_(nullptr) {} + ~ViewManagerServiceTest() override {} + + // ViewManagerServiceImpl for the window manager. + ViewManagerServiceImpl* wm_connection() { + return connection_manager_->GetConnection(1); + } + + TestViewManagerClient* last_view_manager_client() { + return delegate_.last_client(); + } + + TestClientConnection* last_client_connection() { + return delegate_.last_connection(); + } + + ConnectionManager* connection_manager() { return connection_manager_.get(); } + + TestViewManagerClient* wm_client() { return wm_client_; } + + protected: + // testing::Test: + void SetUp() override { + connection_manager_.reset(new ConnectionManager( + &delegate_, scoped_ptr<DisplayManager>(new TestDisplayManager), + &wm_internal_)); + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager_.get(), kInvalidConnectionId, std::string(), + std::string("mojo:window_manager"), RootViewId())); + scoped_ptr<TestClientConnection> client_connection( + new TestClientConnection(service.Pass())); + wm_client_ = client_connection->client(); + ASSERT_TRUE(wm_client_ != nullptr); + connection_manager_->SetWindowManagerClientConnection( + client_connection.Pass()); + ASSERT_TRUE(wm_connection() != nullptr); + ASSERT_TRUE(wm_connection()->root() != nullptr); + } + + private: + // TestViewManagerClient that is used for the WM connection. + TestViewManagerClient* wm_client_; + + TestWindowManagerInternal wm_internal_; + TestConnectionManagerDelegate delegate_; + scoped_ptr<ConnectionManager> connection_manager_; + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceTest); +}; + +namespace { + +const ServerView* GetFirstCloned(const ServerView* view) { + for (const ServerView* child : view->GetChildren()) { + if (child->id() == ClonedViewId()) + return child; + } + return nullptr; +} + +// Provides common setup for animation tests. Creates the following views: +// 0,1 (the root, provided by view manager) +// 1,1 the second connection is embedded here (view owned by wm_connection()). +// 2,1 bounds=1,2 11x22 +// 2,2 bounds=2,3 6x7 +// 2,3 bounds=3,4 6x7 +// CloneAndAnimate() is invoked for 2,2. +void SetUpAnimate1(ViewManagerServiceTest* test, ViewId* embed_view_id) { + *embed_view_id = ViewId(test->wm_connection()->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, test->wm_connection()->CreateView(*embed_view_id)); + EXPECT_TRUE(test->wm_connection()->SetViewVisibility(*embed_view_id, true)); + EXPECT_TRUE(test->wm_connection()->AddView(*(test->wm_connection()->root()), + *embed_view_id)); + test->wm_connection()->EmbedUrl(std::string(), *embed_view_id, nullptr, + nullptr); + ViewManagerServiceImpl* connection1 = + test->connection_manager()->GetConnectionWithRoot(*embed_view_id); + ASSERT_TRUE(connection1 != nullptr); + ASSERT_NE(connection1, test->wm_connection()); + + const ViewId child1(connection1->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1)); + const ViewId child2(connection1->id(), 2); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2)); + const ViewId child3(connection1->id(), 3); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3)); + + ServerView* v1 = connection1->GetView(child1); + v1->SetVisible(true); + v1->SetBounds(gfx::Rect(1, 2, 11, 22)); + ServerView* v2 = connection1->GetView(child2); + v2->SetVisible(true); + v2->SetBounds(gfx::Rect(2, 3, 6, 7)); + ServerView* v3 = connection1->GetView(child3); + v3->SetVisible(true); + v3->SetBounds(gfx::Rect(3, 4, 6, 7)); + + EXPECT_TRUE(connection1->AddView(*embed_view_id, child1)); + EXPECT_TRUE(connection1->AddView(child1, child2)); + EXPECT_TRUE(connection1->AddView(child2, child3)); + + TestViewManagerClient* connection1_client = test->last_view_manager_client(); + connection1_client->tracker()->changes()->clear(); + test->wm_client()->tracker()->changes()->clear(); + EXPECT_TRUE(test->connection_manager()->CloneAndAnimate(child2)); + EXPECT_TRUE(connection1_client->tracker()->changes()->empty()); + EXPECT_TRUE(test->wm_client()->tracker()->changes()->empty()); + + // We cloned v2. The cloned view ends up as a sibling of it. + const ServerView* cloned_view = GetFirstCloned(connection1->GetView(child1)); + ASSERT_TRUE(cloned_view); + // |cloned_view| should have one and only one cloned child (corresponds to + // |child3|). + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + + // Cloned views should match the bounds of the view they were cloned from. + EXPECT_EQ(v2->bounds(), cloned_view->bounds()); + EXPECT_EQ(v3->bounds(), cloned_view->GetChildren()[0]->bounds()); + + // Cloned views are owned by the ConnectionManager and shouldn't be returned + // from ViewManagerServiceImpl::GetView. + EXPECT_TRUE(connection1->GetView(ClonedViewId()) == nullptr); + EXPECT_TRUE(test->wm_connection()->GetView(ClonedViewId()) == nullptr); +} + +} // namespace + +// Verifies ViewManagerService::GetViewTree() doesn't return cloned views. +TEST_F(ViewManagerServiceTest, ConnectionsCantSeeClonedViews) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + + const ViewId child1(connection1->id(), 1); + const ViewId child2(connection1->id(), 2); + const ViewId child3(connection1->id(), 3); + + // Verify the root doesn't see any cloned views. + std::vector<const ServerView*> views( + wm_connection()->GetViewTree(*wm_connection()->root())); + ASSERT_EQ(5u, views.size()); + ASSERT_TRUE(views[0]->id() == *wm_connection()->root()); + ASSERT_TRUE(views[1]->id() == embed_view_id); + ASSERT_TRUE(views[2]->id() == child1); + ASSERT_TRUE(views[3]->id() == child2); + ASSERT_TRUE(views[4]->id() == child3); + + // Verify connection1 doesn't see any cloned views. + std::vector<const ServerView*> v1_views( + connection1->GetViewTree(embed_view_id)); + ASSERT_EQ(4u, v1_views.size()); + ASSERT_TRUE(v1_views[0]->id() == embed_view_id); + ASSERT_TRUE(v1_views[1]->id() == child1); + ASSERT_TRUE(v1_views[2]->id() == child2); + ASSERT_TRUE(v1_views[3]->id() == child3); +} + +TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnConnectionClose) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + // Destroy connection1, which should force the cloned view to become a child + // of where it was embedded (the embedded view still exists). + connection_manager()->OnConnectionError(last_client_connection()); + + ServerView* embed_view = wm_connection()->GetView(embed_view_id); + ASSERT_TRUE(embed_view != nullptr); + const ServerView* cloned_view = GetFirstCloned(embed_view); + ASSERT_TRUE(cloned_view); + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + + // Because the cloned view changed parents its bounds should have changed. + EXPECT_EQ(gfx::Rect(3, 5, 6, 7), cloned_view->bounds()); + // The bounds of the cloned child should not have changed though. + EXPECT_EQ(gfx::Rect(3, 4, 6, 7), cloned_view->GetChildren()[0]->bounds()); +} + +TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnHide) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + + // Hide the parent of the cloned view, which should force the cloned view to + // become a sibling of the parent. + const ServerView* view_to_hide = + connection1->GetView(ViewId(connection1->id(), 1)); + ASSERT_TRUE(connection1->SetViewVisibility(view_to_hide->id(), false)); + + const ServerView* cloned_view = GetFirstCloned(view_to_hide->parent()); + ASSERT_TRUE(cloned_view); + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + EXPECT_EQ(2u, cloned_view->parent()->GetChildren().size()); + EXPECT_TRUE(cloned_view->parent()->GetChildren()[1] == cloned_view); +} + +// Clone and animate on a tree with more depth. Basically that of +// SetUpAnimate1() but cloning 2,1. +TEST_F(ViewManagerServiceTest, CloneAndAnimateLargerDepth) { + const ViewId embed_view_id(wm_connection()->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, wm_connection()->CreateView(embed_view_id)); + EXPECT_TRUE(wm_connection()->SetViewVisibility(embed_view_id, true)); + EXPECT_TRUE( + wm_connection()->AddView(*(wm_connection()->root()), embed_view_id)); + wm_connection()->EmbedUrl(std::string(), embed_view_id, nullptr, nullptr); + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + ASSERT_TRUE(connection1 != nullptr); + ASSERT_NE(connection1, wm_connection()); + + const ViewId child1(connection1->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1)); + const ViewId child2(connection1->id(), 2); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2)); + const ViewId child3(connection1->id(), 3); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3)); + + ServerView* v1 = connection1->GetView(child1); + v1->SetVisible(true); + connection1->GetView(child2)->SetVisible(true); + connection1->GetView(child3)->SetVisible(true); + + EXPECT_TRUE(connection1->AddView(embed_view_id, child1)); + EXPECT_TRUE(connection1->AddView(child1, child2)); + EXPECT_TRUE(connection1->AddView(child2, child3)); + + TestViewManagerClient* connection1_client = last_view_manager_client(); + connection1_client->tracker()->changes()->clear(); + wm_client()->tracker()->changes()->clear(); + EXPECT_TRUE(connection_manager()->CloneAndAnimate(child1)); + EXPECT_TRUE(connection1_client->tracker()->changes()->empty()); + EXPECT_TRUE(wm_client()->tracker()->changes()->empty()); + + // We cloned v1. The cloned view ends up as a sibling of it. + const ServerView* cloned_view = GetFirstCloned(v1->parent()); + ASSERT_TRUE(cloned_view); + // |cloned_view| should have a child and its child should have a child. + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + const ServerView* cloned_view_child = cloned_view->GetChildren()[0]; + EXPECT_EQ(1u, cloned_view_child->GetChildren().size()); + EXPECT_TRUE(cloned_view_child->id() == ClonedViewId()); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/window_manager_access_policy.cc b/mojo/services/view_manager/window_manager_access_policy.cc new file mode 100644 index 0000000..18696a0 --- /dev/null +++ b/mojo/services/view_manager/window_manager_access_policy.cc @@ -0,0 +1,97 @@ +// 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 "mojo/services/view_manager/window_manager_access_policy.h" + +#include "mojo/services/view_manager/access_policy_delegate.h" +#include "mojo/services/view_manager/server_view.h" + +namespace view_manager { + +// TODO(sky): document why this differs from default for each case. Maybe want +// to subclass DefaultAccessPolicy. + +WindowManagerAccessPolicy::WindowManagerAccessPolicy( + mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate) + : connection_id_(connection_id), delegate_(delegate) { +} + +WindowManagerAccessPolicy::~WindowManagerAccessPolicy() { +} + +bool WindowManagerAccessPolicy::CanRemoveViewFromParent( + const ServerView* view) const { + return true; +} + +bool WindowManagerAccessPolicy::CanAddView(const ServerView* parent, + const ServerView* child) const { + return true; +} + +bool WindowManagerAccessPolicy::CanReorderView( + const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const { + return true; +} + +bool WindowManagerAccessPolicy::CanDeleteView(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanGetViewTree(const ServerView* view) const { + return view->id() != ClonedViewId(); +} + +bool WindowManagerAccessPolicy::CanDescendIntoViewForViewTree( + const ServerView* view) const { + return view->id() != ClonedViewId(); +} + +bool WindowManagerAccessPolicy::CanEmbed(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanChangeViewVisibility( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanSetViewSurfaceId( + const ServerView* view) const { + if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) + return false; + return view->id().connection_id == connection_id_ || + (delegate_->IsRootForAccessPolicy(view->id())); +} + +bool WindowManagerAccessPolicy::CanSetViewBounds(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanSetViewProperties( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const { + if (view->id() == ClonedViewId()) + return false; + + // Notify if we've already told the window manager about the view, or if we've + // already told the window manager about the parent. The later handles the + // case of a view that wasn't parented to the root getting added to the root. + return IsViewKnown(view) || (*new_parent && IsViewKnown(*new_parent)); +} + +bool WindowManagerAccessPolicy::IsViewKnown(const ServerView* view) const { + return delegate_->IsViewKnownForAccessPolicy(view); +} + +} // namespace view_manager diff --git a/mojo/services/view_manager/window_manager_access_policy.h b/mojo/services/view_manager/window_manager_access_policy.h new file mode 100644 index 0000000..c1840ae --- /dev/null +++ b/mojo/services/view_manager/window_manager_access_policy.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ +#define SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ + +#include "base/basictypes.h" +#include "mojo/services/view_manager/access_policy.h" + +namespace view_manager { + +class AccessPolicyDelegate; + +class WindowManagerAccessPolicy : public AccessPolicy { + public: + WindowManagerAccessPolicy(mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate); + ~WindowManagerAccessPolicy() override; + + // AccessPolicy: + bool CanRemoveViewFromParent(const ServerView* view) const override; + bool CanAddView(const ServerView* parent, + const ServerView* child) const override; + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const override; + bool CanDeleteView(const ServerView* view) const override; + bool CanGetViewTree(const ServerView* view) const override; + bool CanDescendIntoViewForViewTree(const ServerView* view) const override; + bool CanEmbed(const ServerView* view) const override; + bool CanChangeViewVisibility(const ServerView* view) const override; + bool CanSetViewSurfaceId(const ServerView* view) const override; + bool CanSetViewBounds(const ServerView* view) const override; + bool CanSetViewProperties(const ServerView* view) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const override; + + private: + bool IsViewKnown(const ServerView* view) const; + + const mojo::ConnectionSpecificId connection_id_; + AccessPolicyDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerAccessPolicy); +}; + +} // namespace view_manager + +#endif // SERVICES_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ diff --git a/mojo/services/window_manager/BUILD.gn b/mojo/services/window_manager/BUILD.gn new file mode 100644 index 0000000..c34ebe1 --- /dev/null +++ b/mojo/services/window_manager/BUILD.gn @@ -0,0 +1,129 @@ +# 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. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//testing/test.gni") + +mojo_native_application("window_manager") { + sources = [ + "main.cc", + ] + + public_deps = [ + ":lib", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/common:tracing_impl", + "//third_party/mojo_services/src/view_manager/public/cpp", + ] +} + +source_set("lib") { + sources = [ + "basic_focus_rules.cc", + "basic_focus_rules.h", + "capture_controller.cc", + "capture_controller.h", + "capture_controller_observer.h", + "focus_controller.cc", + "focus_controller.h", + "focus_controller_observer.h", + "focus_rules.h", + "native_viewport_event_dispatcher_impl.cc", + "native_viewport_event_dispatcher_impl.h", + "view_event_dispatcher.cc", + "view_event_dispatcher.h", + "view_target.cc", + "view_target.h", + "view_targeter.cc", + "view_targeter.h", + "window_manager_app.cc", + "window_manager_app.h", + "window_manager_app_android.cc", + "window_manager_app_linux.cc", + "window_manager_app_win.cc", + "window_manager_delegate.h", + "window_manager_impl.cc", + "window_manager_impl.h", + ] + + deps = [ + "//base", + "//ui/base", + "//ui/events", + "//ui/gfx", + "//ui/gfx/geometry", + "//mojo/application", + "//mojo/common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} + +test("window_manager_unittests") { + sources = [ + "focus_controller_unittest.cc", + "run_all_unittests.cc", + "view_target_unittest.cc", + "view_targeter_unittest.cc", + "window_manager_api_unittest.cc", + "window_manager_test_util.cc", + "window_manager_test_util.h", + ] + + public_deps = [ + ":lib", + ] + + deps = [ + "//base/test:test_support", + "//mojo/converters/geometry", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//mojo/shell/application_manager", + "//mojo/shell:test_support", + "//testing/gtest", + "//ui/events:test_support", + "//ui/gfx", + "//ui/gfx:test_support", + "//ui/gl", + ] + + if (use_x11) { + deps += [ "//ui/gfx/x" ] + } +} + +mojo_native_application("window_manager_apptests") { + testonly = true + + sources = [ + "window_manager_apptest.cc", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/system:system", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] + + data_deps = [ ":window_manager($default_toolchain)" ] +} diff --git a/mojo/services/window_manager/DEPS b/mojo/services/window_manager/DEPS new file mode 100644 index 0000000..4a637c6 --- /dev/null +++ b/mojo/services/window_manager/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+mojo/application", + "+mojo/converters", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/services/window_manager/basic_focus_rules.cc b/mojo/services/window_manager/basic_focus_rules.cc new file mode 100644 index 0000000..5ba49af --- /dev/null +++ b/mojo/services/window_manager/basic_focus_rules.cc @@ -0,0 +1,171 @@ +// 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 "mojo/services/window_manager/basic_focus_rules.h" + +#include "base/macros.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +using mojo::View; + +namespace window_manager { + +BasicFocusRules::BasicFocusRules(View* window_container) + : window_container_(window_container) { +} + +BasicFocusRules::~BasicFocusRules() {} + +bool BasicFocusRules::SupportsChildActivation(View* view) const { + return true; +} + +bool BasicFocusRules::IsToplevelView(View* view) const { + if (!IsViewParentedToWindowContainer(view)) + return false; + + // The window must exist within a container that supports activation. + // The window cannot be blocked by a modal transient. + return SupportsChildActivation(view->parent()); +} + +bool BasicFocusRules::CanActivateView(View* view) const { + if (!view) + return true; + + // Only toplevel windows can be activated + if (!IsToplevelView(view)) + return false; + + // The view must be visible. + if (!view->visible()) + return false; + + // TODO(erg): The aura version of this class asks the aura::Window's + // ActivationDelegate whether the window is activatable. + + // A window must be focusable to be activatable. We don't call + // CanFocusWindow() from here because it will call back to us via + // GetActivatableWindow(). + if (!CanFocusViewImpl(view)) + return false; + + // TODO(erg): In the aura version, we also check whether the window is + // blocked by a modal transient window. + + return true; +} + +bool BasicFocusRules::CanFocusView(View* view) const { + // It is possible to focus a NULL window, it is equivalent to clearing focus. + if (!view) + return true; + + // The focused view is always inside the active view, so views that aren't + // activatable can't contain the focused view. + View* activatable = GetActivatableView(view); + if (!activatable || !activatable->Contains(view)) + return false; + return CanFocusViewImpl(view); +} + +View* BasicFocusRules::GetToplevelView(View* view) const { + View* parent = view->parent(); + View* child = view; + while (parent) { + if (IsToplevelView(child)) + return child; + + parent = parent->parent(); + child = child->parent(); + } + + return nullptr; +} + +View* BasicFocusRules::GetActivatableView(View* view) const { + View* parent = view->parent(); + View* child = view; + while (parent) { + if (CanActivateView(child)) + return child; + + // TODO(erg): In the aura version of this class, we have a whole bunch of + // checks to support modal transient windows, and transient parents. + + parent = parent->parent(); + child = child->parent(); + } + + return nullptr; +} + +View* BasicFocusRules::GetFocusableView(View* view) const { + if (CanFocusView(view)) + return view; + + // |view| may be in a hierarchy that is non-activatable, in which case we + // need to cut over to the activatable hierarchy. + View* activatable = GetActivatableView(view); + if (!activatable) { + // There may not be a related activatable hierarchy to cut over to, in which + // case we try an unrelated one. + View* toplevel = GetToplevelView(view); + if (toplevel) + activatable = GetNextActivatableView(toplevel); + if (!activatable) + return nullptr; + } + + if (!activatable->Contains(view)) { + // If there's already a child window focused in the activatable hierarchy, + // just use that (i.e. don't shift focus), otherwise we need to at least cut + // over to the activatable hierarchy. + View* focused = GetFocusableView(activatable); + return activatable->Contains(focused) ? focused : activatable; + } + + while (view && !CanFocusView(view)) + view = view->parent(); + return view; +} + +View* BasicFocusRules::GetNextActivatableView(View* activatable) const { + DCHECK(activatable); + + // In the basic scenarios handled by BasicFocusRules, the pool of activatable + // windows is limited to the |ignore|'s siblings. + const View::Children& siblings = activatable->parent()->children(); + DCHECK(!siblings.empty()); + + for (auto rit = siblings.rbegin(); rit != siblings.rend(); ++rit) { + View* cur = *rit; + if (cur == activatable) + continue; + if (CanActivateView(cur)) + return cur; + } + return nullptr; +} + +// TODO(erg): aura::Window::CanFocus() exists. View::CanFocus() does +// not. This is a hack that does everything that Window::CanFocus() currently +// does that doesn't require a delegate or an EventClient. +bool BasicFocusRules::CanFocusViewImpl(View* view) const { + // TODO(erg): In unit tests, views will never be drawn, so we can't rely on + // IsDrawn() here. + if (IsViewParentedToWindowContainer(view)) + return view->visible(); + + // TODO(erg): Add the intermediary delegate and event client checks once we + // have those. + + return CanFocusViewImpl(view->parent()); +} + +bool BasicFocusRules::IsViewParentedToWindowContainer(View* view) const { + return view->parent() == window_container_; +} + +} // namespace mojo diff --git a/mojo/services/window_manager/basic_focus_rules.h b/mojo/services/window_manager/basic_focus_rules.h new file mode 100644 index 0000000..d14a9b0d --- /dev/null +++ b/mojo/services/window_manager/basic_focus_rules.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ +#define SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ + +#include "mojo/services/window_manager/focus_rules.h" + +namespace mojo { +class View; +} + +namespace window_manager { + +// The focusing rules used inside a window manager. +// +// This is intended to be a user supplyable, subclassable component passed to +// WindowManagerApp, allowing for the creation of other window managers. +class BasicFocusRules : public FocusRules { + public: + BasicFocusRules(mojo::View* window_container); + ~BasicFocusRules() override; + + protected: + // Overridden from mojo::FocusRules: + bool SupportsChildActivation(mojo::View* view) const override; + bool IsToplevelView(mojo::View* view) const override; + bool CanActivateView(mojo::View* view) const override; + bool CanFocusView(mojo::View* view) const override; + mojo::View* GetToplevelView(mojo::View* view) const override; + mojo::View* GetActivatableView(mojo::View* view) const override; + mojo::View* GetFocusableView(mojo::View* view) const override; + mojo::View* GetNextActivatableView(mojo::View* activatable) const override; + + private: + bool CanFocusViewImpl(mojo::View* view) const; + + // Tests to see if |view| is in |window_container_|. + bool IsViewParentedToWindowContainer(mojo::View* view) const; + + mojo::View* window_container_; + + DISALLOW_COPY_AND_ASSIGN(BasicFocusRules); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ diff --git a/mojo/services/window_manager/capture_controller.cc b/mojo/services/window_manager/capture_controller.cc new file mode 100644 index 0000000..ad6891c --- /dev/null +++ b/mojo/services/window_manager/capture_controller.cc @@ -0,0 +1,103 @@ +// 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 "mojo/services/window_manager/capture_controller.h" + +#include "mojo/services/window_manager/capture_controller_observer.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h" + +DECLARE_VIEW_PROPERTY_TYPE(window_manager::CaptureController*); + +namespace window_manager { + +namespace { +DEFINE_VIEW_PROPERTY_KEY(CaptureController*, + kRootViewCaptureController, + nullptr); +} // namespace + +CaptureController::CaptureController() + : capture_view_(nullptr) {} + +CaptureController::~CaptureController() {} + +void CaptureController::AddObserver(CaptureControllerObserver* observer) { + capture_controller_observers_.AddObserver(observer); +} + +void CaptureController::RemoveObserver(CaptureControllerObserver* observer) { + capture_controller_observers_.RemoveObserver(observer); +} + +void CaptureController::SetCapture(mojo::View* view) { + if (capture_view_ == view) + return; + + if (capture_view_) + capture_view_->RemoveObserver(this); + + mojo::View* old_capture_view = capture_view_; + capture_view_ = view; + + if (capture_view_) + capture_view_->AddObserver(this); + + NotifyCaptureChange(capture_view_, old_capture_view); +} + +void CaptureController::ReleaseCapture(mojo::View* view) { + if (capture_view_ != view) + return; + SetCapture(nullptr); +} + +mojo::View* CaptureController::GetCapture() { + return capture_view_; +} + +void CaptureController::NotifyCaptureChange(mojo::View* new_capture, + mojo::View* old_capture) { + mojo::ViewTracker view_tracker; + if (new_capture) + view_tracker.Add(new_capture); + if (old_capture) + view_tracker.Add(old_capture); + + FOR_EACH_OBSERVER( + CaptureControllerObserver, capture_controller_observers_, + OnCaptureChanged(view_tracker.Contains(new_capture) ? new_capture + : nullptr)); +} + +void CaptureController::OnViewDestroying(mojo::View* view) { + if (view == capture_view_) { + view->RemoveObserver(this); + NotifyCaptureChange(nullptr, view); + capture_view_ = nullptr; + } +} + +void SetCaptureController(mojo::View* root_view, + CaptureController* capture_controller) { + DCHECK_EQ(root_view->GetRoot(), root_view); + root_view->SetLocalProperty(kRootViewCaptureController, capture_controller); +} + +CaptureController* GetCaptureController(mojo::View* root_view) { + if (root_view) + DCHECK_EQ(root_view->GetRoot(), root_view); + return root_view ? + root_view->GetLocalProperty(kRootViewCaptureController) : nullptr; +} + +mojo::View* GetCaptureView(mojo::View* view) { + mojo::View* root = view->GetRoot(); + if (!root) + return nullptr; + CaptureController* controller = GetCaptureController(root); + return controller ? controller->GetCapture() : nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/capture_controller.h b/mojo/services/window_manager/capture_controller.h new file mode 100644 index 0000000..64a3339 --- /dev/null +++ b/mojo/services/window_manager/capture_controller.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ +#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ + +#include "base/observer_list.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" + +namespace window_manager { + +class CaptureControllerObserver; + +// Manages input capture. A view which has capture will take all input events. +class CaptureController : public mojo::ViewObserver { + public: + CaptureController(); + ~CaptureController() override; + + void AddObserver(CaptureControllerObserver* observer); + void RemoveObserver(CaptureControllerObserver* observer); + + void SetCapture(mojo::View* view); + void ReleaseCapture(mojo::View* view); + mojo::View* GetCapture(); + + private: + void NotifyCaptureChange(mojo::View* new_capture, mojo::View* old_capture); + + // Overridden from ViewObserver: + void OnViewDestroying(mojo::View* view) override; + + // The current capture view. Null if there is no capture view. + mojo::View* capture_view_; + + ObserverList<CaptureControllerObserver> capture_controller_observers_; + + DISALLOW_COPY_AND_ASSIGN(CaptureController); +}; + +void SetCaptureController(mojo::View* view, + CaptureController* capture_controller); +CaptureController* GetCaptureController(mojo::View* view); +mojo::View* GetCaptureView(mojo::View* view); + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ diff --git a/mojo/services/window_manager/capture_controller_observer.h b/mojo/services/window_manager/capture_controller_observer.h new file mode 100644 index 0000000..fc62d60 --- /dev/null +++ b/mojo/services/window_manager/capture_controller_observer.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ +#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ + +namespace window_manager { + +class CaptureControllerObserver { + public: + virtual void OnCaptureChanged(mojo::View* gained_capture) = 0; + + protected: + virtual ~CaptureControllerObserver() {} +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ diff --git a/mojo/services/window_manager/focus_controller.cc b/mojo/services/window_manager/focus_controller.cc new file mode 100644 index 0000000..25d60d6 --- /dev/null +++ b/mojo/services/window_manager/focus_controller.cc @@ -0,0 +1,317 @@ +// 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 "mojo/services/window_manager/focus_controller.h" + +#include "base/auto_reset.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/focus_rules.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h" +#include "ui/events/event.h" + +DECLARE_VIEW_PROPERTY_TYPE(window_manager::FocusController*); + +using mojo::View; + +namespace window_manager { + +namespace { +DEFINE_VIEW_PROPERTY_KEY(FocusController*, kRootViewFocusController, nullptr); +} // namespace + +FocusController::FocusController(scoped_ptr<FocusRules> rules) + : active_view_(nullptr), + focused_view_(nullptr), + updating_focus_(false), + updating_activation_(false), + rules_(rules.Pass()), + observer_manager_(this) { + DCHECK(rules_); +} + +FocusController::~FocusController() {} + +void FocusController::AddObserver(FocusControllerObserver* observer) { + focus_controller_observers_.AddObserver(observer); +} + +void FocusController::RemoveObserver(FocusControllerObserver* observer) { + focus_controller_observers_.RemoveObserver(observer); +} + +void FocusController::ActivateView(View* view) { + FocusView(view); +} + +void FocusController::DeactivateView(View* view) { + if (view) + FocusView(rules_->GetNextActivatableView(view)); +} + +View* FocusController::GetActiveView() { + return active_view_; +} + +View* FocusController::GetActivatableView(View* view) { + return rules_->GetActivatableView(view); +} + +View* FocusController::GetToplevelView(View* view) { + return rules_->GetToplevelView(view); +} + +bool FocusController::CanActivateView(View* view) const { + return rules_->CanActivateView(view); +} + +void FocusController::FocusView(View* view) { + if (view && + (view->Contains(focused_view_) || view->Contains(active_view_))) { + return; + } + + // Focusing a window also activates its containing activatable window. Note + // that the rules could redirect activation activation and/or focus. + View* focusable = rules_->GetFocusableView(view); + View* activatable = + focusable ? rules_->GetActivatableView(focusable) : nullptr; + + // We need valid focusable/activatable windows in the event we're not clearing + // focus. "Clearing focus" is inferred by whether or not |window| passed to + // this function is non-null. + if (view && (!focusable || !activatable)) + return; + DCHECK((focusable && activatable) || !view); + + // Activation change observers may change the focused window. If this happens + // we must not adjust the focus below since this will clobber that change. + View* last_focused_view = focused_view_; + if (!updating_activation_) + SetActiveView(view, activatable); + + // If the window's ActivationChangeObserver shifted focus to a valid window, + // we don't want to focus the window we thought would be focused by default. + bool activation_changed_focus = last_focused_view != focused_view_; + if (!updating_focus_ && (!activation_changed_focus || !focused_view_)) { + if (active_view_ && focusable) + DCHECK(active_view_->Contains(focusable)); + SetFocusedView(focusable); + } +} + +void FocusController::ResetFocusWithinActiveView(View* view) { + DCHECK(view); + if (!active_view_) + return; + if (!active_view_->Contains(view)) + return; + SetFocusedView(view); +} + +View* FocusController::GetFocusedView() { + return focused_view_; +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, ui::EventHandler implementation: + +void FocusController::OnKeyEvent(ui::KeyEvent* event) { +} + +void FocusController::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +void FocusController::OnScrollEvent(ui::ScrollEvent* event) { +} + +void FocusController::OnTouchEvent(ui::TouchEvent* event) { + if (event->type() == ui::ET_TOUCH_PRESSED && !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +void FocusController::OnGestureEvent(ui::GestureEvent* event) { + if (event->type() == ui::ET_GESTURE_BEGIN && + event->details().touch_points() == 1 && + !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, mojo::ViewObserver implementation: + +void FocusController::OnViewVisibilityChanged(View* view) { + bool visible = view->visible(); + if (!visible) + ViewLostFocusFromDispositionChange(view, view->parent()); +} + +void FocusController::OnViewDestroying(View* view) { + ViewLostFocusFromDispositionChange(view, view->parent()); +} + +void FocusController::OnTreeChanging(const TreeChangeParams& params) { + // TODO(erg): In the aura version, you could get into a situation where you + // have different focus clients, so you had to check for that. Does that + // happen here? Could we get away with not checking if it does? + if (params.receiver == active_view_ && + params.target->Contains(params.receiver) && + (!params.new_parent || + /* different_focus_clients */ false)) { + ViewLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +void FocusController::OnTreeChanged(const TreeChangeParams& params) { + // TODO(erg): Same as Changing version. + if (params.receiver == focused_view_ && + params.target->Contains(params.receiver) && + (!params.new_parent || + /* different_focus_clients */ false)) { + ViewLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, private: + +void FocusController::SetFocusedView(View* view) { + if (updating_focus_ || view == focused_view_) + return; + DCHECK(rules_->CanFocusView(view)); + if (view) + DCHECK_EQ(view, rules_->GetFocusableView(view)); + + base::AutoReset<bool> updating_focus(&updating_focus_, true); + View* lost_focus = focused_view_; + + // TODO(erg): In the aura version, we reset the text input client here. Do + // that if we bring in something like the TextInputClient. + + // Allow for the window losing focus to be deleted during dispatch. If it is + // deleted pass null to observers instead of a deleted window. + mojo::ViewTracker view_tracker; + if (lost_focus) + view_tracker.Add(lost_focus); + if (focused_view_ && observer_manager_.IsObserving(focused_view_) && + focused_view_ != active_view_) { + observer_manager_.Remove(focused_view_); + } + focused_view_ = view; + if (focused_view_ && !observer_manager_.IsObserving(focused_view_)) + observer_manager_.Add(focused_view_); + + FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_, + OnFocused(focused_view_)); + + // TODO(erg): In aura, there's a concept of a single FocusChangeObserver that + // is attached to an aura::Window. We don't currently have this in + // mojo::View, but if we add it later, we should make something analogous + // here. + + // TODO(erg): In the aura version, we reset the TextInputClient here, too. +} + +void FocusController::SetActiveView(View* requested_view, View* view) { + if (updating_activation_) + return; + + if (view == active_view_) { + if (requested_view) { + FOR_EACH_OBSERVER(FocusControllerObserver, + focus_controller_observers_, + OnAttemptToReactivateView(requested_view, + active_view_)); + } + return; + } + + DCHECK(rules_->CanActivateView(view)); + if (view) + DCHECK_EQ(view, rules_->GetActivatableView(view)); + + base::AutoReset<bool> updating_activation(&updating_activation_, true); + View* lost_activation = active_view_; + // Allow for the window losing activation to be deleted during dispatch. If + // it is deleted pass null to observers instead of a deleted window. + mojo::ViewTracker view_tracker; + if (lost_activation) + view_tracker.Add(lost_activation); + if (active_view_ && observer_manager_.IsObserving(active_view_) && + focused_view_ != active_view_) { + observer_manager_.Remove(active_view_); + } + active_view_ = view; + if (active_view_ && !observer_manager_.IsObserving(active_view_)) + observer_manager_.Add(active_view_); + + if (active_view_) { + // TODO(erg): Reenable this when we have modal windows. + // StackTransientParentsBelowModalWindow(active_view_); + + active_view_->MoveToFront(); + } + + // TODO(erg): Individual windows can have a single ActivationChangeObserver + // set on them. In the aura version of this code, it sends an + // OnWindowActivated message to both the window that lost activation, and the + // window that gained it. + + FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_, + OnActivated(active_view_)); +} + +void FocusController::ViewLostFocusFromDispositionChange( + View* view, + View* next) { + // TODO(erg): We clear the modality state here in the aura::Window version of + // this class, and should probably do the same once we have modal windows. + + // TODO(beng): See if this function can be replaced by a call to + // FocusWindow(). + // Activation adjustments are handled first in the event of a disposition + // changed. If an activation change is necessary, focus is reset as part of + // that process so there's no point in updating focus independently. + if (view == active_view_) { + View* next_activatable = rules_->GetNextActivatableView(view); + SetActiveView(nullptr, next_activatable); + if (!(active_view_ && active_view_->Contains(focused_view_))) + SetFocusedView(next_activatable); + } else if (view->Contains(focused_view_)) { + // Active window isn't changing, but focused window might be. + SetFocusedView(rules_->GetFocusableView(next)); + } +} + +void FocusController::ViewFocusedFromInputEvent(View* view) { + // Only focus |window| if it or any of its parents can be focused. Otherwise + // FocusWindow() will focus the topmost window, which may not be the + // currently focused one. + if (rules_->CanFocusView(GetToplevelView(view))) + FocusView(view); +} + +void SetFocusController(View* root_view, FocusController* focus_controller) { + DCHECK_EQ(root_view->GetRoot(), root_view); + root_view->SetLocalProperty(kRootViewFocusController, focus_controller); +} + +FocusController* GetFocusController(View* root_view) { + if (root_view) + DCHECK_EQ(root_view->GetRoot(), root_view); + return root_view ? + root_view->GetLocalProperty(kRootViewFocusController) : nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/focus_controller.h b/mojo/services/window_manager/focus_controller.h new file mode 100644 index 0000000..8a9e8ef --- /dev/null +++ b/mojo/services/window_manager/focus_controller.h @@ -0,0 +1,102 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/scoped_observer.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" +#include "ui/events/event_handler.h" + +namespace window_manager { + +class FocusControllerObserver; +class FocusRules; + +// FocusController handles focus and activation changes in a mojo window +// manager. Within the window manager, there can only be one focused and one +// active window at a time. When focus or activation changes, notifications are +// sent using the FocusControllerObserver interface. +class FocusController : public ui::EventHandler, public mojo::ViewObserver { + public: + // |rules| cannot be null. + explicit FocusController(scoped_ptr<FocusRules> rules); + ~FocusController() override; + + void AddObserver(FocusControllerObserver* observer); + void RemoveObserver(FocusControllerObserver* observer); + + void ActivateView(mojo::View* view); + void DeactivateView(mojo::View* view); + mojo::View* GetActiveView(); + mojo::View* GetActivatableView(mojo::View* view); + mojo::View* GetToplevelView(mojo::View* view); + bool CanActivateView(mojo::View* view) const; + + void FocusView(mojo::View* view); + + void ResetFocusWithinActiveView(mojo::View* view); + mojo::View* GetFocusedView(); + + // Overridden from ui::EventHandler: + void OnKeyEvent(ui::KeyEvent* event) override; + void OnMouseEvent(ui::MouseEvent* event) override; + void OnScrollEvent(ui::ScrollEvent* event) override; + void OnTouchEvent(ui::TouchEvent* event) override; + void OnGestureEvent(ui::GestureEvent* event) override; + + // Overridden from ViewObserver: + void OnTreeChanging(const TreeChangeParams& params) override; + void OnTreeChanged(const TreeChangeParams& params) override; + void OnViewVisibilityChanged(mojo::View* view) override; + void OnViewDestroying(mojo::View* view) override; + + private: + // Internal implementation that sets the focused view, fires events etc. + // This function must be called with a valid focusable view. + void SetFocusedView(mojo::View* view); + + // Internal implementation that sets the active window, fires events etc. + // This function must be called with a valid |activatable_window|. + // |requested window| refers to the window that was passed in to an external + // request (e.g. FocusWindow or ActivateWindow). It may be null, e.g. if + // SetActiveWindow was not called by an external request. |activatable_window| + // refers to the actual window to be activated, which may be different. + void SetActiveView(mojo::View* requested_view, mojo::View* activatable_view); + + // Called when a window's disposition changed such that it and its hierarchy + // are no longer focusable/activatable. |next| is a valid window that is used + // as a starting point for finding a window to focus next based on rules. + void ViewLostFocusFromDispositionChange(mojo::View* view, mojo::View* next); + + // Called when an attempt is made to focus or activate a window via an input + // event targeted at that window. Rules determine the best focusable window + // for the input window. + void ViewFocusedFromInputEvent(mojo::View* view); + + mojo::View* active_view_; + mojo::View* focused_view_; + + bool updating_focus_; + bool updating_activation_; + + scoped_ptr<FocusRules> rules_; + + ObserverList<FocusControllerObserver> focus_controller_observers_; + + ScopedObserver<mojo::View, ViewObserver> observer_manager_; + + DISALLOW_COPY_AND_ASSIGN(FocusController); +}; + +// Sets/Gets the focus controller for a view. +void SetFocusController(mojo::View* view, FocusController* focus_controller); +FocusController* GetFocusController(mojo::View* view); + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ diff --git a/mojo/services/window_manager/focus_controller_observer.h b/mojo/services/window_manager/focus_controller_observer.h new file mode 100644 index 0000000..c5960b0 --- /dev/null +++ b/mojo/services/window_manager/focus_controller_observer.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ + +namespace mojo { +class View; +} + +namespace window_manager { + +class FocusControllerObserver { + public: + // Called when |active| gains focus, or there is no active view + // (|active| is null in this case.). + virtual void OnActivated(mojo::View* gained_active) = 0; + + // Called when focus moves to |gained_focus|. + virtual void OnFocused(mojo::View* gained_focus) = 0; + + // Called when during view activation the currently active view is + // selected for activation. This can happen when a view requested for + // activation cannot be activated because a system modal view is active. + virtual void OnAttemptToReactivateView(mojo::View* request_active, + mojo::View* actual_active) {} + + protected: + virtual ~FocusControllerObserver() {} +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ diff --git a/mojo/services/window_manager/focus_controller_unittest.cc b/mojo/services/window_manager/focus_controller_unittest.cc new file mode 100644 index 0000000..ea60ac7 --- /dev/null +++ b/mojo/services/window_manager/focus_controller_unittest.cc @@ -0,0 +1,1197 @@ +// Copyright (c) 2012 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 "mojo/services/window_manager/focus_controller.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/geometry/rect.h" + +using mojo::View; + +namespace window_manager { + +// Counts the number of events that occur. +class FocusNotificationObserver : public FocusControllerObserver { + public: + FocusNotificationObserver() + : activation_changed_count_(0), + focus_changed_count_(0), + reactivation_count_(0), + reactivation_requested_view_(NULL), + reactivation_actual_view_(NULL) {} + ~FocusNotificationObserver() override {} + + void ExpectCounts(int activation_changed_count, int focus_changed_count) { + EXPECT_EQ(activation_changed_count, activation_changed_count_); + EXPECT_EQ(focus_changed_count, focus_changed_count_); + } + int reactivation_count() const { return reactivation_count_; } + View* reactivation_requested_view() const { + return reactivation_requested_view_; + } + View* reactivation_actual_view() const { + return reactivation_actual_view_; + } + + protected: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + ++activation_changed_count_; + } + + void OnFocused(View* gained_focus) override { ++focus_changed_count_; } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + ++reactivation_count_; + reactivation_requested_view_ = request_active; + reactivation_actual_view_ = actual_active; + } + + private: + int activation_changed_count_; + int focus_changed_count_; + int reactivation_count_; + View* reactivation_requested_view_; + View* reactivation_actual_view_; + + DISALLOW_COPY_AND_ASSIGN(FocusNotificationObserver); +}; + +class ViewDestroyer { + public: + virtual View* GetDestroyedView() = 0; + + protected: + virtual ~ViewDestroyer() {} +}; + +// FocusNotificationObserver that keeps track of whether it was notified about +// activation changes or focus changes with a destroyed view. +class RecordingFocusNotificationObserver : public FocusNotificationObserver { + public: + RecordingFocusNotificationObserver(FocusController* focus_controller, + ViewDestroyer* destroyer) + : focus_controller_(focus_controller), + destroyer_(destroyer), + active_(nullptr), + focus_(nullptr), + was_notified_with_destroyed_view_(false) { + focus_controller_->AddObserver(this); + } + ~RecordingFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + bool was_notified_with_destroyed_view() const { + return was_notified_with_destroyed_view_; + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (active_ && active_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (focus_ && focus_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + focus_ = gained_focus; + } + + private: + FocusController* focus_controller_; + + // Not owned. + ViewDestroyer* destroyer_; + View* active_; + View* focus_; + + // Whether the observer was notified about the loss of activation or the + // loss of focus with a view already destroyed by |destroyer_| as the + // |lost_active| or |lost_focus| parameter. + bool was_notified_with_destroyed_view_; + + DISALLOW_COPY_AND_ASSIGN(RecordingFocusNotificationObserver); +}; + +class DestroyOnLoseActivationFocusNotificationObserver + : public FocusNotificationObserver, + public ViewDestroyer { + public: + DestroyOnLoseActivationFocusNotificationObserver( + FocusController* focus_controller, + View* view_to_destroy, + View* initial_active) + : focus_controller_(focus_controller), + view_to_destroy_(view_to_destroy), + active_(initial_active), + did_destroy_(false) { + focus_controller_->AddObserver(this); + } + ~DestroyOnLoseActivationFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (view_to_destroy_ && active_ == view_to_destroy_) { + view_to_destroy_->Destroy(); + did_destroy_ = true; + } + active_ = gained_active; + } + + // Overridden from ViewDestroyer: + View* GetDestroyedView() override { + return did_destroy_ ? view_to_destroy_ : nullptr; + } + + private: + FocusController* focus_controller_; + View* view_to_destroy_; + View* active_; + bool did_destroy_; + + DISALLOW_COPY_AND_ASSIGN(DestroyOnLoseActivationFocusNotificationObserver); +}; + +class ScopedFocusNotificationObserver : public FocusNotificationObserver { + public: + ScopedFocusNotificationObserver(FocusController* focus_controller) + : focus_controller_(focus_controller) { + focus_controller_->AddObserver(this); + } + ~ScopedFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + FocusController* focus_controller_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFocusNotificationObserver); +}; + +// Only responds to events if a message contains |target| as a parameter. +class ScopedFilteringFocusNotificationObserver + : public FocusNotificationObserver { + public: + ScopedFilteringFocusNotificationObserver(FocusController* focus_controller, + View* target, + View* initial_active, + View* initial_focus) + : focus_controller_(focus_controller), + target_(target), + active_(initial_active), + focus_(initial_focus) { + focus_controller_->AddObserver(this); + } + ~ScopedFilteringFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + if (gained_active == target_ || active_ == target_) + FocusNotificationObserver::OnActivated(gained_active); + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (gained_focus == target_ || focus_ == target_) + FocusNotificationObserver::OnFocused(gained_focus); + focus_ = gained_focus; + } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + if (request_active == target_ || actual_active == target_) { + FocusNotificationObserver::OnAttemptToReactivateView(request_active, + actual_active); + } + } + + FocusController* focus_controller_; + View* target_; + View* active_; + View* focus_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFilteringFocusNotificationObserver); +}; + +// Used to fake the handling of events in the pre-target phase. +class SimpleEventHandler : public ui::EventHandler { + public: + SimpleEventHandler() {} + ~SimpleEventHandler() override {} + + // Overridden from ui::EventHandler: + void OnMouseEvent(ui::MouseEvent* event) override { + event->SetHandled(); + } + void OnGestureEvent(ui::GestureEvent* event) override { + event->SetHandled(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleEventHandler); +}; + +class FocusShiftingActivationObserver + : public FocusControllerObserver { + public: + explicit FocusShiftingActivationObserver(FocusController* focus_controller, + View* activated_view) + : focus_controller_(focus_controller), + activated_view_(activated_view), + shift_focus_to_(NULL) {} + ~FocusShiftingActivationObserver() override {} + + void set_shift_focus_to(View* shift_focus_to) { + shift_focus_to_ = shift_focus_to; + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + // Shift focus to a child. This should prevent the default focusing from + // occurring in FocusController::FocusView(). + if (gained_active == activated_view_) + focus_controller_->FocusView(shift_focus_to_); + } + + void OnFocused(View* gained_focus) override {} + + FocusController* focus_controller_; + View* activated_view_; + View* shift_focus_to_; + + DISALLOW_COPY_AND_ASSIGN(FocusShiftingActivationObserver); +}; + +// BasicFocusRules subclass that allows basic overrides of focus/activation to +// be tested. This is intended more as a test that the override system works at +// all, rather than as an exhaustive set of use cases, those should be covered +// in tests for those FocusRules implementations. +class TestFocusRules : public BasicFocusRules { + public: + TestFocusRules(View* root) + : BasicFocusRules(root), focus_restriction_(NULL) {} + + // Restricts focus and activation to this view and its child hierarchy. + void set_focus_restriction(View* focus_restriction) { + focus_restriction_ = focus_restriction; + } + + // Overridden from BasicFocusRules: + bool SupportsChildActivation(View* view) const override { + // In FocusControllerTests, only the Root has activatable children. + return view->GetRoot() == view; + } + bool CanActivateView(View* view) const override { + // Restricting focus to a non-activatable child view means the activatable + // parent outside the focus restriction is activatable. + bool can_activate = + CanFocusOrActivate(view) || view->Contains(focus_restriction_); + return can_activate ? BasicFocusRules::CanActivateView(view) : false; + } + bool CanFocusView(View* view) const override { + return CanFocusOrActivate(view) ? BasicFocusRules::CanFocusView(view) + : false; + } + View* GetActivatableView(View* view) const override { + return BasicFocusRules::GetActivatableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetFocusableView(View* view) const override { + return BasicFocusRules::GetFocusableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetNextActivatableView(View* ignore) const override { + View* next_activatable = BasicFocusRules::GetNextActivatableView(ignore); + return CanFocusOrActivate(next_activatable) + ? next_activatable + : GetActivatableView(focus_restriction_); + } + + private: + bool CanFocusOrActivate(View* view) const { + return !focus_restriction_ || focus_restriction_->Contains(view); + } + + View* focus_restriction_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusRules); +}; + +// Common infrastructure shared by all FocusController test types. +class FocusControllerTestBase : public testing::Test { + protected: + // Hierarchy used by all tests: + // root_view + // +-- w1 + // | +-- w11 + // | +-- w12 + // +-- w2 + // | +-- w21 + // | +-- w211 + // +-- w3 + FocusControllerTestBase() + : root_view_(TestView::Build(0, gfx::Rect(0, 0, 800, 600))), + v1(TestView::Build(1, gfx::Rect(0, 0, 50, 50), root_view())), + v11(TestView::Build(11, gfx::Rect(5, 5, 10, 10), v1)), + v12(TestView::Build(12, gfx::Rect(15, 15, 10, 10), v1)), + v2(TestView::Build(2, gfx::Rect(75, 75, 50, 50), root_view())), + v21(TestView::Build(21, gfx::Rect(5, 5, 10, 10), v2)), + v211(TestView::Build(211, gfx::Rect(1, 1, 5, 5), v21)), + v3(TestView::Build(3, gfx::Rect(125, 125, 50, 50), root_view())) {} + + // Overridden from testing::Test: + void SetUp() override { + testing::Test::SetUp(); + + test_focus_rules_ = new TestFocusRules(root_view()); + focus_controller_.reset( + new FocusController(scoped_ptr<FocusRules>(test_focus_rules_))); + SetFocusController(root_view(), focus_controller_.get()); + + capture_controller_.reset(new CaptureController); + SetCaptureController(root_view(), capture_controller_.get()); + + ViewTarget* root_target = root_view_->target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_.reset(new ViewEventDispatcher()); + view_event_dispatcher_->SetRootViewTarget(root_target); + + GetRootViewTarget()->AddPreTargetHandler(focus_controller_.get()); + } + + void TearDown() override { + GetRootViewTarget()->RemovePreTargetHandler(focus_controller_.get()); + view_event_dispatcher_.reset(); + + root_view_->Destroy(); + + capture_controller_.reset(); + test_focus_rules_ = nullptr; // Owned by FocusController. + focus_controller_.reset(); + + testing::Test::TearDown(); + } + + void FocusView(View* view) { focus_controller_->FocusView(view); } + View* GetFocusedView() { return focus_controller_->GetFocusedView(); } + int GetFocusedViewId() { + View* focused_view = GetFocusedView(); + return focused_view ? focused_view->id() : -1; + } + void ActivateView(View* view) { focus_controller_->ActivateView(view); } + void DeactivateView(View* view) { focus_controller_->DeactivateView(view); } + View* GetActiveView() { return focus_controller_->GetActiveView(); } + int GetActiveViewId() { + View* active_view = GetActiveView(); + return active_view ? active_view->id() : -1; + } + + View* GetViewById(int id) { return root_view_->GetChildById(id); } + + void ClickLeftButton(View* view) { + // Get the center bounds of |target| in |root_view_| coordinate space. + gfx::Point center = + gfx::Rect(view->bounds().To<gfx::Rect>().size()).CenterPoint(); + ViewTarget::ConvertPointToTarget(ViewTarget::TargetFromView(view), + root_view_->target(), ¢er); + + ui::MouseEvent button_down(ui::ET_MOUSE_PRESSED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&button_down); + CHECK(!details.dispatcher_destroyed); + + ui::MouseEvent button_up(ui::ET_MOUSE_RELEASED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + details = view_event_dispatcher_->OnEventFromSource(&button_up); + CHECK(!details.dispatcher_destroyed); + } + + ViewTarget* GetRootViewTarget() { + return ViewTarget::TargetFromView(root_view()); + } + + View* root_view() { return root_view_; } + TestFocusRules* test_focus_rules() { return test_focus_rules_; } + FocusController* focus_controller() { return focus_controller_.get(); } + CaptureController* capture_controller() { return capture_controller_.get(); } + + // Test functions. + virtual void BasicFocus() = 0; + virtual void BasicActivation() = 0; + virtual void FocusEvents() = 0; + virtual void DuplicateFocusEvents() {} + virtual void ActivationEvents() = 0; + virtual void ReactivationEvents() {} + virtual void DuplicateActivationEvents() {} + virtual void ShiftFocusWithinActiveView() {} + virtual void ShiftFocusToChildOfInactiveView() {} + virtual void ShiftFocusToParentOfFocusedView() {} + virtual void FocusRulesOverride() = 0; + virtual void ActivationRulesOverride() = 0; + virtual void ShiftFocusOnActivation() {} + virtual void ShiftFocusOnActivationDueToHide() {} + virtual void NoShiftActiveOnActivation() {} + virtual void ChangeFocusWhenNothingFocusedAndCaptured() {} + virtual void DontPassDestroyedView() {} + // TODO(erg): Also, void FocusedTextInputClient() once we build the IME. + + private: + TestView* root_view_; + scoped_ptr<FocusController> focus_controller_; + TestFocusRules* test_focus_rules_; + scoped_ptr<CaptureController> capture_controller_; + // TODO(erg): The aura version of this class also keeps track of WMState. Do + // we need something analogous here? + + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + TestView* v1; + TestView* v11; + TestView* v12; + TestView* v2; + TestView* v21; + TestView* v211; + TestView* v3; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerTestBase); +}; + +// Test base for tests where focus is directly set to a target view. +class FocusControllerDirectTestBase : public FocusControllerTestBase { + protected: + FocusControllerDirectTestBase() {} + + // Different test types shift focus in different ways. + virtual void FocusViewDirect(View* view) = 0; + virtual void ActivateViewDirect(View* view) = 0; + virtual void DeactivateViewDirect(View* view) = 0; + + // Input events do not change focus if the view can not be focused. + virtual bool IsInputEvent() = 0; + + void FocusViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + FocusViewDirect(view); + } + void ActivateViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + ActivateViewDirect(view); + } + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(nullptr, GetFocusedView()); + FocusViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + } + void BasicActivation() override { + EXPECT_EQ(nullptr, GetActiveView()); + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + // Verify that attempting to deactivate NULL does not crash and does not + // change activation. + DeactivateView(nullptr); + EXPECT_EQ(2, GetActiveViewId()); + DeactivateView(GetActiveView()); + EXPECT_EQ(1, GetActiveViewId()); + } + void FocusEvents() override { + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + { + SCOPED_TRACE("initial state"); + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(1); + { + SCOPED_TRACE("FocusViewById(1)"); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(2); + { + SCOPED_TRACE("FocusViewById(2)"); + root_observer.ExpectCounts(2, 2); + observer1.ExpectCounts(2, 2); + observer2.ExpectCounts(1, 1); + } + } + void DuplicateFocusEvents() override { + // Focusing an existing focused view should not resend focus events. + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + } + void ActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ReactivationEvents() override { + ActivateViewById(1); + ScopedFocusNotificationObserver root_observer(focus_controller()); + EXPECT_EQ(0, root_observer.reactivation_count()); + GetViewById(2)->SetVisible(false); + // When we attempt to activate "2", which cannot be activated because it + // is not visible, "1" will be reactivated. + ActivateViewById(2); + EXPECT_EQ(1, root_observer.reactivation_count()); + EXPECT_EQ(GetViewById(2), + root_observer.reactivation_requested_view()); + EXPECT_EQ(GetViewById(1), + root_observer.reactivation_actual_view()); + } + void DuplicateActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + + // Activating an existing active view should not resend activation events. + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ShiftFocusWithinActiveView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ShiftFocusToChildOfInactiveView() override { + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(11, GetFocusedViewId()); + } + void ShiftFocusToParentOfFocusedView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(1); + // Focus should _not_ shift to the parent of the already-focused view. + EXPECT_EQ(11, GetFocusedViewId()); + } + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(GetViewById(211)); + FocusViewById(12); + // Input events leave focus unchanged; direct API calls will change focus + // to the restricted view. + int focused_view = IsInputEvent() ? 11 : 211; + EXPECT_EQ(focused_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ActivationRulesOverride() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* v3 = GetViewById(3); + test_focus_rules()->set_focus_restriction(v3); + + ActivateViewById(2); + // Input events leave activation unchanged; direct API calls will activate + // the restricted view. + int active_view = IsInputEvent() ? 1 : 3; + EXPECT_EQ(active_view, GetActiveViewId()); + EXPECT_EQ(active_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + void ShiftFocusOnActivation() override { + // When a view is activated, by default that view is also focused. + // An ActivationChangeObserver may shift focus to another view within the + // same activatable view. + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + ActivateViewById(2); + + View* target = GetViewById(1); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(11)); + focus_controller()->AddObserver(observer.get()); + + ActivateViewById(1); + + // w1's ActivationChangeObserver shifted focus to this child, pre-empting + // FocusController's default setting. + EXPECT_EQ(11, GetFocusedViewId()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + + // Simulate a focus reset by the ActivationChangeObserver. This should + // trigger the default setting in FocusController. + observer->set_shift_focus_to(nullptr); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + } + void ShiftFocusOnActivationDueToHide() override { + // Similar to ShiftFocusOnActivation except the activation change is + // triggered by hiding the active view. + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + // Removes view 3 as candidate for next activatable view. + root_view()->GetChildById(3)->SetVisible(false); + EXPECT_EQ(1, GetFocusedViewId()); + + View* target = root_view()->GetChildById(2); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(21)); + focus_controller()->AddObserver(observer.get()); + + // Hide the active view. + root_view()->GetChildById(1)->SetVisible(false); + + EXPECT_EQ(21, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + } + void NoShiftActiveOnActivation() override { + // When a view is activated, we need to prevent any change to activation + // from being made in response to an activation change notification. + } + + // Verifies focus change is honored while capture held. + void ChangeFocusWhenNothingFocusedAndCaptured() override { + View* v1 = root_view()->GetChildById(1); + capture_controller()->SetCapture(v1); + + EXPECT_EQ(-1, GetActiveViewId()); + EXPECT_EQ(-1, GetFocusedViewId()); + + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + capture_controller()->ReleaseCapture(v1); + } + + // Verifies if a view that loses activation or focus is destroyed during + // observer notification we don't pass the destroyed view to other observers. + void DontPassDestroyedView() override { + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + { + View* to_destroy = root_view()->GetChildById(1); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(2); + + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + + { + View* to_destroy = root_view()->GetChildById(2); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(3); + + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDirectTestBase); +}; + +// Focus and Activation changes via the FocusController API. +class FocusControllerApiTest : public FocusControllerDirectTestBase { + public: + FocusControllerApiTest() {} + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { FocusView(view); } + void ActivateViewDirect(View* view) override { ActivateView(view); } + void DeactivateViewDirect(View* view) override { DeactivateView(view); } + bool IsInputEvent() override { return false; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerApiTest); +}; + +// Focus and Activation changes via input events. +class FocusControllerMouseEventTest : public FocusControllerDirectTestBase { + public: + FocusControllerMouseEventTest() {} + + // Tests that a handled mouse event does not trigger a view activation. + void IgnoreHandledEvent() { + EXPECT_EQ(NULL, GetActiveView()); + View* v1 = root_view()->GetChildById(1); + SimpleEventHandler handler; + GetRootViewTarget()->PrependPreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(NULL, GetActiveView()); + // TODO(erg): Add gesture testing when we get gestures working. + GetRootViewTarget()->RemovePreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(1, GetActiveViewId()); + } + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { ClickLeftButton(view); } + void ActivateViewDirect(View* view) override { ClickLeftButton(view); } + void DeactivateViewDirect(View* view) override { + View* next_activatable = test_focus_rules()->GetNextActivatableView(view); + ClickLeftButton(next_activatable); + } + bool IsInputEvent() override { return true; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest); +}; + +// TODO(erg): Add a FocusControllerGestureEventTest once we have working +// gesture forwarding and handling. + +// Test base for tests where focus is implicitly set to a window as the result +// of a disposition change to the focused window or the hierarchy that contains +// it. +class FocusControllerImplicitTestBase : public FocusControllerTestBase { + protected: + explicit FocusControllerImplicitTestBase(bool parent) : parent_(parent) {} + + View* GetDispositionView(View* view) { + return parent_ ? view->parent() : view; + } + + // Change the disposition of |view| in such a way as it will lose focus. + virtual void ChangeViewDisposition(View* view) = 0; + + // Allow each disposition change test to add additional post-disposition + // change expectations. + virtual void PostDispostionChangeExpectations() {} + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(NULL, GetFocusedView()); + + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + ChangeViewDisposition(w211); + // BasicFocusRules passes focus to the parent. + EXPECT_EQ(parent_ ? 2 : 21, GetFocusedViewId()); + } + + void BasicActivation() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + EXPECT_EQ(NULL, GetActiveView()); + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + EXPECT_EQ(2, GetActiveViewId()); + + ChangeViewDisposition(w2); + EXPECT_EQ(3, GetActiveViewId()); + PostDispostionChangeExpectations(); + } + + void FocusEvents() override { + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer211( + focus_controller(), GetViewById(211), GetActiveView(), + GetFocusedView()); + + { + SCOPED_TRACE("first"); + root_observer.ExpectCounts(0, 0); + observer211.ExpectCounts(0, 0); + } + + ChangeViewDisposition(w211); + { + SCOPED_TRACE("second"); + { + SCOPED_TRACE("root_observer"); + root_observer.ExpectCounts(0, 1); + } + { + SCOPED_TRACE("observer211"); + observer211.ExpectCounts(0, 1); + } + } + } + + void ActivationEvents() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer3( + focus_controller(), GetViewById(3), GetActiveView(), GetFocusedView()); + root_observer.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + observer3.ExpectCounts(0, 0); + + ChangeViewDisposition(w2); + root_observer.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + observer3.ExpectCounts(1, 1); + } + + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(root_view()->GetChildById(11)); + ChangeViewDisposition(w211); + // Normally, focus would shift to the parent (w21) but the override shifts + // it to 11. + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + } + + void ActivationRulesOverride() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w1 = root_view()->GetChildById(1); + ActivateView(w1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* w3 = root_view()->GetChildById(3); + test_focus_rules()->set_focus_restriction(w3); + + // Normally, activation/focus would move to w2, but since we have a focus + // restriction, it should move to w3 instead. + ChangeViewDisposition(w1); + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateView(root_view()->GetChildById(2)); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + + private: + // When true, the disposition change occurs to the parent of the window + // instead of to the window. This verifies that changes occurring in the + // hierarchy that contains the window affect the window's focus. + bool parent_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerImplicitTestBase); +}; + +// Focus and Activation changes in response to window visibility changes. +class FocusControllerHideTest : public FocusControllerImplicitTestBase { + public: + FocusControllerHideTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerHideTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->SetVisible(false); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerHideTest); +}; + +// Focus and Activation changes in response to window parent visibility +// changes. +class FocusControllerParentHideTest : public FocusControllerHideTest { + public: + FocusControllerParentHideTest() : FocusControllerHideTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentHideTest); +}; + +// Focus and Activation changes in response to window destruction. +class FocusControllerDestructionTest : public FocusControllerImplicitTestBase { + public: + FocusControllerDestructionTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerDestructionTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->Destroy(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDestructionTest); +}; + +// Focus and Activation changes in response to window removal. +class FocusControllerRemovalTest : public FocusControllerImplicitTestBase { + public: + FocusControllerRemovalTest() + : FocusControllerImplicitTestBase(false), + window_to_destroy_(nullptr) {} + + protected: + FocusControllerRemovalTest(bool parent) + : FocusControllerImplicitTestBase(parent), + window_to_destroy_(nullptr) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + View* disposition_view = GetDispositionView(view); + disposition_view->parent()->RemoveChild(disposition_view); + window_to_destroy_ = disposition_view; + } + void TearDown() override { + if (window_to_destroy_) + window_to_destroy_->Destroy(); + + FocusControllerImplicitTestBase::TearDown(); + } + + private: + View* window_to_destroy_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerRemovalTest); +}; + +// Focus and Activation changes in response to window parent removal. +class FocusControllerParentRemovalTest : public FocusControllerRemovalTest { + public: + FocusControllerParentRemovalTest() : FocusControllerRemovalTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentRemovalTest); +}; + +#define FOCUS_CONTROLLER_TEST(TESTCLASS, TESTNAME) \ + TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); } + +// Runs direct focus change tests (input events and API calls). +// +// TODO(erg): Enable gesture events in the future. +#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target. +#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerDestructionTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerRemovalTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target's parent +// hierarchy. +#define IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentRemovalTest, TESTNAME) +// TODO(erg): FocusControllerParentDestructionTest were commented out in the +// aura version of this file, and don't work when I tried porting things over. + +// Runs all implicit focus change tests (changes to the target and target's +// parent hierarchy) +#define IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) + +// Runs all possible focus change tests. +#define ALL_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) + +// Runs focus change tests that apply only to the target. For example, +// implicit activation changes caused by window disposition changes do not +// occur when changes to the containing hierarchy happen. +#define TARGET_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) + +// - Focuses a window, verifies that focus changed. +ALL_FOCUS_TESTS(BasicFocus); + +// - Activates a window, verifies that activation changed. +TARGET_FOCUS_TESTS(BasicActivation); + +// - Focuses a window, verifies that focus events were dispatched. +ALL_FOCUS_TESTS(FocusEvents); + +// - Focuses or activates a window multiple times, verifies that events are only +// dispatched when focus/activation actually changes. +DIRECT_FOCUS_CHANGE_TESTS(DuplicateFocusEvents); +DIRECT_FOCUS_CHANGE_TESTS(DuplicateActivationEvents); + +// - Activates a window, verifies that activation events were dispatched. +TARGET_FOCUS_TESTS(ActivationEvents); + +// - Attempts to active a hidden view, verifies that current view is +// attempted to be reactivated and the appropriate event dispatched. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, ReactivationEvents); + +// - Input events/API calls shift focus between focusable views within the +// active view. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusWithinActiveView); + +// - Input events/API calls to a child view of an inactive view shifts +// activation to the activatable parent and focuses the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToChildOfInactiveView); + +// - Input events/API calls to focus the parent of the focused view do not +// shift focus away from the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToParentOfFocusedView); + +// - Verifies that FocusRules determine what can be focused. +ALL_FOCUS_TESTS(FocusRulesOverride); + +// - Verifies that FocusRules determine what can be activated. +TARGET_FOCUS_TESTS(ActivationRulesOverride); + +// - Verifies that attempts to change focus or activation from a focus or +// activation change observer are ignored. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivation); +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivationDueToHide); +DIRECT_FOCUS_CHANGE_TESTS(NoShiftActiveOnActivation); + +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, + ChangeFocusWhenNothingFocusedAndCaptured); + +// See description above DontPassDestroyedView() for details. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, DontPassDestroyedView); + +// TODO(erg): Add the TextInputClient tests here. + +// If a mouse event was handled, it should not activate a view. +FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent); + +} // namespace window_manager diff --git a/mojo/services/window_manager/focus_rules.h b/mojo/services/window_manager/focus_rules.h new file mode 100644 index 0000000..becd114 --- /dev/null +++ b/mojo/services/window_manager/focus_rules.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ + +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace window_manager { + +// Implemented by an object that establishes the rules about what can be +// focused or activated. +class FocusRules { + public: + virtual ~FocusRules() {} + + // Returns true if the children of |window| can be activated. + virtual bool SupportsChildActivation(mojo::View* window) const = 0; + + // Returns true if |view| is a toplevel view. Whether or not a view + // is considered toplevel is determined by a similar set of rules that + // govern activation and focus. Not all toplevel views are activatable, + // call CanActivateView() to determine if a view can be activated. + virtual bool IsToplevelView(mojo::View* view) const = 0; + // Returns true if |view| can be activated or focused. + virtual bool CanActivateView(mojo::View* view) const = 0; + // For CanFocusView(), null is supported, because null is a valid focusable + // view (in the case of clearing focus). + virtual bool CanFocusView(mojo::View* view) const = 0; + + // Returns the toplevel view containing |view|. Not all toplevel views + // are activatable, call GetActivatableView() instead to return the + // activatable view, which might be in a different hierarchy. + // Will return null if |view| is not contained by a view considered to be + // a toplevel view. + virtual mojo::View* GetToplevelView(mojo::View* view) const = 0; + // Returns the activatable or focusable view given an attempt to activate or + // focus |view|. Some possible scenarios (not intended to be exhaustive): + // - |view| is a child of a non-focusable view and so focus must be set + // according to rules defined by the delegate, e.g. to a parent. + // - |view| is an activatable view that is the transient parent of a modal + // view, so attempts to activate |view| should result in the modal + // transient being activated instead. + // These methods may return null if they are unable to find an activatable + // or focusable view given |view|. + virtual mojo::View* GetActivatableView(mojo::View* view) const = 0; + virtual mojo::View* GetFocusableView(mojo::View* view) const = 0; + + // Returns the next view to activate in the event that |ignore| is no longer + // activatable. This function is called when something is happening to + // |ignore| that means it can no longer have focus or activation, including + // but not limited to: + // - it or its parent hierarchy is being hidden, or removed from the + // RootView. + // - it is being destroyed. + // - it is being explicitly deactivated. + // |ignore| cannot be null. + virtual mojo::View* GetNextActivatableView(mojo::View* ignore) const = 0; +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ diff --git a/mojo/services/window_manager/hit_test.h b/mojo/services/window_manager/hit_test.h new file mode 100644 index 0000000..747dd01 --- /dev/null +++ b/mojo/services/window_manager/hit_test.h @@ -0,0 +1,45 @@ +// Copyright 2015 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. + +#ifndef SERVICES_WINDOW_MANAGER_HIT_TEST_H_ +#define SERVICES_WINDOW_MANAGER_HIT_TEST_H_ + +#if !defined(OS_WIN) + +// Defines the same symbolic names used by the WM_NCHITTEST Notification under +// win32 (the integer values are not guaranteed to be equivalent). We do this +// because we have a whole bunch of code that deals with window resizing and +// such that requires these values. +enum HitTestCompat { + HTNOWHERE = 0, + HTBORDER, + HTBOTTOM, + HTBOTTOMLEFT, + HTBOTTOMRIGHT, + HTCAPTION, + HTCLIENT, + HTCLOSE, + HTERROR, + HTGROWBOX, + HTHELP, + HTHSCROLL, + HTLEFT, + HTMENU, + HTMAXBUTTON, + HTMINBUTTON, + HTREDUCE, + HTRIGHT, + HTSIZE, + HTSYSMENU, + HTTOP, + HTTOPLEFT, + HTTOPRIGHT, + HTTRANSPARENT, + HTVSCROLL, + HTZOOM +}; + +#endif // !defined(OS_WIN) + +#endif // SERVICES_WINDOW_MANAGER_HIT_TEST_H_ diff --git a/mojo/services/window_manager/main.cc b/mojo/services/window_manager/main.cc new file mode 100644 index 0000000..701a249 --- /dev/null +++ b/mojo/services/window_manager/main.cc @@ -0,0 +1,92 @@ +// 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 "base/memory/scoped_ptr.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "mojo/services/window_manager/window_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" + +// ApplicationDelegate implementation file for WindowManager users (e.g. +// core window manager tests) that do not want to provide their own +// ApplicationDelegate::Create(). + +using mojo::View; +using mojo::ViewManager; + +namespace window_manager { + +class DefaultWindowManager : public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate, + public WindowManagerDelegate { + public: + DefaultWindowManager() + : window_manager_app_(new WindowManagerApp(this, this)), + root_(nullptr), + window_offset_(10) { + } + ~DefaultWindowManager() override {} + + private: + // Overridden from mojo::ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* impl) override { + window_manager_app_->Initialize(impl); + tracing_.Initialize(impl); + } + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + window_manager_app_->ConfigureIncomingConnection(connection); + return true; + } + + // Overridden from ViewManagerDelegate: + void OnEmbed(View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + root_ = root; + window_manager_app_->InitFocus( + make_scoped_ptr(new window_manager::BasicFocusRules(root_))); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + // Overridden from WindowManagerDelegate: + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + DCHECK(root_); + View* view = root_->view_manager()->CreateView(); + root_->AddChild(view); + + mojo::Rect rect; + rect.x = rect.y = window_offset_; + rect.width = rect.height = 100; + view->SetBounds(rect); + window_offset_ += 10; + + view->SetVisible(true); + view->Embed(url, services.Pass(), exposed_services.Pass()); + } + + scoped_ptr<WindowManagerApp> window_manager_app_; + + View* root_; + int window_offset_; + mojo::TracingImpl tracing_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(DefaultWindowManager); +}; + +} // namespace window_manager + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner( + new window_manager::DefaultWindowManager); + return runner.Run(shell_handle); +} diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc new file mode 100644 index 0000000..4bd0b80 --- /dev/null +++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc @@ -0,0 +1,36 @@ +// 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 "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h" + +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +NativeViewportEventDispatcherImpl::NativeViewportEventDispatcherImpl( + WindowManagerApp* app, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request) + : app_(app), binding_(this, request.Pass()) { +} +NativeViewportEventDispatcherImpl::~NativeViewportEventDispatcherImpl() { +} + +ui::EventProcessor* NativeViewportEventDispatcherImpl::GetEventProcessor() { + return app_->event_dispatcher(); +} + +void NativeViewportEventDispatcherImpl::OnEvent( + mojo::EventPtr event, + const mojo::Callback<void()>& callback) { + scoped_ptr<ui::Event> ui_event = event.To<scoped_ptr<ui::Event>>(); + + if (ui_event) + SendEventToProcessor(ui_event.get()); + + callback.Run(); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h new file mode 100644 index 0000000..6624c93 --- /dev/null +++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h @@ -0,0 +1,42 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ +#define SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ + +#include "base/basictypes.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "ui/events/event_source.h" + +namespace window_manager { + +class WindowManagerApp; + +class NativeViewportEventDispatcherImpl + : public ui::EventSource, + public mojo::NativeViewportEventDispatcher { + public: + NativeViewportEventDispatcherImpl( + WindowManagerApp* app, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request); + ~NativeViewportEventDispatcherImpl() override; + + private: + // ui::EventSource: + ui::EventProcessor* GetEventProcessor() override; + + // NativeViewportEventDispatcher: + void OnEvent(mojo::EventPtr event, + const mojo::Callback<void()>& callback) override; + + WindowManagerApp* app_; + + mojo::StrongBinding<mojo::NativeViewportEventDispatcher> binding_; + DISALLOW_COPY_AND_ASSIGN(NativeViewportEventDispatcherImpl); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ diff --git a/mojo/services/window_manager/run_all_unittests.cc b/mojo/services/window_manager/run_all_unittests.cc new file mode 100644 index 0000000..51fd967 --- /dev/null +++ b/mojo/services/window_manager/run_all_unittests.cc @@ -0,0 +1,43 @@ +// 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 "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "ui/gl/gl_surface.h" + +#if defined(USE_X11) +#include "ui/gfx/x/x11_connection.h" +#endif + +namespace window_manager { + +class WindowManagerTestSuite : public base::TestSuite { + public: + WindowManagerTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + ~WindowManagerTestSuite() override {} + + protected: + void Initialize() override { +#if defined(USE_X11) + // Each test ends up creating a new thread for the native viewport service. + // In other words we'll use X on different threads, so tell it that. + gfx::InitializeThreadedX11(); +#endif + base::TestSuite::Initialize(); + gfx::GLSurface::InitializeOneOffForTests(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(WindowManagerTestSuite); +}; + +} // namespace window_manager + +int main(int argc, char** argv) { + window_manager::WindowManagerTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/mojo/services/window_manager/view_event_dispatcher.cc b/mojo/services/window_manager/view_event_dispatcher.cc new file mode 100644 index 0000000..657cf88 --- /dev/null +++ b/mojo/services/window_manager/view_event_dispatcher.cc @@ -0,0 +1,55 @@ +// 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 "mojo/services/window_manager/view_event_dispatcher.h" + +#include "mojo/services/window_manager/view_target.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace window_manager { + +ViewEventDispatcher::ViewEventDispatcher() + : event_dispatch_target_(nullptr), + old_dispatch_target_(nullptr) { +} + +ViewEventDispatcher::~ViewEventDispatcher() {} + +void ViewEventDispatcher::SetRootViewTarget(ViewTarget* root_view_target) { + root_view_target_ = root_view_target; +} + +ui::EventTarget* ViewEventDispatcher::GetRootTarget() { + return root_view_target_; +} + +void ViewEventDispatcher::OnEventProcessingStarted(ui::Event* event) { +} + +bool ViewEventDispatcher::CanDispatchToTarget(ui::EventTarget* target) { + return event_dispatch_target_ == target; +} + +ui::EventDispatchDetails ViewEventDispatcher::PreDispatchEvent( + ui::EventTarget* target, + ui::Event* event) { + // TODO(erg): PreDispatch in aura::WindowEventDispatcher does many, many + // things. It, and the functions split off for different event types, are + // most of the file. + old_dispatch_target_ = event_dispatch_target_; + event_dispatch_target_ = static_cast<ViewTarget*>(target); + return ui::EventDispatchDetails(); +} + +ui::EventDispatchDetails ViewEventDispatcher::PostDispatchEvent( + ui::EventTarget* target, + const ui::Event& event) { + // TODO(erg): Not at all as long as PreDispatchEvent, but still missing core + // details. + event_dispatch_target_ = old_dispatch_target_; + old_dispatch_target_ = nullptr; + return ui::EventDispatchDetails(); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_event_dispatcher.h b/mojo/services/window_manager/view_event_dispatcher.h new file mode 100644 index 0000000..399939e --- /dev/null +++ b/mojo/services/window_manager/view_event_dispatcher.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/event_processor.h" +#include "ui/events/event_target.h" + +namespace window_manager { + +class ViewTarget; + +class ViewEventDispatcher : public ui::EventProcessor { + public: + ViewEventDispatcher(); + ~ViewEventDispatcher() override; + + void SetRootViewTarget(ViewTarget* root_view_target); + + private: + // Overridden from ui::EventProcessor: + ui::EventTarget* GetRootTarget() override; + void OnEventProcessingStarted(ui::Event* event) override; + + // Overridden from ui::EventDispatcherDelegate. + bool CanDispatchToTarget(ui::EventTarget* target) override; + ui::EventDispatchDetails PreDispatchEvent(ui::EventTarget* target, + ui::Event* event) override; + ui::EventDispatchDetails PostDispatchEvent( + ui::EventTarget* target, const ui::Event& event) override; + + // We keep a weak reference to ViewTarget*, which corresponds to the root of + // the mojo::View tree. + ViewTarget* root_view_target_; + + ViewTarget* event_dispatch_target_; + ViewTarget* old_dispatch_target_; + + DISALLOW_COPY_AND_ASSIGN(ViewEventDispatcher); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ diff --git a/mojo/services/window_manager/view_target.cc b/mojo/services/window_manager/view_target.cc new file mode 100644 index 0000000..adb3ec2 --- /dev/null +++ b/mojo/services/window_manager/view_target.cc @@ -0,0 +1,192 @@ +// 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 "mojo/services/window_manager/view_target.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "ui/events/event.h" +#include "ui/events/event_target_iterator.h" +#include "ui/events/event_targeter.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/transform.h" + +namespace window_manager { + +namespace { + +DEFINE_OWNED_VIEW_PROPERTY_KEY(ViewTarget, kViewTargetKey, nullptr); + +// Provides a version which keeps a copy of the data (for when it has to be +// derived instead of pointed at). +template <typename T> +class CopyingEventTargetIteratorImpl : public ui::EventTargetIterator { + public: + explicit CopyingEventTargetIteratorImpl(const std::vector<T*>& children) + : children_(children), + begin_(children_.rbegin()), + end_(children_.rend()) {} + ~CopyingEventTargetIteratorImpl() override {} + + ui::EventTarget* GetNextTarget() override { + if (begin_ == end_) + return nullptr; + ui::EventTarget* target = *(begin_); + ++begin_; + return target; + } + + private: + typename std::vector<T*> children_; + typename std::vector<T*>::const_reverse_iterator begin_; + typename std::vector<T*>::const_reverse_iterator end_; +}; + +} // namespace + +ViewTarget::~ViewTarget() { +} + +// static +ViewTarget* ViewTarget::TargetFromView(mojo::View* view) { + if (!view) + return nullptr; + + ViewTarget* target = view->GetLocalProperty(kViewTargetKey); + if (target) + return target; + + return new ViewTarget(view); +} + +void ViewTarget::ConvertPointToTarget(const ViewTarget* source, + const ViewTarget* target, + gfx::Point* point) { + // TODO(erg): Do we need to deal with |source| and |target| being in + // different trees? + DCHECK_EQ(source->GetRoot(), target->GetRoot()); + if (source == target) + return; + + const ViewTarget* root_target = source->GetRoot(); + CHECK_EQ(root_target, target->GetRoot()); + + if (source != root_target) + source->ConvertPointForAncestor(root_target, point); + if (target != root_target) + target->ConvertPointFromAncestor(root_target, point); +} + +std::vector<ViewTarget*> ViewTarget::GetChildren() const { + std::vector<ViewTarget*> targets; + for (mojo::View* child : view_->children()) + targets.push_back(TargetFromView(child)); + return targets; +} + +const ViewTarget* ViewTarget::GetParent() const { + return TargetFromView(view_->parent()); +} + +gfx::Rect ViewTarget::GetBounds() const { + return view_->bounds().To<gfx::Rect>(); +} + +bool ViewTarget::HasParent() const { + return !!view_->parent(); +} + +bool ViewTarget::IsVisible() const { + return view_->visible(); +} + +const ViewTarget* ViewTarget::GetRoot() const { + const ViewTarget* root = this; + for (const ViewTarget* parent = this; parent; parent = parent->GetParent()) + root = parent; + return root; +} + +scoped_ptr<ViewTargeter> ViewTarget::SetEventTargeter( + scoped_ptr<ViewTargeter> targeter) { + scoped_ptr<ViewTargeter> old_targeter = targeter_.Pass(); + targeter_ = targeter.Pass(); + return old_targeter.Pass(); +} + +bool ViewTarget::CanAcceptEvent(const ui::Event& event) { + // We need to make sure that a touch cancel event and any gesture events it + // creates can always reach the window. This ensures that we receive a valid + // touch / gesture stream. + if (event.IsEndingEvent()) + return true; + + if (!view_->visible()) + return false; + + // The top-most window can always process an event. + if (!view_->parent()) + return true; + + // In aura, we only accept events if this is a key event or if the user + // supplied a TargetHandler, usually the aura::WindowDelegate. Here, we're + // just forwarding events to other Views which may be in other processes, so + // always accept. + return true; +} + +ui::EventTarget* ViewTarget::GetParentTarget() { + return TargetFromView(view_->parent()); +} + +scoped_ptr<ui::EventTargetIterator> ViewTarget::GetChildIterator() const { + return scoped_ptr<ui::EventTargetIterator>( + new CopyingEventTargetIteratorImpl<ViewTarget>(GetChildren())); +} + +ui::EventTargeter* ViewTarget::GetEventTargeter() { + return targeter_.get(); +} + +void ViewTarget::ConvertEventToTarget(ui::EventTarget* target, + ui::LocatedEvent* event) { + event->ConvertLocationToTarget(this, static_cast<ViewTarget*>(target)); +} + +ViewTarget::ViewTarget(mojo::View* view_to_wrap) : view_(view_to_wrap) { + DCHECK(view_->GetLocalProperty(kViewTargetKey) == nullptr); + view_->SetLocalProperty(kViewTargetKey, this); +} + +bool ViewTarget::ConvertPointForAncestor(const ViewTarget* ancestor, + gfx::Point* point) const { + gfx::Vector2d offset; + bool result = GetTargetOffsetRelativeTo(ancestor, &offset); + *point += offset; + return result; +} + +bool ViewTarget::ConvertPointFromAncestor(const ViewTarget* ancestor, + gfx::Point* point) const { + gfx::Vector2d offset; + bool result = GetTargetOffsetRelativeTo(ancestor, &offset); + *point -= offset; + return result; +} + +bool ViewTarget::GetTargetOffsetRelativeTo(const ViewTarget* ancestor, + gfx::Vector2d* offset) const { + const ViewTarget* v = this; + for (; v && v != ancestor; v = v->GetParent()) { + gfx::Rect bounds = v->GetBounds(); + *offset += gfx::Vector2d(bounds.x(), bounds.y()); + } + return v == ancestor; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_target.h b/mojo/services/window_manager/view_target.h new file mode 100644 index 0000000..1119e1f --- /dev/null +++ b/mojo/services/window_manager/view_target.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ + +#include "ui/events/event_target.h" + +namespace gfx { +class Point; +class Rect; +class Vector2d; +} + +namespace ui { +class EventTargeter; +} + +namespace mojo { +class View; +} + +namespace window_manager { + +class TestView; +class ViewTargeter; +class WindowManagerApp; + +// A wrapper class around mojo::View; we can't subclass View to implement the +// event targeting interfaces, so we create a separate object which observes +// the View and ties its lifetime to it. +// +// We set ourselves as a property of the view passed in, and we are owned by +// said View. +class ViewTarget : public ui::EventTarget { + public: + ~ViewTarget() override; + + // Returns the ViewTarget for a View. ViewTargets are owned by the |view| + // passed in, and are created on demand. + static ViewTarget* TargetFromView(mojo::View* view); + + // Converts |point| from |source|'s coordinates to |target|'s. If |source| is + // NULL, the function returns without modifying |point|. |target| cannot be + // NULL. + static void ConvertPointToTarget(const ViewTarget* source, + const ViewTarget* target, + gfx::Point* point); + + mojo::View* view() { return view_; } + + // TODO(erg): Make this const once we've removed aura from the tree and it's + // feasible to change all callers of the EventTargeter interface to pass and + // accept const objects. (When that gets done, re-const the + // EventTargetIterator::GetNextTarget and EventTarget::GetChildIterator + // interfaces.) + std::vector<ViewTarget*> GetChildren() const; + + const ViewTarget* GetParent() const; + gfx::Rect GetBounds() const; + bool HasParent() const; + bool IsVisible() const; + + const ViewTarget* GetRoot() const; + + // Sets a new ViewTargeter for the view, and returns the previous + // ViewTargeter. + scoped_ptr<ViewTargeter> SetEventTargeter(scoped_ptr<ViewTargeter> targeter); + + // Overridden from ui::EventTarget: + bool CanAcceptEvent(const ui::Event& event) override; + EventTarget* GetParentTarget() override; + scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override; + ui::EventTargeter* GetEventTargeter() override; + void ConvertEventToTarget(ui::EventTarget* target, + ui::LocatedEvent* event) override; + + private: + friend class TestView; + explicit ViewTarget(mojo::View* view_to_wrap); + + bool ConvertPointForAncestor(const ViewTarget* ancestor, + gfx::Point* point) const; + bool ConvertPointFromAncestor(const ViewTarget* ancestor, + gfx::Point* point) const; + bool GetTargetOffsetRelativeTo(const ViewTarget* ancestor, + gfx::Vector2d* offset) const; + + // The mojo::View that we dispatch to. + mojo::View* view_; + + scoped_ptr<ViewTargeter> targeter_; + + DISALLOW_COPY_AND_ASSIGN(ViewTarget); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ diff --git a/mojo/services/window_manager/view_target_unittest.cc b/mojo/services/window_manager/view_target_unittest.cc new file mode 100644 index 0000000..bec5c37 --- /dev/null +++ b/mojo/services/window_manager/view_target_unittest.cc @@ -0,0 +1,81 @@ +// 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 "mojo/services/window_manager/view_target.h" + +#include <set> + +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "ui/gfx/geometry/rect.h" + +namespace window_manager { + +using ViewTargetTest = testing::Test; + +// V1 +// +-- V2 +// +-- V3 +TEST_F(ViewTargetTest, GetRoot) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + TestView v3(3, gfx::Rect(10, 10, 100, 100)); + v1.AddChild(&v2); + v2.AddChild(&v3); + + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v1)->GetRoot()); + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v2)->GetRoot()); + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v3)->GetRoot()); +} + +// V1 +// +-- V2 +TEST_F(ViewTargetTest, ConvertPointToTarget_Simple) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + v1.AddChild(&v2); + + ViewTarget* t1 = v1.target(); + ViewTarget* t2 = v2.target(); + + gfx::Point point1_in_t2_coords(5, 5); + ViewTarget::ConvertPointToTarget(t2, t1, &point1_in_t2_coords); + gfx::Point point1_in_t1_coords(15, 15); + EXPECT_EQ(point1_in_t1_coords, point1_in_t2_coords); + + gfx::Point point2_in_t1_coords(5, 5); + ViewTarget::ConvertPointToTarget(t1, t2, &point2_in_t1_coords); + gfx::Point point2_in_t2_coords(-5, -5); + EXPECT_EQ(point2_in_t2_coords, point2_in_t1_coords); +} + +// V1 +// +-- V2 +// +-- V3 +TEST_F(ViewTargetTest, ConvertPointToTarget_Medium) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + TestView v3(3, gfx::Rect(10, 10, 100, 100)); + v1.AddChild(&v2); + v2.AddChild(&v3); + + ViewTarget* t1 = v1.target(); + ViewTarget* t3 = v3.target(); + + gfx::Point point1_in_t3_coords(5, 5); + ViewTarget::ConvertPointToTarget(t3, t1, &point1_in_t3_coords); + gfx::Point point1_in_t1_coords(25, 25); + EXPECT_EQ(point1_in_t1_coords, point1_in_t3_coords); + + gfx::Point point2_in_t1_coords(5, 5); + ViewTarget::ConvertPointToTarget(t1, t3, &point2_in_t1_coords); + gfx::Point point2_in_t3_coords(-15, -15); + EXPECT_EQ(point2_in_t3_coords, point2_in_t1_coords); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_targeter.cc b/mojo/services/window_manager/view_targeter.cc new file mode 100644 index 0000000..58fb370 --- /dev/null +++ b/mojo/services/window_manager/view_targeter.cc @@ -0,0 +1,109 @@ +// 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 "mojo/services/window_manager/view_targeter.h" + +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/view_target.h" + +namespace window_manager { + +ViewTargeter::ViewTargeter() {} + +ViewTargeter::~ViewTargeter() {} + +ui::EventTarget* ViewTargeter::FindTargetForEvent(ui::EventTarget* root, + ui::Event* event) { + ViewTarget* view = static_cast<ViewTarget*>(root); + ViewTarget* target = + event->IsKeyEvent() + ? FindTargetForKeyEvent(view, *static_cast<ui::KeyEvent*>(event)) + : static_cast<ViewTarget*>( + EventTargeter::FindTargetForEvent(root, event)); + + // TODO(erg): The aura version of this method does a lot of work to handle + // dispatching to a target that isn't a child of |view|. For now, punt on + // this. + DCHECK_EQ(view->GetRoot(), target->GetRoot()); + + return target; +} + +ui::EventTarget* ViewTargeter::FindTargetForLocatedEvent( + ui::EventTarget* root, + ui::LocatedEvent* event) { + ViewTarget* view = static_cast<ViewTarget*>(root); + if (!view->HasParent()) { + ViewTarget* target = FindTargetInRootView(view, *event); + if (target) { + view->ConvertEventToTarget(target, event); + return target; + } + } + return EventTargeter::FindTargetForLocatedEvent(view, event); +} + +bool ViewTargeter::SubtreeCanAcceptEvent(ui::EventTarget* target, + const ui::LocatedEvent& event) const { + ViewTarget* view = static_cast<ViewTarget*>(target); + + if (!view->IsVisible()) + return false; + + // TODO(erg): We may need to keep track of the parent on ViewTarget, because + // we have a check here about + // WindowDelegate::ShouldDescendIntoChildForEventHandling(). + + // TODO(sky): decide if we really want this. If we do, it should be a public + // constant and documented. + if (view->view()->shared_properties().count("deliver-events-to-parent")) + return false; + + return true; +} + +bool ViewTargeter::EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const { + ViewTarget* view = static_cast<ViewTarget*>(target); + gfx::Point point = event.location(); + const ViewTarget* parent = view->GetParent(); + if (parent) + ViewTarget::ConvertPointToTarget(parent, view, &point); + return gfx::Rect(view->GetBounds().size()).Contains(point); +} + +ViewTarget* ViewTargeter::FindTargetForKeyEvent(ViewTarget* view_target, + const ui::KeyEvent& key) { + FocusController* focus_controller = GetFocusController(view_target->view()); + if (focus_controller) { + mojo::View* focused_view = focus_controller->GetFocusedView(); + if (focused_view) + return ViewTarget::TargetFromView(focused_view); + } + return view_target; +} + +ViewTarget* ViewTargeter::FindTargetInRootView(ViewTarget* root_view, + const ui::LocatedEvent& event) { + // TODO(erg): This here is important because it resolves + // mouse_pressed_handler() in the aura version. This is what makes sure + // that a view gets both the mouse down and up. + + CaptureController* capture_controller = + GetCaptureController(root_view->view()); + if (capture_controller) { + mojo::View* capture_view = capture_controller->GetCapture(); + if (capture_view) + return ViewTarget::TargetFromView(capture_view); + } + + // TODO(erg): There's a whole bunch of junk about handling touch events + // here. Handle later. + + return nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_targeter.h b/mojo/services/window_manager/view_targeter.h new file mode 100644 index 0000000..3a01e57 --- /dev/null +++ b/mojo/services/window_manager/view_targeter.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ + +#include "ui/events/event_targeter.h" + +namespace window_manager { + +class ViewTarget; + +class ViewTargeter : public ui::EventTargeter { + public: + ViewTargeter(); + ~ViewTargeter() override; + + protected: + // ui::EventTargeter: + ui::EventTarget* FindTargetForEvent(ui::EventTarget* root, + ui::Event* event) override; + ui::EventTarget* FindTargetForLocatedEvent(ui::EventTarget* root, + ui::LocatedEvent* event) override; + bool SubtreeCanAcceptEvent(ui::EventTarget* target, + const ui::LocatedEvent& event) const override; + bool EventLocationInsideBounds(ui::EventTarget* target, + const ui::LocatedEvent& event) const override; + + private: + // Targets either the root View or the currently focused view. + ViewTarget* FindTargetForKeyEvent(ViewTarget* view, const ui::KeyEvent& key); + + // Deals with cases where the |root_view| needs to change how things are + // dispatched. (For example, in the case of capture.) + ViewTarget* FindTargetInRootView(ViewTarget* root_view, + const ui::LocatedEvent& event); + + DISALLOW_COPY_AND_ASSIGN(ViewTargeter); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ diff --git a/mojo/services/window_manager/view_targeter_unittest.cc b/mojo/services/window_manager/view_targeter_unittest.cc new file mode 100644 index 0000000..0b3281d --- /dev/null +++ b/mojo/services/window_manager/view_targeter_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 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. + +#include "mojo/services/window_manager/view_targeter.h" + +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_utils.h" +#include "ui/events/test/test_event_handler.h" + +namespace window_manager { + +class ViewTargeterTest : public testing::Test { + public: + ViewTargeterTest() {} + ~ViewTargeterTest() override {} + + void SetUp() override { + view_event_dispatcher_.reset(new ViewEventDispatcher()); + } + + void TearDown() override { + view_event_dispatcher_.reset(); + testing::Test::TearDown(); + } + + protected: + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewTargeterTest); +}; + +TEST_F(ViewTargeterTest, Basic) { + // The dispatcher will take ownership of the tree root. + TestView root(1, gfx::Rect(0, 0, 100, 100)); + ViewTarget* root_target = root.target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_->SetRootViewTarget(root_target); + + CaptureController capture_controller; + SetCaptureController(&root, &capture_controller); + + TestView one(2, gfx::Rect(0, 0, 500, 100)); + TestView two(3, gfx::Rect(501, 0, 500, 1000)); + + root.AddChild(&one); + root.AddChild(&two); + + ui::test::TestEventHandler handler; + one.target()->AddPreTargetHandler(&handler); + + ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::Point(20, 20), + gfx::Point(20, 20), ui::EventTimeForNow(), ui::EF_NONE, + ui::EF_NONE); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&press); + ASSERT_FALSE(details.dispatcher_destroyed); + + EXPECT_EQ(1, handler.num_mouse_events()); + + one.target()->RemovePreTargetHandler(&handler); +} + +TEST_F(ViewTargeterTest, KeyTest) { + // The dispatcher will take ownership of the tree root. + TestView root(1, gfx::Rect(0, 0, 100, 100)); + ViewTarget* root_target = root.target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_->SetRootViewTarget(root_target); + + CaptureController capture_controller; + SetCaptureController(&root, &capture_controller); + + TestView one(2, gfx::Rect(0, 0, 500, 100)); + TestView two(3, gfx::Rect(501, 0, 500, 1000)); + + root.AddChild(&one); + root.AddChild(&two); + + ui::test::TestEventHandler one_handler; + one.target()->AddPreTargetHandler(&one_handler); + + ui::test::TestEventHandler two_handler; + two.target()->AddPreTargetHandler(&two_handler); + + FocusController focus_controller(make_scoped_ptr(new BasicFocusRules(&root))); + SetFocusController(&root, &focus_controller); + + // Focus |one|. Then test that it receives a key event. + focus_controller.FocusView(&one); + ui::KeyEvent key_event_one(ui::ET_KEY_PRESSED, ui::VKEY_A, 0); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&key_event_one); + ASSERT_FALSE(details.dispatcher_destroyed); + EXPECT_EQ(1, one_handler.num_key_events()); + + // Focus |two|. Then test that it receives a key event. + focus_controller.FocusView(&two); + ui::KeyEvent key_event_two(ui::ET_KEY_PRESSED, ui::VKEY_A, 0); + details = view_event_dispatcher_->OnEventFromSource(&key_event_two); + ASSERT_FALSE(details.dispatcher_destroyed); + EXPECT_EQ(1, two_handler.num_key_events()); + + two.target()->RemovePreTargetHandler(&two_handler); + one.target()->RemovePreTargetHandler(&one_handler); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_api_unittest.cc b/mojo/services/window_manager/window_manager_api_unittest.cc new file mode 100644 index 0000000..7d5dccf --- /dev/null +++ b/mojo/services/window_manager/window_manager_api_unittest.cc @@ -0,0 +1,260 @@ +// 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 "base/bind.h" +#include "base/command_line.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/shell_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +using mojo::ApplicationImpl; +using mojo::Id; +using mojo::View; + +namespace window_manager { +namespace { + +const char kTestServiceURL[] = "mojo:test_url"; + +void EmptyResultCallback(bool result) {} + +class TestWindowManagerObserver : public mojo::WindowManagerObserver { + public: + using NodeIdCallback = base::Callback<void(Id)>; + + explicit TestWindowManagerObserver( + mojo::InterfaceRequest<mojo::WindowManagerObserver> observer_request) + : binding_(this, observer_request.Pass()) {} + ~TestWindowManagerObserver() override {} + + void set_focus_changed_callback(const NodeIdCallback& callback) { + focus_changed_callback_ = callback; + } + void set_active_window_changed_callback(const NodeIdCallback& callback) { + active_window_changed_callback_ = callback; + } + + private: + // Overridden from mojo::WindowManagerObserver: + void OnCaptureChanged(Id new_capture_node_id) override {} + void OnFocusChanged(Id focused_node_id) override { + if (!focus_changed_callback_.is_null()) + focus_changed_callback_.Run(focused_node_id); + } + void OnActiveWindowChanged(Id active_window) override { + if (!active_window_changed_callback_.is_null()) + active_window_changed_callback_.Run(active_window); + } + + NodeIdCallback focus_changed_callback_; + NodeIdCallback active_window_changed_callback_; + mojo::Binding<WindowManagerObserver> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver); +}; + +class TestApplicationLoader : public mojo::shell::ApplicationLoader, + public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate { + public: + typedef base::Callback<void(View*)> RootAddedCallback; + + explicit TestApplicationLoader(const RootAddedCallback& root_added_callback) + : root_added_callback_(root_added_callback) {} + ~TestApplicationLoader() override {} + + private: + // Overridden from mojo::shell::ApplicationLoader: + void Load( + const GURL& url, + mojo::InterfaceRequest<mojo::Application> application_request) override { + ASSERT_TRUE(application_request.is_pending()); + scoped_ptr<ApplicationImpl> app( + new ApplicationImpl(this, application_request.Pass())); + apps_.push_back(app.release()); + } + + // Overridden from mojo::ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new mojo::ViewManagerClientFactory(app->shell(), this)); + } + + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + // Overridden from mojo::ViewManagerDelegate: + void OnEmbed(View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + root_added_callback_.Run(root); + } + void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override {} + + RootAddedCallback root_added_callback_; + + ScopedVector<ApplicationImpl> apps_; + scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_; + + DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader); +}; + +} // namespace + +class WindowManagerApiTest : public testing::Test { + public: + WindowManagerApiTest() {} + ~WindowManagerApiTest() override {} + + protected: + Id WaitForEmbed() { + Id id; + base::RunLoop run_loop; + root_added_callback_ = base::Bind(&WindowManagerApiTest::OnEmbed, + base::Unretained(this), &id, &run_loop); + run_loop.Run(); + return id; + } + + Id WaitForFocusChange() { + Id new_focused; + base::RunLoop run_loop; + window_manager_observer()->set_focus_changed_callback( + base::Bind(&WindowManagerApiTest::OnFocusChanged, + base::Unretained(this), &new_focused, &run_loop)); + run_loop.Run(); + return new_focused; + } + + Id WaitForActiveWindowChange() { + Id new_active; + base::RunLoop run_loop; + window_manager_observer()->set_active_window_changed_callback( + base::Bind(&WindowManagerApiTest::OnActiveWindowChanged, + base::Unretained(this), &new_active, &run_loop)); + run_loop.Run(); + return new_active; + } + + Id OpenWindow() { + return OpenWindowWithURL(kTestServiceURL); + } + + Id OpenWindowWithURL(const std::string& url) { + base::RunLoop run_loop; + window_manager_->Embed(url, nullptr, nullptr); + run_loop.Run(); + return WaitForEmbed(); + } + + TestWindowManagerObserver* window_manager_observer() { + return window_manager_observer_.get(); + } + + mojo::WindowManagerPtr window_manager_; + + private: + // Overridden from testing::Test: + void SetUp() override { + test_helper_.reset(new mojo::shell::ShellTestHelper); + test_helper_->Init(); + test_helper_->AddURLMapping(GURL("mojo:window_manager"), + GURL("mojo:core_window_manager")); + test_helper_->SetLoaderForURL( + scoped_ptr<mojo::shell::ApplicationLoader>( + new TestApplicationLoader(base::Bind( + &WindowManagerApiTest::OnRootAdded, base::Unretained(this)))), + GURL(kTestServiceURL)); + ConnectToWindowManager2(); + } + void TearDown() override {} + + void ConnectToWindowManager2() { + test_helper_->application_manager()->ConnectToService( + GURL("mojo:window_manager"), &window_manager_); + base::RunLoop connect_loop; + mojo::WindowManagerObserverPtr observer; + window_manager_observer_.reset( + new TestWindowManagerObserver(GetProxy(&observer))); + + window_manager_->GetFocusedAndActiveViews( + observer.Pass(), + base::Bind(&WindowManagerApiTest::GotFocusedAndActiveViews, + base::Unretained(this))); + connect_loop.Run(); + + // The RunLoop above ensures the connection to the window manager completes. + // Without this the ApplicationManager would load the window manager twice. + test_helper_->application_manager()->ConnectToService( + GURL("mojo:core_window_manager"), &window_manager_); + } + + void GotFocusedAndActiveViews(uint32_t, uint32_t, uint32_t) {} + + void OnRootAdded(View* root) { + if (!root_added_callback_.is_null()) + root_added_callback_.Run(root); + } + + void OnEmbed(Id* root_id, + base::RunLoop* loop, + View* root) { + *root_id = root->id(); + loop->Quit(); + } + + void OnFocusChanged(Id* new_focused, + base::RunLoop* run_loop, + Id focused_node_id) { + *new_focused = focused_node_id; + run_loop->Quit(); + } + + void OnActiveWindowChanged(Id* new_active, + base::RunLoop* run_loop, + Id active_node_id) { + *new_active = active_node_id; + run_loop->Quit(); + } + + scoped_ptr<mojo::shell::ShellTestHelper> test_helper_; + scoped_ptr<TestWindowManagerObserver> window_manager_observer_; + TestApplicationLoader::RootAddedCallback root_added_callback_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerApiTest); +}; + +// TODO(sky): resolve this. Temporarily disabled as ApplicationManager ends up +// loading windowmanager twice because of the mapping of window_manager to +// core_window_manager. +TEST_F(WindowManagerApiTest, DISABLED_FocusAndActivateWindow) { + Id first_window = OpenWindow(); + window_manager_->FocusWindow(first_window, base::Bind(&EmptyResultCallback)); + Id id = WaitForFocusChange(); + EXPECT_EQ(id, first_window); + + Id second_window = OpenWindow(); + window_manager_->ActivateWindow(second_window, + base::Bind(&EmptyResultCallback)); + id = WaitForActiveWindowChange(); + EXPECT_EQ(id, second_window); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app.cc b/mojo/services/window_manager/window_manager_app.cc new file mode 100644 index 0000000..99d2b35 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app.cc @@ -0,0 +1,417 @@ +// 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 "mojo/services/window_manager/window_manager_app.h" + +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/interfaces/application/shell.mojom.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/focus_rules.h" +#include "mojo/services/window_manager/hit_test.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" + +using mojo::ApplicationConnection; +using mojo::Id; +using mojo::ServiceProvider; +using mojo::View; +using mojo::WindowManager; + +namespace window_manager { + +namespace { + +Id GetIdForView(View* view) { + return view ? view->id() : 0; +} + +} // namespace + +// Used for calls to Embed() that occur before we've connected to the +// ViewManager. +struct WindowManagerApp::PendingEmbed { + mojo::String url; + mojo::InterfaceRequest<ServiceProvider> services; + mojo::ServiceProviderPtr exposed_services; +}; + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, public: + +WindowManagerApp::WindowManagerApp( + ViewManagerDelegate* view_manager_delegate, + WindowManagerDelegate* window_manager_delegate) + : shell_(nullptr), + wrapped_view_manager_delegate_(view_manager_delegate), + window_manager_delegate_(window_manager_delegate), + root_(nullptr) { +} + +WindowManagerApp::~WindowManagerApp() { + // TODO(msw|sky): Should this destructor explicitly delete the ViewManager? + mojo::ViewManager* cached_view_manager = view_manager(); + for (RegisteredViewIdSet::const_iterator it = registered_view_id_set_.begin(); + cached_view_manager && it != registered_view_id_set_.end(); ++it) { + View* view = cached_view_manager->GetViewById(*it); + if (view && view == root_) + root_ = nullptr; + if (view) + view->RemoveObserver(this); + } + registered_view_id_set_.clear(); + DCHECK(!root_); + + STLDeleteElements(&connections_); +} + +void WindowManagerApp::AddConnection(WindowManagerImpl* connection) { + DCHECK(connections_.find(connection) == connections_.end()); + connections_.insert(connection); +} + +void WindowManagerApp::RemoveConnection(WindowManagerImpl* connection) { + DCHECK(connections_.find(connection) != connections_.end()); + connections_.erase(connection); +} + +bool WindowManagerApp::SetCapture(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && SetCaptureImpl(view); +} + +bool WindowManagerApp::FocusWindow(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && FocusWindowImpl(view); +} + +bool WindowManagerApp::ActivateWindow(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && ActivateWindowImpl(view); +} + +bool WindowManagerApp::IsReady() const { + return !!root_; +} + +void WindowManagerApp::InitFocus(scoped_ptr<FocusRules> rules) { + DCHECK(root_); + + focus_controller_.reset(new FocusController(rules.Pass())); + focus_controller_->AddObserver(this); + SetFocusController(root_, focus_controller_.get()); + + capture_controller_.reset(new CaptureController); + capture_controller_->AddObserver(this); + SetCaptureController(root_, capture_controller_.get()); +} + +void WindowManagerApp::Embed( + const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + if (view_manager()) { + window_manager_delegate_->Embed(url, services.Pass(), + exposed_services.Pass()); + return; + } + scoped_ptr<PendingEmbed> pending_embed(new PendingEmbed); + pending_embed->url = url; + pending_embed->services = services.Pass(); + pending_embed->exposed_services = exposed_services.Pass(); + pending_embeds_.push_back(pending_embed.release()); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ApplicationDelegate implementation: + +void WindowManagerApp::Initialize(mojo::ApplicationImpl* impl) { + shell_ = impl->shell(); + LaunchViewManager(impl); +} + +bool WindowManagerApp::ConfigureIncomingConnection( + ApplicationConnection* connection) { + connection->AddService<WindowManager>(this); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ViewManagerDelegate implementation: + +void WindowManagerApp::OnEmbed( + View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + DCHECK(!root_); + root_ = root; + + view_event_dispatcher_.reset(new ViewEventDispatcher); + + RegisterSubtree(root_); + + if (wrapped_view_manager_delegate_) { + wrapped_view_manager_delegate_->OnEmbed(root, services.Pass(), + exposed_services.Pass()); + } + + for (PendingEmbed* pending_embed : pending_embeds_) { + Embed(pending_embed->url, pending_embed->services.Pass(), + pending_embed->exposed_services.Pass()); + } + pending_embeds_.clear(); +} + +void WindowManagerApp::OnViewManagerDisconnected( + mojo::ViewManager* view_manager) { + if (wrapped_view_manager_delegate_) + wrapped_view_manager_delegate_->OnViewManagerDisconnected(view_manager); + + base::MessageLoop* message_loop = base::MessageLoop::current(); + if (message_loop && message_loop->is_running()) + message_loop->Quit(); +} + +bool WindowManagerApp::OnPerformAction(mojo::View* view, + const std::string& action) { + if (!view) + return false; + if (action == "capture") + return SetCaptureImpl(view); + if (action == "focus") + return FocusWindowImpl(view); + else if (action == "activate") + return ActivateWindowImpl(view); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ViewObserver implementation: + +void WindowManagerApp::OnTreeChanged( + const ViewObserver::TreeChangeParams& params) { + if (params.receiver != root_) + return; + DCHECK(params.old_parent || params.new_parent); + if (!params.target) + return; + + if (params.new_parent) { + if (registered_view_id_set_.find(params.target->id()) == + registered_view_id_set_.end()) { + RegisteredViewIdSet::const_iterator it = + registered_view_id_set_.find(params.new_parent->id()); + DCHECK(it != registered_view_id_set_.end()); + RegisterSubtree(params.target); + } + } else if (params.old_parent) { + UnregisterSubtree(params.target); + } +} + +void WindowManagerApp::OnViewDestroying(View* view) { + Unregister(view); + if (view == root_) { + root_ = nullptr; + if (focus_controller_) + focus_controller_->RemoveObserver(this); + if (capture_controller_) + capture_controller_->RemoveObserver(this); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ui::EventHandler implementation: + +void WindowManagerApp::OnEvent(ui::Event* event) { + if (!window_manager_client_) + return; + + View* view = static_cast<ViewTarget*>(event->target())->view(); + if (!view) + return; + + if (event->IsKeyEvent()) { + const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(event); + if (key_event->type() == ui::ET_KEY_PRESSED) { + ui::Accelerator accelerator = ConvertEventToAccelerator(key_event); + if (accelerator_manager_.Process(accelerator)) + return; + } + } + + if (focus_controller_) + focus_controller_->OnEvent(event); + + window_manager_client_->DispatchInputEventToView(view->id(), + mojo::Event::From(*event)); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, mojo::FocusControllerObserver implementation: + +void WindowManagerApp::OnFocused(View* gained_focus) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyViewFocused(GetIdForView(gained_focus)); + } +} + +void WindowManagerApp::OnActivated(View* gained_active) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyWindowActivated(GetIdForView(gained_active)); + } + if (gained_active) + gained_active->MoveToFront(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, mojo::CaptureControllerObserver implementation: + +void WindowManagerApp::OnCaptureChanged(View* gained_capture) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyCaptureChanged(GetIdForView(gained_capture)); + } + if (gained_capture) + gained_capture->MoveToFront(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, private: + +bool WindowManagerApp::SetCaptureImpl(View* view) { + CHECK(view); + capture_controller_->SetCapture(view); + return capture_controller_->GetCapture() == view; +} + +bool WindowManagerApp::FocusWindowImpl(View* view) { + CHECK(view); + focus_controller_->FocusView(view); + return focus_controller_->GetFocusedView() == view; +} + +bool WindowManagerApp::ActivateWindowImpl(View* view) { + CHECK(view); + focus_controller_->ActivateView(view); + return focus_controller_->GetActiveView() == view; +} + +void WindowManagerApp::RegisterSubtree(View* view) { + view->AddObserver(this); + DCHECK(registered_view_id_set_.find(view->id()) == + registered_view_id_set_.end()); + // All events pass through the root during dispatch, so we only need a handler + // installed there. + if (view == root_) { + ViewTarget* target = ViewTarget::TargetFromView(view); + target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + target->AddPreTargetHandler(this); + view_event_dispatcher_->SetRootViewTarget(target); + } + registered_view_id_set_.insert(view->id()); + View::Children::const_iterator it = view->children().begin(); + for (; it != view->children().end(); ++it) + RegisterSubtree(*it); +} + +void WindowManagerApp::UnregisterSubtree(View* view) { + for (View* child : view->children()) + UnregisterSubtree(child); + Unregister(view); +} + +void WindowManagerApp::Unregister(View* view) { + RegisteredViewIdSet::iterator it = registered_view_id_set_.find(view->id()); + if (it == registered_view_id_set_.end()) { + // Because we unregister in OnViewDestroying() we can still get a subsequent + // OnTreeChanged for the same view. Ignore this one. + return; + } + view->RemoveObserver(this); + DCHECK(it != registered_view_id_set_.end()); + registered_view_id_set_.erase(it); +} + +void WindowManagerApp::DispatchInputEventToView(View* view, + mojo::EventPtr event) { + window_manager_client_->DispatchInputEventToView(view->id(), event.Pass()); +} + +void WindowManagerApp::SetViewportSize(const gfx::Size& size) { + window_manager_client_->SetViewportSize(mojo::Size::From(size)); +} + +void WindowManagerApp::LaunchViewManager(mojo::ApplicationImpl* app) { + // TODO(sky): figure out logic if this connection goes away. + view_manager_client_factory_.reset( + new mojo::ViewManagerClientFactory(shell_, this)); + + ApplicationConnection* view_manager_app = + app->ConnectToApplication("mojo:view_manager"); + view_manager_app->ConnectToService(&view_manager_service_); + + view_manager_app->AddService<WindowManagerInternal>(this); + view_manager_app->AddService<mojo::NativeViewportEventDispatcher>(this); + + view_manager_app->ConnectToService(&window_manager_client_); +} + +void WindowManagerApp::Create( + ApplicationConnection* connection, + mojo::InterfaceRequest<WindowManagerInternal> request) { + if (wm_internal_binding_.get()) { + VLOG(1) << + "WindowManager allows only one WindowManagerInternal connection."; + return; + } + wm_internal_binding_.reset( + new mojo::Binding<WindowManagerInternal>(this, request.Pass())); +} + +void WindowManagerApp::Create(ApplicationConnection* connection, + mojo::InterfaceRequest<WindowManager> request) { + WindowManagerImpl* wm = new WindowManagerImpl(this, false); + wm->Bind(request.PassMessagePipe()); + // WindowManagerImpl is deleted when the connection has an error, or from our + // destructor. +} + +void WindowManagerApp::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request) { + new NativeViewportEventDispatcherImpl(this, request.Pass()); +} + +void WindowManagerApp::CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) { + // TODO(sky): pass in |connection_id| for validation. + WindowManagerImpl* wm = new WindowManagerImpl(this, true); + wm->Bind(window_manager_pipe.Pass()); + // WindowManagerImpl is deleted when the connection has an error, or from our + // destructor. +} + +void WindowManagerApp::SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) { + view_manager_client_.reset( + mojo::ViewManagerClientFactory::WeakBindViewManagerToPipe( + mojo::MakeRequest<mojo::ViewManagerClient>( + view_manager_client_request.Pass()), + view_manager_service_.Pass(), shell_, this)); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app.h b/mojo/services/window_manager/window_manager_app.h new file mode 100644 index 0000000..f91be46 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app.h @@ -0,0 +1,213 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ + +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/services/window_manager/capture_controller_observer.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/window_manager_impl.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" +#include "ui/base/accelerators/accelerator_manager.h" +#include "ui/events/event_handler.h" + +namespace gfx { +class Size; +} + +namespace window_manager { + +class CaptureController; +class FocusController; +class FocusRules; +class ViewEventDispatcher; +class WindowManagerDelegate; +class WindowManagerImpl; + +// Implements core window manager functionality that could conceivably be shared +// across multiple window managers implementing superficially different user +// experiences. Establishes communication with the view manager. +// A window manager wishing to use this core should create and own an instance +// of this object. They may implement the associated ViewManager/WindowManager +// delegate interfaces exposed by the view manager, this object provides the +// canonical implementation of said interfaces but will call out to the wrapped +// instances. +class WindowManagerApp + : public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate, + public mojo::ViewObserver, + public ui::EventHandler, + public FocusControllerObserver, + public CaptureControllerObserver, + public mojo::InterfaceFactory<mojo::WindowManager>, + public mojo::InterfaceFactory<mojo::WindowManagerInternal>, + public mojo::InterfaceFactory<mojo::NativeViewportEventDispatcher>, + public mojo::WindowManagerInternal { + public: + WindowManagerApp(ViewManagerDelegate* view_manager_delegate, + WindowManagerDelegate* window_manager_delegate); + ~WindowManagerApp() override; + + ViewEventDispatcher* event_dispatcher() { + return view_event_dispatcher_.get(); + } + + // Register/deregister new connections to the window manager service. + void AddConnection(WindowManagerImpl* connection); + void RemoveConnection(WindowManagerImpl* connection); + + // These are canonical implementations of the window manager API methods. + bool SetCapture(mojo::Id view); + bool FocusWindow(mojo::Id view); + bool ActivateWindow(mojo::Id view); + + void DispatchInputEventToView(mojo::View* view, mojo::EventPtr event); + void SetViewportSize(const gfx::Size& size); + + bool IsReady() const; + + FocusController* focus_controller() { return focus_controller_.get(); } + CaptureController* capture_controller() { return capture_controller_.get(); } + + void InitFocus(scoped_ptr<FocusRules> rules); + + ui::AcceleratorManager* accelerator_manager() { + return &accelerator_manager_; + } + + // WindowManagerImpl::Embed() forwards to this. If connected to ViewManager + // then forwards to delegate, otherwise waits for connection to establish then + // forwards. + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + + // Overridden from ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* impl) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + + private: + // TODO(sky): rename this. Connections is ambiguous. + typedef std::set<WindowManagerImpl*> Connections; + typedef std::set<mojo::Id> RegisteredViewIdSet; + + struct PendingEmbed; + class WindowManagerInternalImpl; + + mojo::ViewManager* view_manager() { + return root_ ? root_->view_manager() : nullptr; + } + + bool SetCaptureImpl(mojo::View* view); + bool FocusWindowImpl(mojo::View* view); + bool ActivateWindowImpl(mojo::View* view); + + ui::Accelerator ConvertEventToAccelerator(const ui::KeyEvent* event); + + // Creates an ViewTarget for every view in the hierarchy beneath |view|, + // and adds to the registry so that it can be retrieved later via + // GetViewTargetForViewId(). + // TODO(beng): perhaps View should have a property bag. + void RegisterSubtree(mojo::View* view); + + // Recursively invokes Unregister() for |view| and all its descendants. + void UnregisterSubtree(mojo::View* view); + + // Deletes the ViewTarget associated with the hierarchy beneath |id|, + // and removes from the registry. + void Unregister(mojo::View* view); + + // Overridden from ViewManagerDelegate: + void OnEmbed(mojo::View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override; + bool OnPerformAction(mojo::View* view, const std::string& action) override; + + // Overridden from ViewObserver: + void OnTreeChanged(const ViewObserver::TreeChangeParams& params) override; + void OnViewDestroying(mojo::View* view) override; + + // Overridden from ui::EventHandler: + void OnEvent(ui::Event* event) override; + + // Overridden from mojo::FocusControllerObserver: + void OnFocused(mojo::View* gained_focus) override; + void OnActivated(mojo::View* gained_active) override; + + // Overridden from mojo::CaptureControllerObserver: + void OnCaptureChanged(mojo::View* gained_capture) override; + + // Creates the connection to the ViewManager. + void LaunchViewManager(mojo::ApplicationImpl* app); + + // InterfaceFactory<WindowManagerInternal>: + void Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override; + + // InterfaceFactory<WindowManager>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManager> request) override; + + // InterfaceFactory<NativeViewportEventDispatcher>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> + request) override; + + // WindowManagerInternal: + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override; + void SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) override; + + mojo::Shell* shell_; + + ViewManagerDelegate* wrapped_view_manager_delegate_; + WindowManagerDelegate* window_manager_delegate_; + + mojo::ViewManagerServicePtr view_manager_service_; + scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_; + mojo::View* root_; + + scoped_ptr<FocusController> focus_controller_; + scoped_ptr<CaptureController> capture_controller_; + + ui::AcceleratorManager accelerator_manager_; + + Connections connections_; + RegisteredViewIdSet registered_view_id_set_; + + mojo::WindowManagerInternalClientPtr window_manager_client_; + + ScopedVector<PendingEmbed> pending_embeds_; + + scoped_ptr<mojo::ViewManagerClient> view_manager_client_; + + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + scoped_ptr<mojo::Binding<WindowManagerInternal>> wm_internal_binding_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerApp); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ diff --git a/mojo/services/window_manager/window_manager_app_android.cc b/mojo/services/window_manager/window_manager_app_android.cc new file mode 100644 index 0000000..84feff1 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_android.cc @@ -0,0 +1,20 @@ +// Copyright 2015 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 "mojo/services/window_manager/window_manager_app.h" + +#include <android/keycodes.h> + +#include "ui/events/keycodes/keyboard_codes_posix.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + if (event->platform_keycode() == AKEYCODE_BACK) + return ui::Accelerator(ui::VKEY_BROWSER_BACK, 0); + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app_linux.cc b/mojo/services/window_manager/window_manager_app_linux.cc new file mode 100644 index 0000000..11d43dc --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_linux.cc @@ -0,0 +1,14 @@ +// Copyright 2015 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 "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app_win.cc b/mojo/services/window_manager/window_manager_app_win.cc new file mode 100644 index 0000000..11d43dc --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_win.cc @@ -0,0 +1,14 @@ +// Copyright 2015 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 "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_apptest.cc b/mojo/services/window_manager/window_manager_apptest.cc new file mode 100644 index 0000000..12a9aca --- /dev/null +++ b/mojo/services/window_manager/window_manager_apptest.cc @@ -0,0 +1,212 @@ +// 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 "base/bind.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/public/cpp/system/macros.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +namespace mojo { +namespace { + +// TestApplication's view is embedded by the window manager. +class TestApplication : public ApplicationDelegate, public ViewManagerDelegate { + public: + TestApplication() : root_(nullptr) {} + ~TestApplication() override {} + + View* root() const { return root_; } + + void set_embed_callback(const base::Closure& callback) { + embed_callback_ = callback; + } + + private: + // ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new ViewManagerClientFactory(app->shell(), this)); + } + + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + // ViewManagerDelegate: + void OnEmbed(View* root, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override { + root_ = root; + embed_callback_.Run(); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + View* root_; + base::Closure embed_callback_; + scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestApplication); +}; + +class TestWindowManagerObserver : public WindowManagerObserver { + public: + explicit TestWindowManagerObserver( + InterfaceRequest<WindowManagerObserver> observer_request) + : binding_(this, observer_request.Pass()) {} + ~TestWindowManagerObserver() override {} + + private: + // Overridden from WindowManagerClient: + void OnCaptureChanged(Id new_capture_node_id) override {} + void OnFocusChanged(Id focused_node_id) override {} + void OnActiveWindowChanged(Id active_window) override {} + + Binding<WindowManagerObserver> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver); +}; + +class WindowManagerApplicationTest : public test::ApplicationTestBase { + public: + WindowManagerApplicationTest() {} + ~WindowManagerApplicationTest() override {} + + protected: + // ApplicationTestBase: + void SetUp() override { + ApplicationTestBase::SetUp(); + application_impl()->ConnectToService("mojo:window_manager", + &window_manager_); + } + ApplicationDelegate* GetApplicationDelegate() override { + return &test_application_; + } + + void EmbedApplicationWithURL(const std::string& url) { + window_manager_->Embed(url, nullptr, nullptr); + + base::RunLoop run_loop; + test_application_.set_embed_callback(run_loop.QuitClosure()); + run_loop.Run(); + } + + WindowManagerPtr window_manager_; + TestApplication test_application_; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(WindowManagerApplicationTest); +}; + +TEST_F(WindowManagerApplicationTest, Embed) { + EXPECT_EQ(nullptr, test_application_.root()); + EmbedApplicationWithURL(application_impl()->url()); + EXPECT_NE(nullptr, test_application_.root()); +} + +struct BoolCallback { + BoolCallback(bool* bool_value, base::RunLoop* run_loop) + : bool_value(bool_value), run_loop(run_loop) {} + + void Run(bool value) const { + *bool_value = value; + run_loop->Quit(); + } + + bool* bool_value; + base::RunLoop* run_loop; +}; + +TEST_F(WindowManagerApplicationTest, SetCaptureFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->SetCapture(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +TEST_F(WindowManagerApplicationTest, FocusWindowFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->FocusWindow(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +TEST_F(WindowManagerApplicationTest, ActivateWindowFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->ActivateWindow(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +struct FocusedAndActiveViewsCallback { + FocusedAndActiveViewsCallback(uint32* capture_view_id, + uint32* focused_view_id, + uint32* active_view_id, + base::RunLoop* run_loop) + : capture_view_id(capture_view_id), + focused_view_id(focused_view_id), + active_view_id(active_view_id), + run_loop(run_loop) { + } + + void Run(uint32 capture, uint32 focused, uint32 active) const { + *capture_view_id = capture; + *focused_view_id = focused; + *active_view_id = active; + run_loop->Quit(); + } + + uint32* capture_view_id; + uint32* focused_view_id; + uint32* active_view_id; + base::RunLoop* run_loop; +}; + +TEST_F(WindowManagerApplicationTest, GetFocusedAndActiveViewsFailsWithoutFC) { + EmbedApplicationWithURL(application_impl()->url()); + uint32 capture_view_id = -1; + uint32 focused_view_id = -1; + uint32 active_view_id = -1; + base::RunLoop run_loop; + + WindowManagerObserverPtr observer; + scoped_ptr<TestWindowManagerObserver> window_manager_observer( + new TestWindowManagerObserver(GetProxy(&observer))); + + window_manager_->GetFocusedAndActiveViews( + observer.Pass(), + FocusedAndActiveViewsCallback(&capture_view_id, + &focused_view_id, + &active_view_id, + &run_loop)); + run_loop.Run(); + // This call fails if the WindowManager does not have a FocusController. + EXPECT_EQ(0u, capture_view_id); + EXPECT_EQ(0u, focused_view_id); + EXPECT_EQ(0u, active_view_id); +} + +// TODO(msw): Write tests exercising other WindowManager functionality. + +} // namespace +} // namespace mojo diff --git a/mojo/services/window_manager/window_manager_delegate.h b/mojo/services/window_manager/window_manager_delegate.h new file mode 100644 index 0000000..277e3a8 --- /dev/null +++ b/mojo/services/window_manager/window_manager_delegate.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ + +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" + +namespace window_manager { + +class WindowManagerDelegate { + public: + // See WindowManager::Embed() for details. + virtual void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) = 0; + + protected: + virtual ~WindowManagerDelegate() {} +}; + +} // namespace mojo + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ diff --git a/mojo/services/window_manager/window_manager_impl.cc b/mojo/services/window_manager/window_manager_impl.cc new file mode 100644 index 0000000..99b3e2f --- /dev/null +++ b/mojo/services/window_manager/window_manager_impl.cc @@ -0,0 +1,98 @@ +// 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 "mojo/services/window_manager/window_manager_impl.h" + +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +using mojo::Callback; +using mojo::Id; + +namespace window_manager { + +WindowManagerImpl::WindowManagerImpl(WindowManagerApp* window_manager, + bool from_vm) + : window_manager_(window_manager), from_vm_(from_vm), binding_(this) { + window_manager_->AddConnection(this); + binding_.set_error_handler(this); +} + +WindowManagerImpl::~WindowManagerImpl() { + window_manager_->RemoveConnection(this); +} + +void WindowManagerImpl::Bind( + mojo::ScopedMessagePipeHandle window_manager_pipe) { + binding_.Bind(window_manager_pipe.Pass()); +} + +void WindowManagerImpl::NotifyViewFocused(Id focused_id) { + if (from_vm_ && observer_) + observer_->OnFocusChanged(focused_id); +} + +void WindowManagerImpl::NotifyWindowActivated(Id active_id) { + if (from_vm_ && observer_) + observer_->OnActiveWindowChanged(active_id); +} + +void WindowManagerImpl::NotifyCaptureChanged(Id capture_id) { + if (from_vm_ && observer_) + observer_->OnCaptureChanged(capture_id); +} + +void WindowManagerImpl::Embed( + const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + window_manager_->Embed(url, services.Pass(), exposed_services.Pass()); +} + +void WindowManagerImpl::SetCapture(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->SetCapture(view)); +} + +void WindowManagerImpl::FocusWindow(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->FocusWindow(view)); +} + +void WindowManagerImpl::ActivateWindow(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->ActivateWindow(view)); +} + +void WindowManagerImpl::GetFocusedAndActiveViews( + mojo::WindowManagerObserverPtr observer, + const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback) { + observer_ = observer.Pass(); + if (!window_manager_->focus_controller()) { + // TODO(sky): add typedef for 0. + callback.Run(0, 0, 0); + return; + } + mojo::View* capture_view = + window_manager_->capture_controller()->GetCapture(); + mojo::View* active_view = + window_manager_->focus_controller()->GetActiveView(); + mojo::View* focused_view = + window_manager_->focus_controller()->GetFocusedView(); + // TODO(sky): sanitize ids for client. + callback.Run(capture_view ? capture_view->id() : 0, + focused_view ? focused_view->id() : 0, + active_view ? active_view->id() : 0); +} + +void WindowManagerImpl::OnConnectionError() { + delete this; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_impl.h b/mojo/services/window_manager/window_manager_impl.h new file mode 100644 index 0000000..f51941f --- /dev/null +++ b/mojo/services/window_manager/window_manager_impl.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +namespace window_manager { + +class WindowManagerApp; + +class WindowManagerImpl : public mojo::WindowManager, + public mojo::ErrorHandler { + public: + // See description above |from_vm_| for details on |from_vm|. + // WindowManagerImpl deletes itself on connection errors. WindowManagerApp + // also deletes WindowManagerImpl in its destructor. + WindowManagerImpl(WindowManagerApp* window_manager, bool from_vm); + ~WindowManagerImpl() override; + + void Bind(mojo::ScopedMessagePipeHandle window_manager_pipe); + + void NotifyViewFocused(mojo::Id focused_id); + void NotifyWindowActivated(mojo::Id active_id); + void NotifyCaptureChanged(mojo::Id capture_id); + + private: + // mojo::WindowManager: + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + void SetCapture(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void FocusWindow(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void ActivateWindow(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void GetFocusedAndActiveViews( + mojo::WindowManagerObserverPtr observer, + const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback) + override; + + // mojo::ErrorHandler: + void OnConnectionError() override; + + WindowManagerApp* window_manager_; + + // Whether this connection originated from the ViewManager. Connections that + // originate from the view manager are expected to have clients. Connections + // that don't originate from the view manager do not have clients. + const bool from_vm_; + + mojo::Binding<mojo::WindowManager> binding_; + mojo::WindowManagerObserverPtr observer_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerImpl); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ diff --git a/mojo/services/window_manager/window_manager_test_util.cc b/mojo/services/window_manager/window_manager_test_util.cc new file mode 100644 index 0000000..7c3eb21 --- /dev/null +++ b/mojo/services/window_manager/window_manager_test_util.cc @@ -0,0 +1,39 @@ +// 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 "mojo/services/window_manager/window_manager_test_util.h" + +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "ui/gfx/geometry/rect.h" + +namespace window_manager { + +TestView::TestView(int id, const gfx::Rect& rect) + : target_(new ViewTarget(this)) { + mojo::ViewPrivate(this).set_id(id); + + mojo::Rect mojo_rect = *mojo::Rect::From(rect); + SetBounds(mojo_rect); +} + +TestView::TestView(int id, const gfx::Rect& rect, View* parent) + : TestView(id, rect) { + parent->AddChild(this); +} + +TestView::~TestView() { +} + +// static +TestView* TestView::Build(int id, const gfx::Rect& rect) { + return new TestView(id, rect); +} + +// static +TestView* TestView::Build(int id, const gfx::Rect& rect, mojo::View* parent) { + return new TestView(id, rect, parent); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_test_util.h b/mojo/services/window_manager/window_manager_test_util.h new file mode 100644 index 0000000..62424c7 --- /dev/null +++ b/mojo/services/window_manager/window_manager_test_util.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ + +#include <set> + +#include "mojo/services/window_manager/view_target.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_private.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace gfx { +class Rect; +} + +namespace window_manager { + +// A wrapper around View so we can instantiate these directly without a +// ViewManager. +class TestView : public mojo::View { + public: + TestView(int id, const gfx::Rect& rect); + TestView(int id, const gfx::Rect& rect, mojo::View* parent); + ~TestView(); + + // Builds a child view as a pointer. The caller is responsible for making + // sure that the root of any tree allocated this way is Destroy()ed. + static TestView* Build(int id, const gfx::Rect& rect); + static TestView* Build(int id, const gfx::Rect& rect, View* parent); + + ViewTarget* target() { return target_; } + + private: + ViewTarget* target_; + + DISALLOW_COPY_AND_ASSIGN(TestView); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ diff --git a/mojo/shell/BUILD.gn b/mojo/shell/BUILD.gn new file mode 100644 index 0000000..c121d57 --- /dev/null +++ b/mojo/shell/BUILD.gn @@ -0,0 +1,473 @@ +# 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. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +# We don't support building in the component build since mojo apps are +# inherently components. +assert(!is_component_build) + +group("shell") { + testonly = true + + deps = [ + ":mojo_shell", + ":tests", + ] + + if (!is_win) { + deps += [ ":mojo_launcher" ] + } + + if (is_android) { + deps += [ + ":mojo_shell_apk", + ":mojo_shell_tests_apk", + ] + } +} + +group("tests") { + testonly = true + deps = [ + ":mojo_shell_tests", + "//mojo/shell/application_manager:mojo_application_manager_unittests", + ] +} + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") +} + +executable("mojo_shell") { + sources = [] + + deps = [ + ":init", + ":lib", + "//base", + "//build/config/sanitizers:deps", + "//mojo/common", + "//mojo/environment:chromium", + ] + + if (!is_android) { + sources += [ "desktop/main.cc" ] + } else { + sources += [ + "android/library_loader.cc", + "android/main.cc", + "android/main.h", + ] + + # On android, the executable is also the native library used by the apk. + # It means dynamic symbols must be preserved and exported. + ldflags = [ "-Wl,--export-dynamic" ] + + deps += [ + ":jni_headers", + "//mojo/services/native_viewport:lib", + "//mojo/shell/application_manager", + "//ui/gl", + ] + } +} + +executable("mojo_launcher") { + sources = [ + "launcher_main.cc", + ] + + deps = [ + ":init", + ":in_process_native_runner", + "//base", + "//build/config/sanitizers:deps", + "//mojo/common", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//url", + ] +} + +source_set("init") { + sources = [ + "init.cc", + "init.h", + ] + + deps = [ + "//base", + ] +} + +source_set("in_process_native_runner") { + sources = [ + "in_process_native_runner.cc", + "in_process_native_runner.h", + ] + + public_deps = [ + ":native_application_support", + "//mojo/shell/application_manager", + ] + + deps = [ + "//base", + ] +} + +source_set("lib") { + sources = [ + "app_child_process.cc", + "app_child_process.h", + "app_child_process_host.cc", + "app_child_process_host.h", + "child_process.cc", + "child_process.h", + "child_process_host.cc", + "child_process_host.h", + "command_line_util.cc", + "command_line_util.h", + "context.cc", + "context.h", + "filename_util.cc", + "filename_util.h", + "out_of_process_native_runner.cc", + "out_of_process_native_runner.h", + "task_runners.cc", + "task_runners.h", + "url_resolver.cc", + "url_resolver.h", + ] + + deps = [ + ":app_child_process_bindings", + ":init", + ":in_process_native_runner", + ":native_application_support", + "//base", + "//base/third_party/dynamic_annotations", + "//base:base_static", + "//mojo/application", + "//mojo/common", + "//mojo/common:tracing_impl", + "//third_party/mojo/src/mojo/edk/system", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//mojo/services/network/public/interfaces", + "//mojo/shell/application_manager", + "//mojo/services/tracing:bindings", + "//url", + ] + + public_deps = [ + ":switches", + ] + + if (is_android) { + sources += [ + "android/android_handler.cc", + "android/android_handler.h", + "android/android_handler_loader.cc", + "android/android_handler_loader.h", + "android/background_application_loader.cc", + "android/background_application_loader.h", + "android/keyboard_impl.cc", + "android/keyboard_impl.h", + "android/native_viewport_application_loader.cc", + "android/native_viewport_application_loader.h", + "android/ui_application_loader_android.cc", + "android/ui_application_loader_android.h", + ] + + deps += [ + ":jni_headers", + ":run_android_application_function", + "//mojo/application:content_handler", + "//mojo/services/keyboard/public/interfaces", + "//mojo/services/gles2", + "//mojo/services/native_viewport:lib", + ] + } + + # This target includes some files behind #ifdef OS... guards. Since gn is not + # smart enough to understand preprocess includes, it does complains about + # these includes when not using the build files for that OS. Suppress checking + # so we can enable checking for the rest of the targets in this file. + # TODO: Might be better to split the files with OS-specific includes out to a + # separate source_set so we can leave checking on for the rest of the target. + check_includes = false +} + +source_set("native_application_support") { + sources = [ + "native_application_support.cc", + "native_application_support.h", + ] + + public_deps = [ + "//third_party/mojo/src/mojo/public/cpp/bindings", + ] + + deps = [ + "//base", + "//mojo/gles2", + ] + + # This target has to include the public thunk headers, which generally + # shouldn't be included without picking an implementation. We are providing + # the implementation but the thunk header target cannot declare that we are + # permitted to include it since it's in the public SDK and we are not. + # Suppress include checking so we can still check the rest of the targets in + # this file. + check_includes = false +} + +source_set("switches") { + sources = [ + "switches.cc", + "switches.h", + ] + + deps = [ + "//base", + ] +} + +if (is_android) { + generate_jni("jni_headers") { + sources = [ + "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java", + "android/apk/src/org/chromium/mojo/shell/Bootstrap.java", + "android/apk/src/org/chromium/mojo/shell/Keyboard.java", + "android/apk/src/org/chromium/mojo/shell/ShellMain.java", + "android/tests/src/org/chromium/mojo/shell/ShellTestBase.java", + ] + jni_package = "mojo/shell" + } + + android_library("bootstrap_java") { + java_files = [ "android/apk/src/org/chromium/mojo/shell/Bootstrap.java" ] + + deps = [ + "//base:base_java", + ] + + dex_path = "$target_out_dir/bootstrap_java.dex.jar" + } + + shared_library("bootstrap") { + sources = [ + "android/bootstrap.cc", + ] + deps = [ + ":jni_headers", + ":lib", + ":run_android_application_function", + "//base", + ] + } + + # Shared header between the bootstrap and the main shell .so. + source_set("run_android_application_function") { + sources = [ + "android/run_android_application_function.h", + ] + + deps = [ + "//base", + ] + } + + android_library("java") { + java_files = [ + "android/apk/src/org/chromium/mojo/shell/AndroidHandler.java", + "android/apk/src/org/chromium/mojo/shell/FileHelper.java", + "android/apk/src/org/chromium/mojo/shell/Keyboard.java", + "android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java", + "android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java", + "android/apk/src/org/chromium/mojo/shell/ShellMain.java", + ] + + deps = [ + "//base:base_java", + ] + } + + android_resources("resources") { + resource_dirs = [ "android/apk/res" ] + custom_package = "org.chromium.mojo.shell" + } + + mojo_shell_assets_dir = "$root_build_dir/mojo_shell_assets" + mojo_shell_test_assets_dir = "$root_build_dir/mojo_shell_test_assets" + + copy_ex("copy_mojo_shell_assets") { + clear_dir = true + dest = mojo_shell_assets_dir + sources = [ + "$root_out_dir/lib.stripped/libbootstrap.so", + "$root_out_dir/network_service.mojo", + "$root_out_dir/obj/mojo/shell/bootstrap_java.dex.jar", + ] + } + + copy("copy_mojo_shell") { + sources = [ + "$root_out_dir/exe.stripped/mojo_shell", + ] + outputs = [ + "$root_out_dir/lib.stripped/libmojo_shell.so", + ] + } + + copy_ex("copy_mojo_shell_test_assets") { + clear_dir = true + dest = mojo_shell_test_assets_dir + sources = [ + "$root_out_dir/test_app.mojo", + "$root_out_dir/test_request_tracker_app.mojo", + ] + } + + android_apk("mojo_shell_apk") { + apk_name = "MojoShell" + + android_manifest = "android/apk/AndroidManifest.xml" + + native_libs = [ "libmojo_shell.so" ] + + asset_location = mojo_shell_assets_dir + + deps = [ + ":copy_mojo_shell", + ":copy_mojo_shell_assets", + ":java", + ":resources", + "//base:base_java", + "//mojo/services/native_viewport:native_viewport_java", + "//third_party/android_tools:google_play_services_default_resources", + ] + } + + android_library("mojo_shell_tests_java") { + java_files = + [ "android/tests/src/org/chromium/mojo/shell/ShellTestBase.java" ] + + deps = [ + ":java", + "//base:base_java", + ] + } +} + +mojom("app_child_process_bindings") { + sources = [ + "app_child_process.mojom", + ] + + deps = [ + "//third_party/mojo/src/mojo/public/interfaces/application", + ] +} + +# GYP version: mojo/mojo.gyp:mojo_shell_tests +test("mojo_shell_tests") { + sources = [ + "app_child_process_host_unittest.cc", + "command_line_util_unittest.cc", + "data_pipe_peek_unittest.cc", + "in_process_native_runner_unittest.cc", + "native_runner_unittest.cc", + "shell_test_base.cc", + "shell_test_base.h", + "shell_test_base_android.cc", + "shell_test_base_unittest.cc", + "shell_test_main.cc", + "url_resolver_unittest.cc", + ] + + deps = [ + ":in_process_native_runner", + ":lib", + "//base", + "//base:i18n", + "//base/test:test_support", + "//testing/gtest", + "//url", + "//mojo/common", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//mojo/services/test_service:bindings", + "//mojo/shell/application_manager", + ] + + datadeps = [ + "//mojo/services/test_service:test_app", + "//mojo/services/test_service:test_request_tracker_app", + ] + + if (is_android) { + sources += [ "android/background_application_loader_unittest.cc" ] + + deps += [ ":jni_headers" ] + + apk_deps = [ + ":copy_mojo_shell_test_assets", + ":mojo_shell_tests_java", + ] + + apk_asset_location = mojo_shell_test_assets_dir + } +} + +# GYP version: mojo/mojo.gyp:mojo_shell_test_support +source_set("test_support") { + sources = [ + "shell_test_helper.cc", + "shell_test_helper.h", + ] + + deps = [ + ":init", + ":lib", + "//base", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/shell/application_manager", + ] +} + +mojo_native_application("apptests") { + output_name = "shell_apptests" + + testonly = true + + sources = [ + # TODO(jam): needs http_server service. + #"shell_apptest.cc", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/common:common", + "//third_party/mojo/src/mojo/public/cpp/bindings:callback", + "//third_party/mojo/src/mojo/public/cpp/environment", + "//third_party/mojo/src/mojo/public/cpp/system:system", + # "//mojo/services/http_server/public/cpp", + # "//mojo/services/http_server/public/interfaces", + "//mojo/services/network/public/interfaces", + "//mojo/shell/test:bindings", + ] + + #data_deps = [ "//services/http_server:http_server($default_toolchain)" ] +} diff --git a/mojo/shell/DEPS b/mojo/shell/DEPS new file mode 100644 index 0000000..04fbd79 --- /dev/null +++ b/mojo/shell/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+crypto", + "+jni", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/shell/PRESUBMIT.py b/mojo/shell/PRESUBMIT.py new file mode 100644 index 0000000..fb19030 --- /dev/null +++ b/mojo/shell/PRESUBMIT.py @@ -0,0 +1,16 @@ +# Copyright 2015 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. + +"""Presubmit script for shell. + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +def CheckChangeOnUpload(input_api, output_api): + results = [] + results += input_api.canned_checks.CheckChangeHasOnlyOneEol(input_api, + output_api) + results += input_api.canned_checks.CheckPatchFormatted(input_api, output_api) + return results diff --git a/mojo/shell/android/android_handler.cc b/mojo/shell/android/android_handler.cc new file mode 100644 index 0000000..383dc72 --- /dev/null +++ b/mojo/shell/android/android_handler.cc @@ -0,0 +1,111 @@ +// 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 "mojo/shell/android/android_handler.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/scoped_native_library.h" +#include "jni/AndroidHandler_jni.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/shell/android/run_android_application_function.h" +#include "mojo/shell/native_application_support.h" + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::GetApplicationContext; + +namespace mojo { +namespace shell { + +namespace { + +// This function loads the application library, sets the application context and +// thunks and calls into the application MojoMain. To ensure that the thunks are +// set correctly we keep it in the Mojo shell .so and pass the function pointer +// to the helper libbootstrap.so. +void RunAndroidApplication(JNIEnv* env, + jobject j_context, + const base::FilePath& app_path, + jint j_handle) { + InterfaceRequest<Application> application_request = + MakeRequest<Application>(MakeScopedHandle(MessagePipeHandle(j_handle))); + + // Load the library, so that we can set the application context there if + // needed. + // TODO(vtl): We'd use a ScopedNativeLibrary, but it doesn't have .get()! + base::NativeLibrary app_library = + LoadNativeApplication(app_path, NativeApplicationCleanup::DELETE); + if (!app_library) + return; + + // Set the application context if needed. Most applications will need to + // access the Android ApplicationContext in which they are run. If the + // application library exports the InitApplicationContext function, we will + // set it there. + const char* init_application_context_name = "InitApplicationContext"; + typedef void (*InitApplicationContextFn)( + const base::android::JavaRef<jobject>&); + InitApplicationContextFn init_application_context = + reinterpret_cast<InitApplicationContextFn>( + base::GetFunctionPointerFromNativeLibrary( + app_library, init_application_context_name)); + if (init_application_context) { + base::android::ScopedJavaLocalRef<jobject> scoped_context(env, j_context); + init_application_context(scoped_context); + } + + // Run the application. + RunNativeApplication(app_library, application_request.Pass()); + // TODO(vtl): See note about unloading and thread-local destructors above + // declaration of |LoadNativeApplication()|. + base::UnloadNativeLibrary(app_library); +} + +} // namespace + +AndroidHandler::AndroidHandler() : content_handler_factory_(this) { +} + +AndroidHandler::~AndroidHandler() { +} + +void AndroidHandler::RunApplication( + InterfaceRequest<Application> application_request, + URLResponsePtr response) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_archive_path = + Java_AndroidHandler_getNewTempArchivePath(env, GetApplicationContext()); + base::FilePath archive_path( + ConvertJavaStringToUTF8(env, j_archive_path.obj())); + + common::BlockingCopyToFile(response->body.Pass(), archive_path); + RunAndroidApplicationFn run_android_application_fn = &RunAndroidApplication; + Java_AndroidHandler_bootstrap( + env, GetApplicationContext(), j_archive_path.obj(), + application_request.PassMessagePipe().release().value(), + reinterpret_cast<jlong>(run_android_application_fn)); +} + +void AndroidHandler::Initialize(ApplicationImpl* app) { +} + +bool AndroidHandler::ConfigureIncomingConnection( + ApplicationConnection* connection) { + connection->AddService(&content_handler_factory_); + return true; +} + +bool RegisterAndroidHandlerJni(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/android_handler.h b/mojo/shell/android/android_handler.h new file mode 100644 index 0000000..868c263 --- /dev/null +++ b/mojo/shell/android/android_handler.h @@ -0,0 +1,46 @@ +// 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. + +#ifndef MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_ +#define MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_ + +#include <jni.h> + +#include "mojo/application/content_handler_factory.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" + +namespace base { +class FilePath; +} + +namespace mojo { +namespace shell { + +class AndroidHandler : public ApplicationDelegate, + public ContentHandlerFactory::Delegate { + public: + AndroidHandler(); + ~AndroidHandler(); + + private: + // ApplicationDelegate: + void Initialize(ApplicationImpl* app) override; + bool ConfigureIncomingConnection(ApplicationConnection* connection) override; + + // ContentHandlerFactory::Delegate: + void RunApplication(InterfaceRequest<Application> application_request, + URLResponsePtr response) override; + + ContentHandlerFactory content_handler_factory_; + MOJO_DISALLOW_COPY_AND_ASSIGN(AndroidHandler); +}; + +bool RegisterAndroidHandlerJni(JNIEnv* env); + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_ANDROID_CONTENT_HANDLER_H_ diff --git a/mojo/shell/android/android_handler_loader.cc b/mojo/shell/android/android_handler_loader.cc new file mode 100644 index 0000000..922dbb5 --- /dev/null +++ b/mojo/shell/android/android_handler_loader.cc @@ -0,0 +1,25 @@ +// 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 "mojo/shell/android/android_handler_loader.h" + +namespace mojo { +namespace shell { + +AndroidHandlerLoader::AndroidHandlerLoader() { +} + +AndroidHandlerLoader::~AndroidHandlerLoader() { +} + +void AndroidHandlerLoader::Load( + const GURL& url, + InterfaceRequest<Application> application_request) { + DCHECK(application_request.is_pending()); + application_.reset( + new ApplicationImpl(&android_handler_, application_request.Pass())); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/android_handler_loader.h b/mojo/shell/android/android_handler_loader.h new file mode 100644 index 0000000..91592737 --- /dev/null +++ b/mojo/shell/android/android_handler_loader.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef SHELL_ANDROID_ANDROID_HANDLER_LOADER_H_ +#define SHELL_ANDROID_ANDROID_HANDLER_LOADER_H_ + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/shell/android/android_handler.h" +#include "mojo/shell/application_manager/application_loader.h" + +namespace mojo { +namespace shell { + +class AndroidHandlerLoader : public ApplicationLoader { + public: + AndroidHandlerLoader(); + virtual ~AndroidHandlerLoader(); + + private: + // ApplicationLoader overrides: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override; + + AndroidHandler android_handler_; + scoped_ptr<ApplicationImpl> application_; + + DISALLOW_COPY_AND_ASSIGN(AndroidHandlerLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_ANDROID_HANDLER_LOADER_H_ diff --git a/mojo/shell/android/apk/AndroidManifest.xml b/mojo/shell/android/apk/AndroidManifest.xml new file mode 100644 index 0000000..0d374c9 --- /dev/null +++ b/mojo/shell/android/apk/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.chromium.mojo.shell"> + + <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="23" /> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> + + <application android:name="MojoShellApplication" + android:label="Mojo Shell"> + <meta-data android:name="com.google.android.gms.version" + android:value="@integer/google_play_services_version" /> + <activity android:name="MojoShellActivity" + android:launchMode="singleTask" + android:theme="@android:style/Theme.Holo.Light.NoActionBar" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize" + android:hardwareAccelerated="true"> + <intent-filter> + <action android:name="android.intent.action.VIEW"/> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/mojo/shell/android/apk/res/values/strings.xml b/mojo/shell/android/apk/res/values/strings.xml new file mode 100644 index 0000000..ff3f8bb --- /dev/null +++ b/mojo/shell/android/apk/res/values/strings.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- 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. + --> + +<resources> +</resources> diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java new file mode 100644 index 0000000..610ff16 --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/AndroidHandler.java @@ -0,0 +1,138 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.content.Context; +import android.util.Log; + +import dalvik.system.DexClassLoader; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; + +/** + * Content handler for archives containing native libraries bundled with Java code. + * <p> + * TODO(ppi): create a seperate instance for each application being bootstrapped to keep track of + * the temporary files and clean them up once the execution finishes. + */ +@JNINamespace("mojo::shell") +public class AndroidHandler { + private static final String TAG = "AndroidHandler"; + + // Bootstrap native and java libraries are packaged with the MojoShell APK as assets. + private static final String BOOTSTRAP_JAVA_LIBRARY = "bootstrap_java.dex.jar"; + private static final String BOOTSTRAP_NATIVE_LIBRARY = "libbootstrap.so"; + // Name of the bootstrapping runnable shipped in the packaged Java library. + private static final String BOOTSTRAP_CLASS = "org.chromium.mojo.shell.Bootstrap"; + + // File extensions used to identify application libraries in the provided archive. + private static final String JAVA_LIBRARY_SUFFIX = ".dex.jar"; + private static final String NATIVE_LIBRARY_SUFFIX = ".so"; + // Filename sections used for naming temporary files holding application files. + private static final String ARCHIVE_PREFIX = "archive"; + private static final String ARCHIVE_SUFFIX = ".zip"; + + // Directories used to hold temporary files. These are cleared when clearTemporaryFiles() is + // called. + private static final String DEX_OUTPUT_DIRECTORY = "dex_output"; + private static final String APP_DIRECTORY = "applications"; + private static final String ASSET_DIRECTORY = "assets"; + + /** + * Deletes directories holding the temporary files. This should be called early on shell startup + * to clean up after the previous run. + */ + static void clearTemporaryFiles(Context context) { + FileHelper.deleteRecursively(getDexOutputDir(context)); + FileHelper.deleteRecursively(getAppDir(context)); + FileHelper.deleteRecursively(getAssetDir(context)); + } + + /** + * Returns the path at which the native part should save the application archive. + */ + @CalledByNative + private static String getNewTempArchivePath(Context context) throws IOException { + return File.createTempFile(ARCHIVE_PREFIX, ARCHIVE_SUFFIX, + getAppDir(context)).getAbsolutePath(); + } + + /** + * Extracts and runs the application libraries contained by the indicated archive. + * @param context the application context + * @param archivePath the path of the archive containing the application to be run + * @param handle handle to the shell to be passed to the native application. On the Java side + * this is opaque payload. + * @param runApplicationPtr pointer to the function that will set the native thunks and call + * into the application MojoMain. On the Java side this is opaque + * payload. + */ + @CalledByNative + private static boolean bootstrap(Context context, String archivePath, int handle, + long runApplicationPtr) { + File bootstrap_java_library; + File bootstrap_native_library; + try { + bootstrap_java_library = FileHelper.extractFromAssets(context, BOOTSTRAP_JAVA_LIBRARY, + getAssetDir(context), true); + bootstrap_native_library = FileHelper.extractFromAssets(context, + BOOTSTRAP_NATIVE_LIBRARY, getAssetDir(context), true); + } catch (Exception e) { + Log.e(TAG, "Extraction of bootstrap files from assets failed.", e); + return false; + } + + File application_java_library; + File application_native_library; + try { + File archive = new File(archivePath); + application_java_library = FileHelper.extractFromArchive(archive, JAVA_LIBRARY_SUFFIX, + getAppDir(context)); + application_native_library = FileHelper.extractFromArchive(archive, + NATIVE_LIBRARY_SUFFIX, getAppDir(context)); + } catch (Exception e) { + Log.e(TAG, "Extraction of application files from the archive failed.", e); + return false; + } + + String dexPath = bootstrap_java_library.getAbsolutePath() + File.pathSeparator + + application_java_library.getAbsolutePath(); + DexClassLoader bootstrapLoader = new DexClassLoader(dexPath, + getDexOutputDir(context).getAbsolutePath(), null, + ClassLoader.getSystemClassLoader()); + + try { + Class<?> loadedClass = bootstrapLoader.loadClass(BOOTSTRAP_CLASS); + Class<? extends Runnable> bootstrapClass = loadedClass.asSubclass(Runnable.class); + Constructor<? extends Runnable> constructor = bootstrapClass.getConstructor( + Context.class, File.class, File.class, Integer.class, Long.class); + Runnable bootstrapRunnable = constructor.newInstance(context, bootstrap_native_library, + application_native_library, Integer.valueOf(handle), + Long.valueOf(runApplicationPtr)); + bootstrapRunnable.run(); + } catch (Throwable t) { + Log.e(TAG, "Running Bootstrap failed.", t); + return false; + } + return true; + } + + private static File getDexOutputDir(Context context) { + return context.getDir(DEX_OUTPUT_DIRECTORY, Context.MODE_PRIVATE); + } + + private static File getAppDir(Context context) { + return context.getDir(APP_DIRECTORY, Context.MODE_PRIVATE); + } + + private static File getAssetDir(Context context) { + return context.getDir(ASSET_DIRECTORY, Context.MODE_PRIVATE); + } +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java new file mode 100644 index 0000000..9916c83 --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Bootstrap.java @@ -0,0 +1,45 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.content.Context; + +import org.chromium.base.JNINamespace; + +import java.io.File; + +/** + * Runnable used to bootstrap execution of Android Mojo application. For the JNI to work, we need a + * Java class with the application classloader in the call stack. We load this class in the + * application classloader and call into native from it to achieve that. + */ +@JNINamespace("mojo::shell") +public class Bootstrap implements Runnable { + private final Context mContext; + private final File mBootstrapNativeLibrary; + private final File mApplicationNativeLibrary; + private final int mHandle; + private final long mRunApplicationPtr; + + public Bootstrap(Context context, File bootstrapNativeLibrary, File applicationNativeLibrary, + Integer handle, Long runApplicationPtr) { + mContext = context; + mBootstrapNativeLibrary = bootstrapNativeLibrary; + mApplicationNativeLibrary = applicationNativeLibrary; + mHandle = handle; + mRunApplicationPtr = runApplicationPtr; + } + + @Override + public void run() { + System.load(mBootstrapNativeLibrary.getAbsolutePath()); + System.load(mApplicationNativeLibrary.getAbsolutePath()); + nativeBootstrap(mContext, mApplicationNativeLibrary.getAbsolutePath(), mHandle, + mRunApplicationPtr); + } + + native void nativeBootstrap(Context context, String libraryPath, int handle, + long runApplicationPtr); +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java new file mode 100644 index 0000000..c455251 --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/FileHelper.java @@ -0,0 +1,179 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Helper methods for file extraction from APK assets and zip archives. + */ +class FileHelper { + public static final String TAG = "MojoFileHelper"; + + // Size of the buffer used in streaming file operations. + private static final int BUFFER_SIZE = 1024 * 1024; + // Prefix used when naming temporary files. + private static final String TEMP_FILE_PREFIX = "temp-"; + // Prefix used when naming timestamp files. + private static final String TIMESTAMP_PREFIX = "asset_timestamp-"; + + /** + * Looks for a timestamp file on disk that indicates the version of the APK that the resource + * assets were extracted from. Returns null if a timestamp was found and it indicates that the + * resources match the current APK. Otherwise returns a String that represents the filename of a + * timestamp to create. + */ + private static String checkAssetTimestamp(Context context, File outputDir) { + PackageManager pm = context.getPackageManager(); + PackageInfo pi = null; + + try { + pi = pm.getPackageInfo(context.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + return TIMESTAMP_PREFIX; + } + + if (pi == null) { + return TIMESTAMP_PREFIX; + } + + String expectedTimestamp = TIMESTAMP_PREFIX + pi.versionCode + "-" + pi.lastUpdateTime; + + String[] timestamps = outputDir.list(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.startsWith(TIMESTAMP_PREFIX); + } + }); + + if (timestamps.length != 1) { + // If there's no timestamp, nuke to be safe as we can't tell the age of the files. + // If there's multiple timestamps, something's gone wrong so nuke. + return expectedTimestamp; + } + + if (!expectedTimestamp.equals(timestamps[0])) { + return expectedTimestamp; + } + + // Timestamp file is already up-to date. + return null; + } + + public static File extractFromAssets(Context context, String assetName, File outputDirectory, + boolean useTempFile) throws IOException, FileNotFoundException { + String timestampToCreate = null; + if (!useTempFile) { + timestampToCreate = checkAssetTimestamp(context, outputDirectory); + if (timestampToCreate != null) { + for (File child : outputDirectory.listFiles()) { + deleteRecursively(child); + } + } + } + + File outputFile; + if (useTempFile) { + // Make the original filename part of the temp file name. + // TODO(ppi): do we need to sanitize the suffix? + String suffix = "-" + assetName; + outputFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, outputDirectory); + } else { + outputFile = new File(outputDirectory, assetName); + if (outputFile.exists()) { + return outputFile; + } + } + + BufferedInputStream inputStream = new BufferedInputStream( + context.getAssets().open(assetName)); + try { + writeStreamToFile(inputStream, outputFile); + } finally { + inputStream.close(); + } + + if (timestampToCreate != null) { + try { + new File(outputDirectory, timestampToCreate).createNewFile(); + } catch (IOException e) { + // In the worst case we don't write a timestamp, so we'll re-extract the asset next + // time. + Log.w(TAG, "Failed to write asset timestamp!"); + } + } + + return outputFile; + } + + /** + * Extracts the file of the given extension from the archive. Throws FileNotFoundException if no + * matching file is found. + */ + static File extractFromArchive(File archive, String suffixToMatch, + File outputDirectory) throws IOException, FileNotFoundException { + ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream( + archive))); + ZipEntry entry; + while ((entry = zip.getNextEntry()) != null) { + if (entry.getName().endsWith(suffixToMatch)) { + // Make the original filename part of the temp file name. + // TODO(ppi): do we need to sanitize the suffix? + String suffix = "-" + new File(entry.getName()).getName(); + File extractedFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, + outputDirectory); + writeStreamToFile(zip, extractedFile); + zip.close(); + return extractedFile; + } + } + zip.close(); + throw new FileNotFoundException(); + } + + /** + * Deletes a file or directory. Directory will be deleted even if not empty. + */ + static void deleteRecursively(File file) { + if (file.isDirectory()) { + for (File child : file.listFiles()) { + deleteRecursively(child); + } + } + if (!file.delete()) { + Log.w(TAG, "Unable to delete file: " + file.getAbsolutePath()); + } + } + + private static void writeStreamToFile(InputStream inputStream, File outputFile) + throws IOException { + byte[] buffer = new byte[BUFFER_SIZE]; + OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(outputFile)); + try { + int read; + while ((read = inputStream.read(buffer, 0, BUFFER_SIZE)) > 0) { + outputStream.write(buffer, 0, read); + } + } finally { + outputStream.close(); + } + } +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java new file mode 100644 index 0000000..e13ddea --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/Keyboard.java @@ -0,0 +1,35 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +/** + * Interaction with the keyboard. + */ +@JNINamespace("mojo::shell") +public class Keyboard { + @CalledByNative + private static void showSoftKeyboard(Activity activity) { + View v = activity.getCurrentFocus(); + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT); + } + + @CalledByNative + private static void hideSoftKeyboard(Activity activity) { + View v = activity.getCurrentFocus(); + InputMethodManager imm = + (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getApplicationWindowToken(), 0); + } +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java new file mode 100644 index 0000000..a56c43d --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellActivity.java @@ -0,0 +1,67 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.JsonReader; +import android.util.Log; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Activity for managing the Mojo Shell. + */ +public class MojoShellActivity extends Activity { + private static final String TAG = "MojoShellActivity"; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // TODO(ppi): Gotcha - the call below will work only once per process lifetime, but the OS + // has no obligation to kill the application process between destroying and restarting the + // activity. If the application process is kept alive, initialization parameters sent with + // the intent will be stale. + // TODO(qsr): We should be passing application context here as required by + // InitApplicationContext on the native side. Currently we can't, as PlatformViewportAndroid + // relies on this being the activity context. + ShellMain.ensureInitialized(this, getParametersFromIntent(getIntent())); + + // TODO(eseidel): ShellMain can fail, but we're ignoring the return. + ShellMain.start(); + } + + private static String[] getParametersFromIntent(Intent intent) { + if (intent == null) { + return null; + } + String[] parameters = intent.getStringArrayExtra("parameters"); + if (parameters != null) { + return parameters; + } + String encodedParameters = intent.getStringExtra("encodedParameters"); + if (encodedParameters != null) { + JsonReader reader = new JsonReader(new StringReader(encodedParameters)); + List<String> parametersList = new ArrayList<String>(); + try { + reader.beginArray(); + while (reader.hasNext()) { + parametersList.add(reader.nextString()); + } + reader.endArray(); + reader.close(); + return parametersList.toArray(new String[parametersList.size()]); + } catch (IOException e) { + Log.w(TAG, e.getMessage(), e); + } + } + return null; + } +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java new file mode 100644 index 0000000..f160260 --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/MojoShellApplication.java @@ -0,0 +1,57 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.util.Log; + +import org.chromium.base.BaseChromiumApplication; +import org.chromium.base.PathUtils; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; +import org.chromium.base.library_loader.ProcessInitException; + +/** + * MojoShell implementation of {@link android.app.Application}, managing application-level global + * state and initializations. + */ +public class MojoShellApplication extends BaseChromiumApplication { + private static final String TAG = "MojoShellApplication"; + private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "mojo_shell"; + + @Override + public void onCreate() { + super.onCreate(); + clearTemporaryFiles(); + initializeJavaUtils(); + initializeNative(); + } + + /** + * Deletes the temporary files and directories created in the previous run of the application. + * This is important regardless of cleanups on exit, as the previous run could have crashed. + */ + private void clearTemporaryFiles() { + AndroidHandler.clearTemporaryFiles(this); + } + + /** + * Initializes Java-side utils. + */ + private void initializeJavaUtils() { + PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX); + } + + /** + * Loads the native library. + */ + private void initializeNative() { + try { + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized(); + } catch (ProcessInitException e) { + Log.e(TAG, "libmojo_shell initialization failed.", e); + throw new RuntimeException(e); + } + } +} diff --git a/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java b/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java new file mode 100644 index 0000000..1f55fa2 --- /dev/null +++ b/mojo/shell/android/apk/src/org/chromium/mojo/shell/ShellMain.java @@ -0,0 +1,105 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * A placeholder class to call native functions. + **/ +@JNINamespace("mojo::shell") +public class ShellMain { + private static final String TAG = "ShellMain"; + + // Directory where applications bundled with the shell will be extracted. + private static final String LOCAL_APP_DIRECTORY = "local_apps"; + // Individual applications bundled with the shell as assets. + private static final String NETWORK_LIBRARY_APP = "network_service.mojo"; + // The mojo_shell library is also an executable run in forked processes when running + // multi-process. + private static final String MOJO_SHELL_EXECUTABLE = "libmojo_shell.so"; + + /** + * A guard flag for calling nativeInit() only once. + **/ + private static boolean sInitialized = false; + + /** + * Initializes the native system. + **/ + static void ensureInitialized(Context applicationContext, String[] parameters) { + if (sInitialized) return; + try { + FileHelper.extractFromAssets(applicationContext, NETWORK_LIBRARY_APP, + getLocalAppsDir(applicationContext), false); + File mojoShell = new File(applicationContext.getApplicationInfo().nativeLibraryDir, + MOJO_SHELL_EXECUTABLE); + + List<String> parametersList = new ArrayList<String>(); + // Program name. + if (parameters != null) { + parametersList.addAll(Arrays.asList(parameters)); + } + + nativeInit(applicationContext, mojoShell.getAbsolutePath(), + parametersList.toArray(new String[parametersList.size()]), + getLocalAppsDir(applicationContext).getAbsolutePath(), + getTmpDir(applicationContext).getAbsolutePath()); + sInitialized = true; + } catch (Exception e) { + Log.e(TAG, "ShellMain initialization failed.", e); + throw new RuntimeException(e); + } + } + + /** + * Starts the specified application in the specified context. + * + * @return <code>true</code> if an application has been launched. + **/ + static boolean start() { + return nativeStart(); + } + + /** + * Adds the given URL to the set of mojo applications to run on start. + */ + static void addApplicationURL(String url) { + nativeAddApplicationURL(url); + } + + private static File getLocalAppsDir(Context context) { + return context.getDir(LOCAL_APP_DIRECTORY, Context.MODE_PRIVATE); + } + + private static File getTmpDir(Context context) { + return new File(context.getCacheDir(), "tmp"); + } + + @CalledByNative + private static void finishActivity(Activity activity) { + activity.finish(); + } + + /** + * Initializes the native system. This API should be called only once per process. + **/ + private static native void nativeInit(Context context, String mojoShellPath, + String[] parameters, String bundledAppsDirectory, String tmpDir); + + private static native boolean nativeStart(); + + private static native void nativeAddApplicationURL(String url); +} diff --git a/mojo/shell/android/background_application_loader.cc b/mojo/shell/android/background_application_loader.cc new file mode 100644 index 0000000..cdb175e --- /dev/null +++ b/mojo/shell/android/background_application_loader.cc @@ -0,0 +1,73 @@ +// 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 "mojo/shell/android/background_application_loader.h" + +#include "base/bind.h" +#include "base/run_loop.h" +#include "mojo/shell/application_manager/application_manager.h" + +namespace mojo { +namespace shell { + +BackgroundApplicationLoader::BackgroundApplicationLoader( + scoped_ptr<ApplicationLoader> real_loader, + const std::string& thread_name, + base::MessageLoop::Type message_loop_type) + : loader_(real_loader.Pass()), + message_loop_type_(message_loop_type), + thread_name_(thread_name), + message_loop_created_(true, false) { +} + +BackgroundApplicationLoader::~BackgroundApplicationLoader() { + if (thread_) + thread_->Join(); +} + +void BackgroundApplicationLoader::Load( + const GURL& url, + InterfaceRequest<Application> application_request) { + DCHECK(application_request.is_pending()); + if (!thread_) { + // TODO(tim): It'd be nice if we could just have each Load call + // result in a new thread like DynamicService{Loader, Runner}. But some + // loaders are creating multiple ApplicationImpls (NetworkApplicationLoader) + // sharing a delegate (etc). So we have to keep it single threaded, wait + // for the thread to initialize, and post to the TaskRunner for subsequent + // Load calls for now. + thread_.reset(new base::DelegateSimpleThread(this, thread_name_)); + thread_->Start(); + message_loop_created_.Wait(); + DCHECK(task_runner_.get()); + } + + task_runner_->PostTask( + FROM_HERE, + base::Bind(&BackgroundApplicationLoader::LoadOnBackgroundThread, + base::Unretained(this), url, + base::Passed(&application_request))); +} + +void BackgroundApplicationLoader::Run() { + base::MessageLoop message_loop(message_loop_type_); + base::RunLoop loop; + task_runner_ = message_loop.task_runner(); + quit_closure_ = loop.QuitClosure(); + message_loop_created_.Signal(); + loop.Run(); + + // Destroy |loader_| on the thread it's actually used on. + loader_.reset(); +} + +void BackgroundApplicationLoader::LoadOnBackgroundThread( + const GURL& url, + InterfaceRequest<Application> application_request) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + loader_->Load(url, application_request.Pass()); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/background_application_loader.h b/mojo/shell/android/background_application_loader.h new file mode 100644 index 0000000..7c549f7 --- /dev/null +++ b/mojo/shell/android/background_application_loader.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_ +#define SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/simple_thread.h" +#include "mojo/shell/application_manager/application_loader.h" + +namespace mojo { +namespace shell { + +class BackgroundApplicationLoader + : public ApplicationLoader, + public base::DelegateSimpleThread::Delegate { + public: + BackgroundApplicationLoader(scoped_ptr<ApplicationLoader> real_loader, + const std::string& thread_name, + base::MessageLoop::Type message_loop_type); + ~BackgroundApplicationLoader() override; + + // ApplicationLoader overrides: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override; + + private: + // |base::DelegateSimpleThread::Delegate| method: + void Run() override; + + // These functions are exected on the background thread. They call through + // to |background_loader_| to do the actual loading. + void LoadOnBackgroundThread( + const GURL& url, + InterfaceRequest<Application> application_request); + bool quit_on_shutdown_; + scoped_ptr<ApplicationLoader> loader_; + + const base::MessageLoop::Type message_loop_type_; + const std::string thread_name_; + + // Created on |thread_| during construction of |this|. Protected against + // uninitialized use by |message_loop_created_|, and protected against + // use-after-free by holding a reference to the thread-safe object. Note + // that holding a reference won't hold |thread_| from exiting. + scoped_refptr<base::TaskRunner> task_runner_; + base::WaitableEvent message_loop_created_; + + // Lives on |thread_|. + base::Closure quit_closure_; + + scoped_ptr<base::DelegateSimpleThread> thread_; + + DISALLOW_COPY_AND_ASSIGN(BackgroundApplicationLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_BACKGROUND_APPLICATION_LOADER_H_ diff --git a/mojo/shell/android/background_application_loader_unittest.cc b/mojo/shell/android/background_application_loader_unittest.cc new file mode 100644 index 0000000..4d18073 --- /dev/null +++ b/mojo/shell/android/background_application_loader_unittest.cc @@ -0,0 +1,51 @@ +// 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 "mojo/shell/android/background_application_loader.h" + +#include "mojo/public/interfaces/application/application.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +class DummyLoader : public ApplicationLoader { + public: + DummyLoader() : simulate_app_quit_(true) {} + ~DummyLoader() override {} + + // ApplicationLoader overrides: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override { + if (simulate_app_quit_) + base::MessageLoop::current()->Quit(); + } + + void DontSimulateAppQuit() { simulate_app_quit_ = false; } + + private: + bool simulate_app_quit_; +}; + +// Tests that the loader can start and stop gracefully. +TEST(BackgroundApplicationLoaderTest, StartStop) { + scoped_ptr<ApplicationLoader> real_loader(new DummyLoader()); + BackgroundApplicationLoader loader(real_loader.Pass(), "test", + base::MessageLoop::TYPE_DEFAULT); +} + +// Tests that the loader can load a service that is well behaved (quits +// itself). +TEST(BackgroundApplicationLoaderTest, Load) { + scoped_ptr<ApplicationLoader> real_loader(new DummyLoader()); + BackgroundApplicationLoader loader(real_loader.Pass(), "test", + base::MessageLoop::TYPE_DEFAULT); + ApplicationPtr application; + loader.Load(GURL(), GetProxy(&application)); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/bootstrap.cc b/mojo/shell/android/bootstrap.cc new file mode 100644 index 0000000..4fcae37 --- /dev/null +++ b/mojo/shell/android/bootstrap.cc @@ -0,0 +1,43 @@ +// 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 "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "jni/Bootstrap_jni.h" +#include "mojo/shell/android/run_android_application_function.h" + +namespace mojo { +namespace shell { + +void Bootstrap(JNIEnv* env, + jobject, + jobject j_context, + jstring j_native_library_path, + jint j_handle, + jlong j_run_application_ptr) { + base::FilePath app_path( + base::android::ConvertJavaStringToUTF8(env, j_native_library_path)); + RunAndroidApplicationFn run_android_application_fn = + reinterpret_cast<RunAndroidApplicationFn>(j_run_application_ptr); + run_android_application_fn(env, j_context, app_path, j_handle); +} + +bool RegisterBootstrapJni(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace shell +} // namespace mojo + +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + + if (!mojo::shell::RegisterBootstrapJni(env)) + return -1; + + return JNI_VERSION_1_4; +} diff --git a/mojo/shell/android/keyboard_impl.cc b/mojo/shell/android/keyboard_impl.cc new file mode 100644 index 0000000..60336bb --- /dev/null +++ b/mojo/shell/android/keyboard_impl.cc @@ -0,0 +1,36 @@ +// Copyright 2015 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 "mojo/shell/android/keyboard_impl.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "jni/Keyboard_jni.h" + +namespace mojo { +namespace shell { + +KeyboardImpl::KeyboardImpl(InterfaceRequest<Keyboard> request) + : binding_(this, request.Pass()) { +} + +KeyboardImpl::~KeyboardImpl() { +} + +void KeyboardImpl::Show() { + Java_Keyboard_showSoftKeyboard(base::android::AttachCurrentThread(), + base::android::GetApplicationContext()); +} + +void KeyboardImpl::Hide() { + Java_Keyboard_hideSoftKeyboard(base::android::AttachCurrentThread(), + base::android::GetApplicationContext()); +} + +bool RegisterKeyboardJni(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/keyboard_impl.h b/mojo/shell/android/keyboard_impl.h new file mode 100644 index 0000000..e40b7dd --- /dev/null +++ b/mojo/shell/android/keyboard_impl.h @@ -0,0 +1,35 @@ +// Copyright 2015 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. + +#ifndef SHELL_ANDROID_KEYBOARD_IMPL_H_ +#define SHELL_ANDROID_KEYBOARD_IMPL_H_ + +#include <jni.h> + +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/services/keyboard/public/interfaces/keyboard.mojom.h" + +namespace mojo { +namespace shell { + +class KeyboardImpl : public Keyboard { + public: + KeyboardImpl(InterfaceRequest<Keyboard> request); + ~KeyboardImpl(); + + // Keyboard implementation + void Show() override; + void Hide() override; + + private: + StrongBinding<Keyboard> binding_; +}; + +bool RegisterKeyboardJni(JNIEnv* env); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_KEYBOARD_IMPL_H_ diff --git a/mojo/shell/android/library_loader.cc b/mojo/shell/android/library_loader.cc new file mode 100644 index 0000000..cd3f7d2 --- /dev/null +++ b/mojo/shell/android/library_loader.cc @@ -0,0 +1,46 @@ +// 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. + +#include "base/android/base_jni_onload.h" +#include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/bind.h" +#include "mojo/services/native_viewport/platform_viewport_android.h" +#include "mojo/shell/android/android_handler.h" +#include "mojo/shell/android/keyboard_impl.h" +#include "mojo/shell/android/main.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + {"AndroidHandler", mojo::shell::RegisterAndroidHandlerJni}, + {"Keyboard", mojo::shell::RegisterKeyboardJni}, + {"PlatformViewportAndroid", + native_viewport::PlatformViewportAndroid::Register}, + {"ShellMain", mojo::shell::RegisterShellMain}, +}; + +bool RegisterJNI(JNIEnv* env) { + if (!base::android::RegisterJni(env)) + return false; + + return RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +} // namespace + +// This is called by the VM when the shared library is first loaded. +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + std::vector<base::android::RegisterCallback> register_callbacks; + register_callbacks.push_back(base::Bind(&RegisterJNI)); + if (!base::android::OnJNIOnLoadRegisterJNI(vm, register_callbacks) || + !base::android::OnJNIOnLoadInit( + std::vector<base::android::InitCallback>())) { + return -1; + } + + return JNI_VERSION_1_4; +} diff --git a/mojo/shell/android/main.cc b/mojo/shell/android/main.cc new file mode 100644 index 0000000..41c507a --- /dev/null +++ b/mojo/shell/android/main.cc @@ -0,0 +1,222 @@ +// 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. + +#include "mojo/shell/android/main.h" + +#include "base/android/fifo_utils.h" +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/simple_thread.h" +#include "jni/ShellMain_jni.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/shell/android/android_handler_loader.h" +#include "mojo/shell/android/background_application_loader.h" +#include "mojo/shell/android/native_viewport_application_loader.h" +#include "mojo/shell/android/ui_application_loader_android.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "mojo/shell/command_line_util.h" +#include "mojo/shell/context.h" +#include "mojo/shell/init.h" +#include "ui/gl/gl_surface_egl.h" + +using base::LazyInstance; + +namespace mojo { +namespace shell { + +namespace { + +// Tag for logging. +const char kLogTag[] = "chromium"; + +// Command line argument for the communication fifo. +const char kFifoPath[] = "fifo-path"; + +class MojoShellRunner : public base::DelegateSimpleThread::Delegate { + public: + MojoShellRunner(const std::vector<std::string>& parameters) + : parameters_(parameters) {} + ~MojoShellRunner() override {} + + private: + void Run() override; + + std::vector<std::string> parameters_; + + DISALLOW_COPY_AND_ASSIGN(MojoShellRunner); +}; + +LazyInstance<scoped_ptr<base::MessageLoop>> g_java_message_loop = + LAZY_INSTANCE_INITIALIZER; + +LazyInstance<scoped_ptr<Context>> g_context = LAZY_INSTANCE_INITIALIZER; + +LazyInstance<scoped_ptr<MojoShellRunner>> g_shell_runner = + LAZY_INSTANCE_INITIALIZER; + +LazyInstance<scoped_ptr<base::DelegateSimpleThread>> g_shell_thread = + LAZY_INSTANCE_INITIALIZER; + +LazyInstance<base::android::ScopedJavaGlobalRef<jobject>> g_main_activiy = + LAZY_INSTANCE_INITIALIZER; + +void ConfigureAndroidServices(Context* context) { + context->application_manager()->SetLoaderForURL( + make_scoped_ptr(new UIApplicationLoader( + make_scoped_ptr(new NativeViewportApplicationLoader()), + g_java_message_loop.Get().get())), + GURL("mojo:native_viewport_service")); + + // Android handler is bundled with the Mojo shell, because it uses the + // MojoShell application as the JNI bridge to bootstrap execution of other + // Android Mojo apps that need JNI. + context->application_manager()->SetLoaderForURL( + make_scoped_ptr(new BackgroundApplicationLoader( + make_scoped_ptr(new AndroidHandlerLoader()), "android_handler", + base::MessageLoop::TYPE_DEFAULT)), + GURL("mojo:android_handler")); + + // By default, the keyboard is handled by the native_viewport_service. + context->url_resolver()->AddURLMapping(GURL("mojo:keyboard"), + GURL("mojo:native_viewport_service")); +} + +void QuitShellThread() { + g_shell_thread.Get()->Join(); + g_shell_thread.Pointer()->reset(); + Java_ShellMain_finishActivity(base::android::AttachCurrentThread(), + g_main_activiy.Get().obj()); + exit(0); +} + +void MojoShellRunner::Run() { + base::MessageLoop loop(common::MessagePumpMojo::Create()); + Context* context = g_context.Pointer()->get(); + ConfigureAndroidServices(context); + context->Init(); + + for (auto& args : parameters_) + ApplyApplicationArgs(context, args); + + RunCommandLineApps(context); + loop.Run(); + + g_java_message_loop.Pointer()->get()->PostTask(FROM_HERE, + base::Bind(&QuitShellThread)); +} + +// Initialize stdout redirection if the command line switch is present. +void InitializeRedirection() { + if (!base::CommandLine::ForCurrentProcess()->HasSwitch(kFifoPath)) + return; + + base::FilePath fifo_path = + base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(kFifoPath); + base::FilePath directory = fifo_path.DirName(); + CHECK(base::CreateDirectoryAndGetError(directory, nullptr)) + << "Unable to create directory: " << directory.value(); + unlink(fifo_path.value().c_str()); + CHECK(base::android::CreateFIFO(fifo_path, 0666)) + << "Unable to create fifo: " << fifo_path.value(); + CHECK(base::android::RedirectStream(stdout, fifo_path, "w")) + << "Failed to redirect stdout to file: " << fifo_path.value(); + CHECK(dup2(STDOUT_FILENO, STDERR_FILENO) != -1) + << "Unable to redirect stderr to stdout."; +} + +} // namespace + +static void Init(JNIEnv* env, + jclass clazz, + jobject activity, + jstring mojo_shell_path, + jobjectArray jparameters, + jstring j_local_apps_directory, + jstring j_tmp_dir) { + g_main_activiy.Get().Reset(env, activity); + + // Setting the TMPDIR environment variable so that applications can use it. + // TODO(qsr) We will need our subprocesses to inherit this. + int return_value = + setenv("TMPDIR", + base::android::ConvertJavaStringToUTF8(env, j_tmp_dir).c_str(), 1); + DCHECK_EQ(return_value, 0); + + base::android::ScopedJavaLocalRef<jobject> scoped_activity(env, activity); + base::android::InitApplicationContext(env, scoped_activity); + + std::vector<std::string> parameters; + parameters.push_back( + base::android::ConvertJavaStringToUTF8(env, mojo_shell_path)); + base::android::AppendJavaStringArrayToStringVector(env, jparameters, + ¶meters); + base::CommandLine::Init(0, nullptr); + base::CommandLine::ForCurrentProcess()->InitFromArgv(parameters); + g_shell_runner.Get().reset(new MojoShellRunner(parameters)); + + InitializeLogging(); + + InitializeRedirection(); + + // We want ~MessageLoop to happen prior to ~Context. Initializing + // LazyInstances is akin to stack-allocating objects; their destructors + // will be invoked first-in-last-out. + Context* shell_context = new Context(); + shell_context->SetShellFileRoot(base::FilePath( + base::android::ConvertJavaStringToUTF8(env, j_local_apps_directory))); + g_context.Get().reset(shell_context); + + g_java_message_loop.Get().reset(new base::MessageLoopForUI); + base::MessageLoopForUI::current()->Start(); + + // TODO(abarth): At which point should we switch to cross-platform + // initialization? + + gfx::GLSurface::InitializeOneOff(); +} + +static jboolean Start(JNIEnv* env, jclass clazz) { + if (!base::CommandLine::ForCurrentProcess()->GetArgs().size()) + return false; + +#if defined(MOJO_SHELL_DEBUG_URL) + base::CommandLine::ForCurrentProcess()->AppendArg(MOJO_SHELL_DEBUG_URL); + // Sleep for 5 seconds to give the debugger a chance to attach. + sleep(5); +#endif + + g_shell_thread.Get().reset(new base::DelegateSimpleThread( + g_shell_runner.Get().get(), "ShellThread")); + g_shell_thread.Get()->Start(); + return true; +} + +static void AddApplicationURL(JNIEnv* env, jclass clazz, jstring jurl) { + base::CommandLine::ForCurrentProcess()->AppendArg( + base::android::ConvertJavaStringToUTF8(env, jurl)); +} + +bool RegisterShellMain(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace shell +} // namespace mojo + +// TODO(vtl): Even though main() should never be called, mojo_shell fails to +// link without it. Figure out if we can avoid this. +int main(int argc, char** argv) { + NOTREACHED(); +} diff --git a/mojo/shell/android/main.h b/mojo/shell/android/main.h new file mode 100644 index 0000000..43ea16a --- /dev/null +++ b/mojo/shell/android/main.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef SHELL_ANDROID_MAIN_H_ +#define SHELL_ANDROID_MAIN_H_ + +#include <jni.h> + +namespace mojo { +namespace shell { + +bool RegisterShellMain(JNIEnv* env); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_MAIN_H_ diff --git a/mojo/shell/android/native_viewport_application_loader.cc b/mojo/shell/android/native_viewport_application_loader.cc new file mode 100644 index 0000000..e3e3767 --- /dev/null +++ b/mojo/shell/android/native_viewport_application_loader.cc @@ -0,0 +1,58 @@ +// 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 "mojo/shell/android/native_viewport_application_loader.h" + +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/gles2/gpu_state.h" +#include "mojo/services/native_viewport/native_viewport_impl.h" +#include "mojo/shell/android/keyboard_impl.h" + +namespace mojo { +namespace shell { + +NativeViewportApplicationLoader::NativeViewportApplicationLoader() { +} + +NativeViewportApplicationLoader::~NativeViewportApplicationLoader() { +} + +void NativeViewportApplicationLoader::Load( + const GURL& url, + InterfaceRequest<Application> application_request) { + DCHECK(application_request.is_pending()); + app_.reset(new ApplicationImpl(this, application_request.Pass())); +} + +bool NativeViewportApplicationLoader::ConfigureIncomingConnection( + ApplicationConnection* connection) { + connection->AddService<NativeViewport>(this); + connection->AddService<Gpu>(this); + connection->AddService<Keyboard>(this); + return true; +} + +void NativeViewportApplicationLoader::Create( + ApplicationConnection* connection, + InterfaceRequest<NativeViewport> request) { + if (!gpu_state_) + gpu_state_ = new gles2::GpuState; + new native_viewport::NativeViewportImpl(false, gpu_state_, request.Pass()); +} + +void NativeViewportApplicationLoader::Create( + ApplicationConnection* connection, + InterfaceRequest<Keyboard> request) { + new KeyboardImpl(request.Pass()); +} + +void NativeViewportApplicationLoader::Create(ApplicationConnection* connection, + InterfaceRequest<Gpu> request) { + if (!gpu_state_) + gpu_state_ = new gles2::GpuState; + new gles2::GpuImpl(request.Pass(), gpu_state_); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/native_viewport_application_loader.h b/mojo/shell/android/native_viewport_application_loader.h new file mode 100644 index 0000000..ece77e1 --- /dev/null +++ b/mojo/shell/android/native_viewport_application_loader.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_ +#define MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_ + +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/services/gles2/gpu_impl.h" +#include "mojo/services/keyboard/public/interfaces/keyboard.mojom.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" + +namespace gles2 { +class GpuState; +} + +namespace mojo { + +class ApplicationImpl; + +namespace shell { + +class NativeViewportApplicationLoader : public ApplicationLoader, + public ApplicationDelegate, + public InterfaceFactory<Keyboard>, + public InterfaceFactory<NativeViewport>, + public InterfaceFactory<Gpu> { + public: + NativeViewportApplicationLoader(); + ~NativeViewportApplicationLoader(); + + private: + // ApplicationLoader implementation. + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override; + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override; + + // InterfaceFactory<NativeViewport> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<NativeViewport> request) override; + + // InterfaceFactory<Gpu> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<Gpu> request) override; + + // InterfaceFactory<Keyboard> implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<Keyboard> request) override; + + scoped_refptr<gles2::GpuState> gpu_state_; + scoped_ptr<ApplicationImpl> app_; + + DISALLOW_COPY_AND_ASSIGN(NativeViewportApplicationLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // MOJO_SHELL_ANDROID_NATIVE_VIEWPORT_APPLICATION_LOADER_H_ diff --git a/mojo/shell/android/run_android_application_function.h b/mojo/shell/android/run_android_application_function.h new file mode 100644 index 0000000..3ed1744 --- /dev/null +++ b/mojo/shell/android/run_android_application_function.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_ +#define SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_ + +#include "base/android/jni_android.h" +#include "base/files/file_path.h" + +namespace mojo { +namespace shell { + +// Type of the function that we inject from the main .so of the Mojo shell to +// the helper libbootstrap.so. This function will set the thunks in the +// application .so and call into application MojoMain. Injecting the function +// from the main .so ensures that the thunks are set correctly. + +typedef void (*RunAndroidApplicationFn)(JNIEnv* env, + jobject j_context, + const base::FilePath& app_path, + jint j_handle); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_RUN_ANDROID_APPLICATION_FUNCTION_H_ diff --git a/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java b/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java new file mode 100644 index 0000000..2aba46f --- /dev/null +++ b/mojo/shell/android/tests/src/org/chromium/mojo/shell/ShellTestBase.java @@ -0,0 +1,40 @@ +// 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. + +package org.chromium.mojo.shell; + +import android.content.Context; +import android.content.res.AssetManager; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +import java.io.File; +import java.io.IOException; + +/** + * Helper method for ShellTestBase. + */ +@JNINamespace("mojo::shell::test") +public class ShellTestBase { + // Directory where applications bundled with the tests will be extracted. + private static final String TEST_APP_DIRECTORY = "test_apps"; + + /** + * Extracts the mojo applications from the apk assets and returns the directory where they are. + */ + @CalledByNative + private static String extractMojoApplications(Context context) throws IOException { + final File outputDirectory = context.getDir(TEST_APP_DIRECTORY, Context.MODE_PRIVATE); + + AssetManager manager = context.getResources().getAssets(); + for (String asset : manager.list("")) { + if (asset.endsWith(".mojo")) { + FileHelper.extractFromAssets(context, asset, outputDirectory, false); + } + } + + return outputDirectory.getAbsolutePath(); + } +} diff --git a/mojo/shell/android/ui_application_loader_android.cc b/mojo/shell/android/ui_application_loader_android.cc new file mode 100644 index 0000000..87f686f --- /dev/null +++ b/mojo/shell/android/ui_application_loader_android.cc @@ -0,0 +1,48 @@ +// 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 "mojo/shell/android/ui_application_loader_android.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "mojo/shell/application_manager/application_manager.h" + +namespace mojo { +namespace shell { + +UIApplicationLoader::UIApplicationLoader( + scoped_ptr<ApplicationLoader> real_loader, + base::MessageLoop* ui_message_loop) + : loader_(real_loader.Pass()), ui_message_loop_(ui_message_loop) { +} + +UIApplicationLoader::~UIApplicationLoader() { + ui_message_loop_->PostTask( + FROM_HERE, base::Bind(&UIApplicationLoader::ShutdownOnUIThread, + base::Unretained(this))); +} + +void UIApplicationLoader::Load( + const GURL& url, + InterfaceRequest<Application> application_request) { + DCHECK(application_request.is_pending()); + ui_message_loop_->PostTask( + FROM_HERE, + base::Bind(&UIApplicationLoader::LoadOnUIThread, base::Unretained(this), + url, base::Passed(&application_request))); +} + +void UIApplicationLoader::LoadOnUIThread( + const GURL& url, + InterfaceRequest<Application> application_request) { + loader_->Load(url, application_request.Pass()); +} + +void UIApplicationLoader::ShutdownOnUIThread() { + // Destroy |loader_| on the thread it's actually used on. + loader_.reset(); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/android/ui_application_loader_android.h b/mojo/shell/android/ui_application_loader_android.h new file mode 100644 index 0000000..37cc063 --- /dev/null +++ b/mojo/shell/android/ui_application_loader_android.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_ +#define SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/shell/application_manager/application_loader.h" + +namespace base { +class MessageLoop; +} + +namespace mojo { +namespace shell { + +class ApplicationManager; + +// ApplicationLoader implementation that creates a background thread and issues +// load +// requests there. +class UIApplicationLoader : public ApplicationLoader { + public: + UIApplicationLoader(scoped_ptr<ApplicationLoader> real_loader, + base::MessageLoop* ui_message_loop); + ~UIApplicationLoader() override; + + // ApplicationLoader overrides: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override; + + private: + class UILoader; + + // These functions are exected on the background thread. They call through + // to |background_loader_| to do the actual loading. + // TODO: having this code take a |manager| is fragile (as ApplicationManager + // isn't thread safe). + void LoadOnUIThread(const GURL& url, + InterfaceRequest<Application> application_request); + void ShutdownOnUIThread(); + + scoped_ptr<ApplicationLoader> loader_; + base::MessageLoop* ui_message_loop_; + + DISALLOW_COPY_AND_ASSIGN(UIApplicationLoader); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_ANDROID_UI_APPLICATION_LOADER_ANDROID_H_ diff --git a/mojo/shell/app_child_process.cc b/mojo/shell/app_child_process.cc new file mode 100644 index 0000000..546c28c --- /dev/null +++ b/mojo/shell/app_child_process.cc @@ -0,0 +1,302 @@ +// 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 "mojo/shell/app_child_process.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/process_delegate.h" +#include "mojo/edk/embedder/simple_platform_support.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/shell/app_child_process.mojom.h" +#include "mojo/shell/native_application_support.h" + +namespace mojo { +namespace shell { + +namespace { + +// Blocker --------------------------------------------------------------------- + +// Blocks a thread until another thread unblocks it, at which point it unblocks +// and runs a closure provided by that thread. +class Blocker { + public: + class Unblocker { + public: + explicit Unblocker(Blocker* blocker = nullptr) : blocker_(blocker) {} + ~Unblocker() {} + + void Unblock(base::Closure run_after) { + DCHECK(blocker_); + DCHECK(blocker_->run_after_.is_null()); + blocker_->run_after_ = run_after; + blocker_->event_.Signal(); + blocker_ = nullptr; + } + + private: + Blocker* blocker_; + + // Copy and assign allowed. + }; + + Blocker() : event_(true, false) {} + ~Blocker() {} + + void Block() { + DCHECK(run_after_.is_null()); + event_.Wait(); + run_after_.Run(); + } + + Unblocker GetUnblocker() { return Unblocker(this); } + + private: + base::WaitableEvent event_; + base::Closure run_after_; + + DISALLOW_COPY_AND_ASSIGN(Blocker); +}; + +// AppContext ------------------------------------------------------------------ + +class AppChildControllerImpl; + +// Should be created and initialized on the main thread. +class AppContext : public embedder::ProcessDelegate { + public: + AppContext() + : io_thread_("io_thread"), controller_thread_("controller_thread") {} + ~AppContext() override {} + + void Init() { + // Initialize Mojo before starting any threads. + embedder::Init(make_scoped_ptr(new embedder::SimplePlatformSupport())); + + // Create and start our I/O thread. + base::Thread::Options io_thread_options(base::MessageLoop::TYPE_IO, 0); + CHECK(io_thread_.StartWithOptions(io_thread_options)); + io_runner_ = io_thread_.message_loop_proxy().get(); + CHECK(io_runner_.get()); + + // Create and start our controller thread. + base::Thread::Options controller_thread_options; + controller_thread_options.message_loop_type = + base::MessageLoop::TYPE_CUSTOM; + controller_thread_options.message_pump_factory = + base::Bind(&common::MessagePumpMojo::Create); + CHECK(controller_thread_.StartWithOptions(controller_thread_options)); + controller_runner_ = controller_thread_.message_loop_proxy().get(); + CHECK(controller_runner_.get()); + + // TODO(vtl): This should be SLAVE, not NONE. + embedder::InitIPCSupport(embedder::ProcessType::NONE, controller_runner_, + this, io_runner_, + embedder::ScopedPlatformHandle()); + } + + void Shutdown() { + Blocker blocker; + shutdown_unblocker_ = blocker.GetUnblocker(); + controller_runner_->PostTask( + FROM_HERE, base::Bind(&AppContext::ShutdownOnControllerThread, + base::Unretained(this))); + blocker.Block(); + } + + base::SingleThreadTaskRunner* io_runner() const { return io_runner_.get(); } + + base::SingleThreadTaskRunner* controller_runner() const { + return controller_runner_.get(); + } + + AppChildControllerImpl* controller() const { return controller_.get(); } + + void set_controller(scoped_ptr<AppChildControllerImpl> controller) { + controller_ = controller.Pass(); + } + + private: + void ShutdownOnControllerThread() { + // First, destroy the controller. + controller_.reset(); + + // Next shutdown IPC. We'll unblock the main thread in OnShutdownComplete(). + embedder::ShutdownIPCSupport(); + } + + // ProcessDelegate implementation. + void OnShutdownComplete() override { + shutdown_unblocker_.Unblock(base::Closure()); + } + + base::Thread io_thread_; + scoped_refptr<base::SingleThreadTaskRunner> io_runner_; + + base::Thread controller_thread_; + scoped_refptr<base::SingleThreadTaskRunner> controller_runner_; + + // Accessed only on the controller thread. + scoped_ptr<AppChildControllerImpl> controller_; + + // Used to unblock the main thread on shutdown. + Blocker::Unblocker shutdown_unblocker_; + + DISALLOW_COPY_AND_ASSIGN(AppContext); +}; + +// AppChildControllerImpl ------------------------------------------------------ + +class AppChildControllerImpl : public AppChildController, public ErrorHandler { + public: + ~AppChildControllerImpl() override { + DCHECK(thread_checker_.CalledOnValidThread()); + + // TODO(vtl): Pass in the result from |MainMain()|. + on_app_complete_.Run(MOJO_RESULT_UNIMPLEMENTED); + } + + // To be executed on the controller thread. Creates the |AppChildController|, + // etc. + static void Init(AppContext* app_context, + embedder::ScopedPlatformHandle platform_channel, + const Blocker::Unblocker& unblocker) { + DCHECK(app_context); + DCHECK(platform_channel.is_valid()); + + DCHECK(!app_context->controller()); + + scoped_ptr<AppChildControllerImpl> impl( + new AppChildControllerImpl(app_context, unblocker)); + + ScopedMessagePipeHandle host_message_pipe(embedder::CreateChannel( + platform_channel.Pass(), app_context->io_runner(), + base::Bind(&AppChildControllerImpl::DidCreateChannel, + base::Unretained(impl.get())), + base::MessageLoopProxy::current())); + + impl->Bind(host_message_pipe.Pass()); + + app_context->set_controller(impl.Pass()); + } + + void Bind(ScopedMessagePipeHandle handle) { binding_.Bind(handle.Pass()); } + + // |ErrorHandler| methods: + void OnConnectionError() override { + // A connection error means the connection to the shell is lost. This is not + // recoverable. + LOG(ERROR) << "Connection error to the shell."; + _exit(1); + } + + // |AppChildController| methods: + void StartApp(const String& app_path, + bool clean_app_path, + InterfaceRequest<Application> application_request, + const StartAppCallback& on_app_complete) override { + DVLOG(2) << "AppChildControllerImpl::StartApp(" << app_path << ", ...)"; + DCHECK(thread_checker_.CalledOnValidThread()); + + on_app_complete_ = on_app_complete; + unblocker_.Unblock(base::Bind(&AppChildControllerImpl::StartAppOnMainThread, + base::FilePath::FromUTF8Unsafe(app_path), + clean_app_path + ? NativeApplicationCleanup::DELETE + : NativeApplicationCleanup::DONT_DELETE, + base::Passed(&application_request))); + } + + void ExitNow(int32_t exit_code) override { + DVLOG(2) << "AppChildControllerImpl::ExitNow(" << exit_code << ")"; + _exit(exit_code); + } + + private: + AppChildControllerImpl(AppContext* app_context, + const Blocker::Unblocker& unblocker) + : app_context_(app_context), + unblocker_(unblocker), + channel_info_(nullptr), + binding_(this) { + binding_.set_error_handler(this); + } + + // Callback for |embedder::CreateChannel()|. + void DidCreateChannel(embedder::ChannelInfo* channel_info) { + DVLOG(2) << "AppChildControllerImpl::DidCreateChannel()"; + DCHECK(thread_checker_.CalledOnValidThread()); + channel_info_ = channel_info; + } + + static void StartAppOnMainThread( + const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request) { + // TODO(vtl): This is copied from in_process_native_runner.cc. + DVLOG(2) << "Loading/running Mojo app from " << app_path.value() + << " out of process"; + + // We intentionally don't unload the native library as its lifetime is the + // same as that of the process. + base::NativeLibrary app_library = LoadNativeApplication(app_path, cleanup); + RunNativeApplication(app_library, application_request.Pass()); + } + + base::ThreadChecker thread_checker_; + AppContext* const app_context_; + Blocker::Unblocker unblocker_; + StartAppCallback on_app_complete_; + + embedder::ChannelInfo* channel_info_; + Binding<AppChildController> binding_; + + DISALLOW_COPY_AND_ASSIGN(AppChildControllerImpl); +}; + +} // namespace + +// AppChildProcess ------------------------------------------------------------- + +AppChildProcess::AppChildProcess() { +} + +AppChildProcess::~AppChildProcess() { +} + +void AppChildProcess::Main() { + DVLOG(2) << "AppChildProcess::Main()"; + + DCHECK(!base::MessageLoop::current()); + + AppContext app_context; + app_context.Init(); + + Blocker blocker; + app_context.controller_runner()->PostTask( + FROM_HERE, + base::Bind(&AppChildControllerImpl::Init, base::Unretained(&app_context), + base::Passed(platform_channel()), blocker.GetUnblocker())); + // This will block, then run whatever the controller wants. + blocker.Block(); + + app_context.Shutdown(); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/app_child_process.h b/mojo/shell/app_child_process.h new file mode 100644 index 0000000..d63afda --- /dev/null +++ b/mojo/shell/app_child_process.h @@ -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. + +#ifndef SHELL_APP_CHILD_PROCESS_H_ +#define SHELL_APP_CHILD_PROCESS_H_ + +#include "base/macros.h" +#include "mojo/shell/child_process.h" + +namespace mojo { +namespace shell { + +// An implementation of |ChildProcess| for an app child process, which runs a +// single app (loaded from the file system) on its main thread. +class AppChildProcess : public ChildProcess { + public: + AppChildProcess(); + ~AppChildProcess() override; + + void Main() override; + + private: + DISALLOW_COPY_AND_ASSIGN(AppChildProcess); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APP_CHILD_PROCESS_H_ diff --git a/mojo/shell/app_child_process.mojom b/mojo/shell/app_child_process.mojom new file mode 100644 index 0000000..4b7480d --- /dev/null +++ b/mojo/shell/app_child_process.mojom @@ -0,0 +1,17 @@ +// 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. + +module mojo.shell; + +import "mojo/public/interfaces/application/application.mojom"; + +interface AppChildController { + // Starts the app at the given path (deleting it if |clean_app_path| is true). + StartApp(string app_path, + bool clean_app_path, + mojo.Application& application_request) => (int32 result); + + // Exits the child process now (with no cleanup), with the given exit code. + ExitNow(int32 exit_code); +}; diff --git a/mojo/shell/app_child_process_host.cc b/mojo/shell/app_child_process_host.cc new file mode 100644 index 0000000..bf290ac --- /dev/null +++ b/mojo/shell/app_child_process_host.cc @@ -0,0 +1,84 @@ +// 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 "mojo/shell/app_child_process_host.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/shell/context.h" +#include "mojo/shell/task_runners.h" + +namespace mojo { +namespace shell { + +AppChildProcessHost::AppChildProcessHost(Context* context) + : ChildProcessHost(context), channel_info_(nullptr) { +} + +AppChildProcessHost::~AppChildProcessHost() { +} + +void AppChildProcessHost::StartApp( + const String& app_path, + bool clean_app_path, + InterfaceRequest<Application> application_request, + const AppChildController::StartAppCallback& on_app_complete) { + DCHECK(controller_); + + on_app_complete_ = on_app_complete; + controller_->StartApp( + app_path, clean_app_path, application_request.Pass(), + base::Bind(&AppChildProcessHost::AppCompleted, base::Unretained(this))); +} + +void AppChildProcessHost::ExitNow(int32_t exit_code) { + DCHECK(controller_); + + controller_->ExitNow(exit_code); +} + +void AppChildProcessHost::WillStart() { + DCHECK(platform_channel()->is_valid()); + + ScopedMessagePipeHandle handle(embedder::CreateChannel( + platform_channel()->Pass(), context()->task_runners()->io_runner(), + base::Bind(&AppChildProcessHost::DidCreateChannel, + base::Unretained(this)), + base::MessageLoop::current()->message_loop_proxy())); + + controller_.Bind(handle.Pass()); +} + +void AppChildProcessHost::DidStart(bool success) { + DVLOG(2) << "AppChildProcessHost::DidStart()"; + + if (!success) { + LOG(ERROR) << "Failed to start app child process"; + AppCompleted(MOJO_RESULT_UNKNOWN); + return; + } +} + +// Callback for |embedder::CreateChannel()|. +void AppChildProcessHost::DidCreateChannel( + embedder::ChannelInfo* channel_info) { + DVLOG(2) << "AppChildProcessHost::DidCreateChannel()"; + + CHECK(channel_info); + channel_info_ = channel_info; +} + +void AppChildProcessHost::AppCompleted(int32_t result) { + if (!on_app_complete_.is_null()) { + auto on_app_complete = on_app_complete_; + on_app_complete_.reset(); + on_app_complete.Run(result); + } +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/app_child_process_host.h b/mojo/shell/app_child_process_host.h new file mode 100644 index 0000000..451fd7e --- /dev/null +++ b/mojo/shell/app_child_process_host.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef SHELL_APP_CHILD_PROCESS_HOST_H_ +#define SHELL_APP_CHILD_PROCESS_HOST_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "mojo/edk/embedder/channel_info_forward.h" +#include "mojo/shell/app_child_process.mojom.h" +#include "mojo/shell/child_process_host.h" + +namespace mojo { +namespace shell { + +// A subclass of |ChildProcessHost| to host an app child process, which runs a +// single app (loaded from the file system). +// +// Note: After |Start()|, |StartApp| must be called and this object must +// remained alive until the |on_app_complete| callback is called. +class AppChildProcessHost : public ChildProcessHost { + public: + explicit AppChildProcessHost(Context* context); + ~AppChildProcessHost() override; + + // See |AppChildController|: + void StartApp(const String& app_path, + bool clean_app_path, + InterfaceRequest<Application> application_request, + const AppChildController::StartAppCallback& on_app_complete); + void ExitNow(int32_t exit_code); + + // |ChildProcessHost| methods: + void WillStart() override; + void DidStart(bool success) override; + + private: + // Callback for |embedder::CreateChannel()|. + void DidCreateChannel(embedder::ChannelInfo* channel_info); + + void AppCompleted(int32_t result); + + AppChildControllerPtr controller_; + embedder::ChannelInfo* channel_info_; + AppChildController::StartAppCallback on_app_complete_; + + DISALLOW_COPY_AND_ASSIGN(AppChildProcessHost); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APP_CHILD_PROCESS_HOST_H_ diff --git a/mojo/shell/app_child_process_host_unittest.cc b/mojo/shell/app_child_process_host_unittest.cc new file mode 100644 index 0000000..37624bf --- /dev/null +++ b/mojo/shell/app_child_process_host_unittest.cc @@ -0,0 +1,63 @@ +// 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. + +// Note: This file also tests app_child_process.*. + +#include "mojo/shell/app_child_process_host.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "mojo/common/message_pump_mojo.h" +#include "mojo/shell/context.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +// Subclass just so we can observe |DidStart()|. +class TestAppChildProcessHost : public AppChildProcessHost { + public: + explicit TestAppChildProcessHost(Context* context) + : AppChildProcessHost(context) {} + ~TestAppChildProcessHost() override {} + + void DidStart(bool success) override { + EXPECT_TRUE(success); + AppChildProcessHost::DidStart(success); + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestAppChildProcessHost); +}; + +#if defined(OS_ANDROID) +// TODO(qsr): Multiprocess shell tests are not supported on android. +#define MAYBE_StartJoin DISABLED_StartJoin +#else +#define MAYBE_StartJoin StartJoin +#endif // defined(OS_ANDROID) +// Just tests starting the child process and joining it (without starting an +// app). +TEST(AppChildProcessHostTest, MAYBE_StartJoin) { + Context context; + base::MessageLoop message_loop( + scoped_ptr<base::MessagePump>(new common::MessagePumpMojo())); + context.Init(); + TestAppChildProcessHost app_child_process_host(&context); + app_child_process_host.Start(); + message_loop.Run(); + app_child_process_host.ExitNow(123); + int exit_code = app_child_process_host.Join(); + VLOG(2) << "Joined child: exit_code = " << exit_code; + EXPECT_EQ(123, exit_code); + + context.Shutdown(); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/BUILD.gn b/mojo/shell/application_manager/BUILD.gn new file mode 100644 index 0000000..f220b43 --- /dev/null +++ b/mojo/shell/application_manager/BUILD.gn @@ -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. + +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +source_set("application_manager") { + output_name = "mojo_application_manager" + sources = [ + "application_loader.h", + "application_manager.cc", + "application_manager.h", + "data_pipe_peek.cc", + "data_pipe_peek.h", + "fetcher.cc", + "fetcher.h", + "identity.cc", + "identity.h", + "local_fetcher.cc", + "local_fetcher.h", + "native_runner.h", + "network_fetcher.cc", + "network_fetcher.h", + "query_util.cc", + "query_util.h", + "shell_impl.cc", + "shell_impl.h", + ] + + public_deps = [ + "//base", + "//mojo/common", + "//third_party/mojo/src/mojo/public/interfaces/application:application", + "//mojo/services/network/public/interfaces", + "//url", + ] + deps = [ + "//base/third_party/dynamic_annotations", + "//crypto:crypto", + "//url", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//third_party/mojo_services/src/content_handler/public/interfaces", + "//mojo/shell:native_application_support", + "//mojo/shell:switches", + ] +} + +test("mojo_application_manager_unittests") { + sources = [ + "application_manager_unittest.cc", + "query_util_unittest.cc", + ] + + deps = [ + ":application_manager", + ":test_bindings", + "//base", + "//mojo/application", + "//mojo/common", + "//third_party/mojo/src/mojo/edk/test:run_all_unittests", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo/src/mojo/public/cpp/bindings", + "//testing/gtest", + "//url", + ] +} + +mojom("test_bindings") { + sources = [ + "test.mojom", + ] +} diff --git a/mojo/shell/application_manager/application_loader.h b/mojo/shell/application_manager/application_loader.h new file mode 100644 index 0000000..11feb38 --- /dev/null +++ b/mojo/shell/application_manager/application_loader.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ +#define SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ + +#include "base/callback.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/interfaces/application/shell.mojom.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "url/gurl.h" + +namespace mojo { + +class Application; + +namespace shell { + +class ApplicationManager; + +// Interface to implement special application loading behavior for a particular +// URL or scheme. +class ApplicationLoader { + public: + virtual ~ApplicationLoader() {} + + virtual void Load(const GURL& url, + InterfaceRequest<Application> application_request) = 0; + + protected: + ApplicationLoader() {} +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_APPLICATION_LOADER_H_ diff --git a/mojo/shell/application_manager/application_manager.cc b/mojo/shell/application_manager/application_manager.cc new file mode 100644 index 0000000..d137fb8 --- /dev/null +++ b/mojo/shell/application_manager/application_manager.cc @@ -0,0 +1,523 @@ +// 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 "mojo/shell/application_manager/application_manager.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/trace_event/trace_event.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/shell/application_manager/fetcher.h" +#include "mojo/shell/application_manager/local_fetcher.h" +#include "mojo/shell/application_manager/network_fetcher.h" +#include "mojo/shell/application_manager/query_util.h" +#include "mojo/shell/application_manager/shell_impl.h" +#include "mojo/shell/switches.h" +#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" + +namespace mojo { +namespace shell { + +namespace { + +// Used by TestAPI. +bool has_created_instance = false; + +std::vector<std::string> Concatenate(const std::vector<std::string>& v1, + const std::vector<std::string>& v2) { + if (!v1.size()) + return v2; + if (!v2.size()) + return v1; + std::vector<std::string> result(v1); + result.insert(result.end(), v1.begin(), v1.end()); + return result; +} + +} // namespace + +ApplicationManager::Delegate::~Delegate() { +} + +GURL ApplicationManager::Delegate::ResolveURL(const GURL& url) { + return url; +} + +GURL ApplicationManager::Delegate::ResolveMappings(const GURL& url) { + return url; +} + +class ApplicationManager::ContentHandlerConnection : public ErrorHandler { + public: + ContentHandlerConnection(ApplicationManager* manager, + const GURL& content_handler_url) + : manager_(manager), content_handler_url_(content_handler_url) { + ServiceProviderPtr services; + manager->ConnectToApplication(content_handler_url, GURL(), + GetProxy(&services), nullptr, + base::Closure()); + MessagePipe pipe; + content_handler_.Bind(pipe.handle0.Pass()); + services->ConnectToService(ContentHandler::Name_, pipe.handle1.Pass()); + content_handler_.set_error_handler(this); + } + + ContentHandler* content_handler() { return content_handler_.get(); } + + GURL content_handler_url() { return content_handler_url_; } + + private: + // ErrorHandler implementation: + void OnConnectionError() override { manager_->OnContentHandlerError(this); } + + ApplicationManager* manager_; + GURL content_handler_url_; + ContentHandlerPtr content_handler_; + + DISALLOW_COPY_AND_ASSIGN(ContentHandlerConnection); +}; + +// static +ApplicationManager::TestAPI::TestAPI(ApplicationManager* manager) + : manager_(manager) { +} + +ApplicationManager::TestAPI::~TestAPI() { +} + +bool ApplicationManager::TestAPI::HasCreatedInstance() { + return has_created_instance; +} + +bool ApplicationManager::TestAPI::HasFactoryForURL(const GURL& url) const { + return manager_->identity_to_shell_impl_.find(Identity(url)) != + manager_->identity_to_shell_impl_.end(); +} + +ApplicationManager::ApplicationManager(Delegate* delegate) + : delegate_(delegate), weak_ptr_factory_(this) { +} + +ApplicationManager::~ApplicationManager() { + STLDeleteValues(&url_to_content_handler_); + TerminateShellConnections(); + STLDeleteValues(&url_to_loader_); + STLDeleteValues(&scheme_to_loader_); +} + +void ApplicationManager::TerminateShellConnections() { + STLDeleteValues(&identity_to_shell_impl_); +} + +void ApplicationManager::ConnectToApplication( + const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end) { + ConnectToApplicationWithParameters( + requested_url, requestor_url, services.Pass(), exposed_services.Pass(), + on_application_end, std::vector<std::string>()); +} + +void ApplicationManager::ConnectToApplicationWithParameters( + const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& pre_redirect_parameters) { + TRACE_EVENT_INSTANT1( + "mojo_shell", "ApplicationManager::ConnectToApplicationWithParameters", + TRACE_EVENT_SCOPE_THREAD, "requested_url", requested_url.spec()); + DCHECK(requested_url.is_valid()); + + // We check both the mapped and resolved urls for existing shell_impls because + // external applications can be registered for the unresolved mojo:foo urls. + + GURL mapped_url = delegate_->ResolveMappings(requested_url); + if (ConnectToRunningApplication(mapped_url, requestor_url, &services, + &exposed_services)) { + return; + } + + GURL resolved_url = delegate_->ResolveURL(mapped_url); + if (ConnectToRunningApplication(resolved_url, requestor_url, &services, + &exposed_services)) { + return; + } + + // The application is not running, let's compute the parameters. + std::vector<std::string> parameters = + Concatenate(pre_redirect_parameters, GetArgsForURL(resolved_url)); + + if (ConnectToApplicationWithLoader(mapped_url, requestor_url, &services, + &exposed_services, on_application_end, + parameters, GetLoaderForURL(mapped_url))) { + return; + } + + if (ConnectToApplicationWithLoader( + resolved_url, requestor_url, &services, &exposed_services, + on_application_end, parameters, GetLoaderForURL(resolved_url))) { + return; + } + + if (ConnectToApplicationWithLoader(resolved_url, requestor_url, &services, + &exposed_services, on_application_end, + parameters, default_loader_.get())) { + return; + } + + auto callback = base::Bind( + &ApplicationManager::HandleFetchCallback, weak_ptr_factory_.GetWeakPtr(), + requestor_url, base::Passed(services.Pass()), + base::Passed(exposed_services.Pass()), on_application_end, + parameters); + + if (resolved_url.SchemeIsFile()) { + new LocalFetcher( + resolved_url, GetBaseURLAndQuery(resolved_url, nullptr), + base::Bind(callback, NativeApplicationCleanup::DONT_DELETE)); + return; + } + + if (!network_service_) + ConnectToService(GURL("mojo:network_service"), &network_service_); + + const NativeApplicationCleanup cleanup = + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDontDeleteOnDownload) + ? NativeApplicationCleanup::DONT_DELETE + : NativeApplicationCleanup::DELETE; + + new NetworkFetcher(disable_cache_, resolved_url, network_service_.get(), + base::Bind(callback, cleanup)); +} + +bool ApplicationManager::ConnectToRunningApplication( + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services) { + GURL application_url = GetBaseURLAndQuery(resolved_url, nullptr); + ShellImpl* shell_impl = GetShellImpl(application_url); + if (!shell_impl) + return false; + + ConnectToClient(shell_impl, resolved_url, requestor_url, services->Pass(), + exposed_services->Pass()); + return true; +} + +bool ApplicationManager::ConnectToApplicationWithLoader( + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters, + ApplicationLoader* loader) { + if (!loader) + return false; + + loader->Load( + resolved_url, + RegisterShell(resolved_url, requestor_url, services->Pass(), + exposed_services->Pass(), on_application_end, parameters)); + return true; +} + +InterfaceRequest<Application> ApplicationManager::RegisterShell( + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters) { + Identity app_identity(resolved_url); + + ApplicationPtr application; + InterfaceRequest<Application> application_request = GetProxy(&application); + ShellImpl* shell = + new ShellImpl(application.Pass(), this, app_identity, on_application_end); + identity_to_shell_impl_[app_identity] = shell; + shell->InitializeApplication(Array<String>::From(parameters)); + ConnectToClient(shell, resolved_url, requestor_url, services.Pass(), + exposed_services.Pass()); + return application_request.Pass(); +} + +ShellImpl* ApplicationManager::GetShellImpl(const GURL& url) { + const auto& shell_it = identity_to_shell_impl_.find(Identity(url)); + if (shell_it != identity_to_shell_impl_.end()) + return shell_it->second; + return nullptr; +} + +void ApplicationManager::ConnectToClient( + ShellImpl* shell_impl, + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + shell_impl->ConnectToClient(resolved_url, requestor_url, services.Pass(), + exposed_services.Pass()); +} + +void ApplicationManager::HandleFetchCallback( + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters, + NativeApplicationCleanup cleanup, + scoped_ptr<Fetcher> fetcher) { + if (!fetcher) { + // Network error. Drop |application_request| to tell requestor. + return; + } + + GURL redirect_url = fetcher->GetRedirectURL(); + if (!redirect_url.is_empty()) { + // And around we go again... Whee! + ConnectToApplicationWithParameters(redirect_url, requestor_url, + services.Pass(), exposed_services.Pass(), + on_application_end, parameters); + return; + } + + // We already checked if the application was running before we fetched it, but + // it might have started while the fetch was outstanding. We don't want to + // have two copies of the app running, so check again. + // + // Also, it's possible the original URL was redirected to an app that is + // already running. + if (ConnectToRunningApplication(fetcher->GetURL(), requestor_url, &services, + &exposed_services)) { + return; + } + + InterfaceRequest<Application> request( + RegisterShell(fetcher->GetURL(), requestor_url, services.Pass(), + exposed_services.Pass(), on_application_end, parameters)); + + // If the response begins with a #!mojo <content-handler-url>, use it. + GURL content_handler_url; + std::string shebang; + if (fetcher->PeekContentHandler(&shebang, &content_handler_url)) { + LoadWithContentHandler( + content_handler_url, request.Pass(), + fetcher->AsURLResponse(blocking_pool_, + static_cast<int>(shebang.size()))); + return; + } + + MimeTypeToURLMap::iterator iter = mime_type_to_url_.find(fetcher->MimeType()); + if (iter != mime_type_to_url_.end()) { + LoadWithContentHandler(iter->second, request.Pass(), + fetcher->AsURLResponse(blocking_pool_, 0)); + return; + } + + // TODO(aa): Sanity check that the thing we got looks vaguely like a mojo + // application. That could either mean looking for the platform-specific dll + // header, or looking for some specific mojo signature prepended to the + // library. + // TODO(vtl): (Maybe this should be done by the factory/runner?) + + GURL base_resolved_url = GetBaseURLAndQuery(fetcher->GetURL(), nullptr); + NativeRunnerFactory::Options options; + if (url_to_native_options_.find(base_resolved_url) != + url_to_native_options_.end()) { + DVLOG(2) << "Applying stored native options to resolved URL " + << fetcher->GetURL(); + options = url_to_native_options_[base_resolved_url]; + } + + fetcher->AsPath( + blocking_pool_, + base::Bind(&ApplicationManager::RunNativeApplication, + weak_ptr_factory_.GetWeakPtr(), base::Passed(request.Pass()), + options, cleanup, base::Passed(fetcher.Pass()))); +} + +void ApplicationManager::RunNativeApplication( + InterfaceRequest<Application> application_request, + const NativeRunnerFactory::Options& options, + NativeApplicationCleanup cleanup, + scoped_ptr<Fetcher> fetcher, + const base::FilePath& path, + bool path_exists) { + // We only passed fetcher to keep it alive. Done with it now. + fetcher.reset(); + + DCHECK(application_request.is_pending()); + + if (!path_exists) { + LOG(ERROR) << "Library not started because library path '" << path.value() + << "' does not exist."; + return; + } + + TRACE_EVENT1("mojo_shell", "ApplicationManager::RunNativeApplication", "path", + path.AsUTF8Unsafe()); + NativeRunner* runner = native_runner_factory_->Create(options).release(); + native_runners_.push_back(runner); + runner->Start(path, cleanup, application_request.Pass(), + base::Bind(&ApplicationManager::CleanupRunner, + weak_ptr_factory_.GetWeakPtr(), runner)); +} + +void ApplicationManager::RegisterExternalApplication( + const GURL& url, + const std::vector<std::string>& args, + ApplicationPtr application) { + const auto& args_it = url_to_args_.find(url); + if (args_it != url_to_args_.end()) { + LOG(WARNING) << "--args-for provided for external application " << url + << " <ignored>"; + } + Identity identity(url); + ShellImpl* shell_impl = + new ShellImpl(application.Pass(), this, identity, base::Closure()); + identity_to_shell_impl_[identity] = shell_impl; + shell_impl->InitializeApplication(Array<String>::From(args)); +} + +void ApplicationManager::RegisterContentHandler( + const std::string& mime_type, + const GURL& content_handler_url) { + DCHECK(content_handler_url.is_valid()) + << "Content handler URL is invalid for mime type " << mime_type; + mime_type_to_url_[mime_type] = content_handler_url; +} + +void ApplicationManager::LoadWithContentHandler( + const GURL& content_handler_url, + InterfaceRequest<Application> application_request, + URLResponsePtr url_response) { + ContentHandlerConnection* connection = nullptr; + URLToContentHandlerMap::iterator iter = + url_to_content_handler_.find(content_handler_url); + if (iter != url_to_content_handler_.end()) { + connection = iter->second; + } else { + connection = new ContentHandlerConnection(this, content_handler_url); + url_to_content_handler_[content_handler_url] = connection; + } + + connection->content_handler()->StartApplication(application_request.Pass(), + url_response.Pass()); +} + +void ApplicationManager::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, + const GURL& url) { + URLToLoaderMap::iterator it = url_to_loader_.find(url); + if (it != url_to_loader_.end()) + delete it->second; + url_to_loader_[url] = loader.release(); +} + +void ApplicationManager::SetLoaderForScheme( + scoped_ptr<ApplicationLoader> loader, + const std::string& scheme) { + SchemeToLoaderMap::iterator it = scheme_to_loader_.find(scheme); + if (it != scheme_to_loader_.end()) + delete it->second; + scheme_to_loader_[scheme] = loader.release(); +} + +void ApplicationManager::SetArgsForURL(const std::vector<std::string>& args, + const GURL& url) { + url_to_args_[url].insert(url_to_args_[url].end(), args.begin(), args.end()); + GURL mapped_url = delegate_->ResolveMappings(url); + if (mapped_url != url) { + url_to_args_[mapped_url].insert(url_to_args_[mapped_url].end(), + args.begin(), args.end()); + } + GURL resolved_url = delegate_->ResolveURL(mapped_url); + if (resolved_url != mapped_url) { + url_to_args_[resolved_url].insert(url_to_args_[resolved_url].end(), + args.begin(), args.end()); + } +} + +void ApplicationManager::SetNativeOptionsForURL( + const NativeRunnerFactory::Options& options, + const GURL& url) { + DCHECK(!url.has_query()); // Precondition. + // Apply mappings and resolution to get the resolved URL. + GURL resolved_url = delegate_->ResolveURL(delegate_->ResolveMappings(url)); + DCHECK(!resolved_url.has_query()); // Still shouldn't have query. + // TODO(vtl): We should probably also remove/disregard the query string (and + // maybe canonicalize in other ways). + DVLOG(2) << "Storing native options for resolved URL " << resolved_url + << " (original URL " << url << ")"; + url_to_native_options_[resolved_url] = options; +} + +ApplicationLoader* ApplicationManager::GetLoaderForURL(const GURL& url) { + auto url_it = url_to_loader_.find(GetBaseURLAndQuery(url, nullptr)); + if (url_it != url_to_loader_.end()) + return url_it->second; + auto scheme_it = scheme_to_loader_.find(url.scheme()); + if (scheme_it != scheme_to_loader_.end()) + return scheme_it->second; + return nullptr; +} + +void ApplicationManager::OnShellImplError(ShellImpl* shell_impl) { + // Called from ~ShellImpl, so we do not need to call Destroy here. + const Identity identity = shell_impl->identity(); + base::Closure on_application_end = shell_impl->on_application_end(); + // Remove the shell. + auto it = identity_to_shell_impl_.find(identity); + DCHECK(it != identity_to_shell_impl_.end()); + delete it->second; + identity_to_shell_impl_.erase(it); + if (!on_application_end.is_null()) + on_application_end.Run(); +} + +void ApplicationManager::OnContentHandlerError( + ContentHandlerConnection* content_handler) { + // Remove the mapping to the content handler. + auto it = + url_to_content_handler_.find(content_handler->content_handler_url()); + DCHECK(it != url_to_content_handler_.end()); + delete it->second; + url_to_content_handler_.erase(it); +} + +ScopedMessagePipeHandle ApplicationManager::ConnectToServiceByName( + const GURL& application_url, + const std::string& interface_name) { + ServiceProviderPtr services; + ConnectToApplication(application_url, GURL(), GetProxy(&services), nullptr, + base::Closure()); + MessagePipe pipe; + services->ConnectToService(interface_name, pipe.handle1.Pass()); + return pipe.handle0.Pass(); +} + +std::vector<std::string> ApplicationManager::GetArgsForURL(const GURL& url) { + const auto& args_it = url_to_args_.find(url); + if (args_it != url_to_args_.end()) + return args_it->second; + return std::vector<std::string>(); +} + +void ApplicationManager::CleanupRunner(NativeRunner* runner) { + native_runners_.erase( + std::find(native_runners_.begin(), native_runners_.end(), runner)); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/application_manager.h b/mojo/shell/application_manager/application_manager.h new file mode 100644 index 0000000..d0d3a40 --- /dev/null +++ b/mojo/shell/application_manager/application_manager.h @@ -0,0 +1,239 @@ +// 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. + +#ifndef SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ +#define SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ + +#include <map> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/interfaces/application/application.mojom.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "mojo/shell/application_manager/identity.h" +#include "mojo/shell/application_manager/native_runner.h" +#include "mojo/shell/native_application_support.h" +#include "url/gurl.h" + +namespace base { +class FilePath; +class SequencedWorkerPool; +} + +namespace mojo { +namespace shell { + +class Fetcher; +class ShellImpl; + +class ApplicationManager { + public: + class Delegate { + public: + virtual ~Delegate(); + virtual GURL ResolveURL(const GURL& url); + virtual GURL ResolveMappings(const GURL& url); + }; + + // API for testing. + class TestAPI { + public: + explicit TestAPI(ApplicationManager* manager); + ~TestAPI(); + + // Returns true if the shared instance has been created. + static bool HasCreatedInstance(); + // Returns true if there is a ShellImpl for this URL. + bool HasFactoryForURL(const GURL& url) const; + + private: + ApplicationManager* manager_; + + DISALLOW_COPY_AND_ASSIGN(TestAPI); + }; + + explicit ApplicationManager(Delegate* delegate); + ~ApplicationManager(); + + // Loads a service if necessary and establishes a new client connection. + void ConnectToApplication(const GURL& application_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end); + + template <typename Interface> + inline void ConnectToService(const GURL& application_url, + InterfacePtr<Interface>* ptr) { + ScopedMessagePipeHandle service_handle = + ConnectToServiceByName(application_url, Interface::Name_); + ptr->Bind(service_handle.Pass()); + } + + ScopedMessagePipeHandle ConnectToServiceByName( + const GURL& application_url, + const std::string& interface_name); + + void RegisterContentHandler(const std::string& mime_type, + const GURL& content_handler_url); + + void RegisterExternalApplication(const GURL& application_url, + const std::vector<std::string>& args, + ApplicationPtr application); + + // Sets the default Loader to be used if not overridden by SetLoaderForURL() + // or SetLoaderForScheme(). + void set_default_loader(scoped_ptr<ApplicationLoader> loader) { + default_loader_ = loader.Pass(); + } + void set_native_runner_factory( + scoped_ptr<NativeRunnerFactory> runner_factory) { + native_runner_factory_ = runner_factory.Pass(); + } + void set_blocking_pool(base::SequencedWorkerPool* blocking_pool) { + blocking_pool_ = blocking_pool; + } + void set_disable_cache(bool disable_cache) { disable_cache_ = disable_cache; } + // Sets a Loader to be used for a specific url. + void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url); + // Sets a Loader to be used for a specific url scheme. + void SetLoaderForScheme(scoped_ptr<ApplicationLoader> loader, + const std::string& scheme); + // These strings will be passed to the Initialize() method when an Application + // is instantiated. + // TODO(vtl): Maybe we should store/compare resolved URLs, like + // SetNativeOptionsForURL() below? + void SetArgsForURL(const std::vector<std::string>& args, const GURL& url); + // These options will be used in running any native application at |url| + // (which shouldn't contain a query string). (|url| will be mapped and + // resolved, and any application whose base resolved URL matches it will have + // |options| applied.) + // TODO(vtl): This may not do what's desired if the resolved URL results in an + // HTTP redirect. Really, we want options to be identified with a particular + // implementation, maybe via a signed manifest or something like that. + void SetNativeOptionsForURL(const NativeRunnerFactory::Options& options, + const GURL& url); + + // Destroys all Shell-ends of connections established with Applications. + // Applications connected by this ApplicationManager will observe pipe errors + // and have a chance to shutdown. + void TerminateShellConnections(); + + // Removes a ShellImpl when it encounters an error. + void OnShellImplError(ShellImpl* shell_impl); + + private: + class ContentHandlerConnection; + + typedef std::map<std::string, ApplicationLoader*> SchemeToLoaderMap; + typedef std::map<GURL, ApplicationLoader*> URLToLoaderMap; + typedef std::map<Identity, ShellImpl*> IdentityToShellImplMap; + typedef std::map<GURL, ContentHandlerConnection*> URLToContentHandlerMap; + typedef std::map<GURL, std::vector<std::string>> URLToArgsMap; + typedef std::map<std::string, GURL> MimeTypeToURLMap; + typedef std::map<GURL, NativeRunnerFactory::Options> URLToNativeOptionsMap; + + void ConnectToApplicationWithParameters( + const GURL& application_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& pre_redirect_parameters); + + bool ConnectToRunningApplication(const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services); + + bool ConnectToApplicationWithLoader( + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider>* services, + ServiceProviderPtr* exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters, + ApplicationLoader* loader); + + InterfaceRequest<Application> RegisterShell( + // The URL after resolution and redirects, including the querystring. + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters); + + ShellImpl* GetShellImpl(const GURL& url); + + void ConnectToClient(ShellImpl* shell_impl, + const GURL& resolved_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services); + + void HandleFetchCallback(const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const base::Closure& on_application_end, + const std::vector<std::string>& parameters, + NativeApplicationCleanup cleanup, + scoped_ptr<Fetcher> fetcher); + + void RunNativeApplication(InterfaceRequest<Application> application_request, + const NativeRunnerFactory::Options& options, + NativeApplicationCleanup cleanup, + scoped_ptr<Fetcher> fetcher, + const base::FilePath& file_path, + bool path_exists); + + void LoadWithContentHandler(const GURL& content_handler_url, + InterfaceRequest<Application> application_request, + URLResponsePtr url_response); + + // Returns the appropriate loader for |url|, or null if there is no loader + // configured for the URL. + ApplicationLoader* GetLoaderForURL(const GURL& url); + + // Removes a ContentHandler when it encounters an error. + void OnContentHandlerError(ContentHandlerConnection* content_handler); + + // Returns the arguments for the given url. + std::vector<std::string> GetArgsForURL(const GURL& url); + + void CleanupRunner(NativeRunner* runner); + + Delegate* const delegate_; + // Loader management. + // Loaders are chosen in the order they are listed here. + URLToLoaderMap url_to_loader_; + SchemeToLoaderMap scheme_to_loader_; + scoped_ptr<ApplicationLoader> default_loader_; + scoped_ptr<NativeRunnerFactory> native_runner_factory_; + + IdentityToShellImplMap identity_to_shell_impl_; + URLToContentHandlerMap url_to_content_handler_; + URLToArgsMap url_to_args_; + // Note: The keys are URLs after mapping and resolving. + URLToNativeOptionsMap url_to_native_options_; + + base::SequencedWorkerPool* blocking_pool_; + NetworkServicePtr network_service_; + MimeTypeToURLMap mime_type_to_url_; + ScopedVector<NativeRunner> native_runners_; + bool disable_cache_; + base::WeakPtrFactory<ApplicationManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(ApplicationManager); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_APPLICATION_MANAGER_H_ diff --git a/mojo/shell/application_manager/application_manager_unittest.cc b/mojo/shell/application_manager/application_manager_unittest.cc new file mode 100644 index 0000000..ed16d15 --- /dev/null +++ b/mojo/shell/application_manager/application_manager_unittest.cc @@ -0,0 +1,829 @@ +// 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 "base/at_exit.h" +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/application_manager/test.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +const char kTestURLString[] = "test:testService"; +const char kTestAURLString[] = "test:TestA"; +const char kTestBURLString[] = "test:TestB"; + +struct TestContext { + TestContext() : num_impls(0), num_loader_deletes(0) {} + std::string last_test_string; + int num_impls; + int num_loader_deletes; +}; + +void QuitClosure(bool* value) { + *value = true; + base::MessageLoop::current()->QuitWhenIdle(); +} + +class QuitMessageLoopErrorHandler : public ErrorHandler { + public: + QuitMessageLoopErrorHandler() {} + ~QuitMessageLoopErrorHandler() override {} + + // |ErrorHandler| implementation: + void OnConnectionError() override { + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler); +}; + +class TestServiceImpl : public TestService { + public: + TestServiceImpl(TestContext* context, InterfaceRequest<TestService> request) + : context_(context), binding_(this, request.Pass()) { + ++context_->num_impls; + } + + ~TestServiceImpl() override { + --context_->num_impls; + if (!base::MessageLoop::current()->is_running()) + return; + base::MessageLoop::current()->Quit(); + } + + // TestService implementation: + void Test(const String& test_string, + const Callback<void()>& callback) override { + context_->last_test_string = test_string; + callback.Run(); + } + + private: + TestContext* context_; + StrongBinding<TestService> binding_; +}; + +class TestClient { + public: + explicit TestClient(TestServicePtr service) + : service_(service.Pass()), quit_after_ack_(false) {} + + void AckTest() { + if (quit_after_ack_) + base::MessageLoop::current()->Quit(); + } + + void Test(const std::string& test_string) { + quit_after_ack_ = true; + service_->Test(test_string, + base::Bind(&TestClient::AckTest, base::Unretained(this))); + } + + private: + TestServicePtr service_; + bool quit_after_ack_; + DISALLOW_COPY_AND_ASSIGN(TestClient); +}; + +class TestApplicationLoader : public ApplicationLoader, + public ApplicationDelegate, + public InterfaceFactory<TestService> { + public: + TestApplicationLoader() : context_(nullptr), num_loads_(0) {} + + ~TestApplicationLoader() override { + if (context_) + ++context_->num_loader_deletes; + test_app_.reset(); + } + + void set_context(TestContext* context) { context_ = context; } + int num_loads() const { return num_loads_; } + const std::vector<std::string>& GetArgs() const { return test_app_->args(); } + + private: + // ApplicationLoader implementation. + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override { + ++num_loads_; + test_app_.reset(new ApplicationImpl(this, application_request.Pass())); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(this); + return true; + } + + // InterfaceFactory implementation. + void Create(ApplicationConnection* connection, + InterfaceRequest<TestService> request) override { + new TestServiceImpl(context_, request.Pass()); + } + + scoped_ptr<ApplicationImpl> test_app_; + TestContext* context_; + int num_loads_; + DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader); +}; + +class ClosingApplicationLoader : public ApplicationLoader { + private: + // ApplicationLoader implementation. + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override {} +}; + +class TesterContext { + public: + explicit TesterContext(base::MessageLoop* loop) + : num_b_calls_(0), + num_c_calls_(0), + num_a_deletes_(0), + num_b_deletes_(0), + num_c_deletes_(0), + tester_called_quit_(false), + a_called_quit_(false), + loop_(loop) {} + + void IncrementNumBCalls() { + base::AutoLock lock(lock_); + num_b_calls_++; + } + + void IncrementNumCCalls() { + base::AutoLock lock(lock_); + num_c_calls_++; + } + + void IncrementNumADeletes() { + base::AutoLock lock(lock_); + num_a_deletes_++; + } + + void IncrementNumBDeletes() { + base::AutoLock lock(lock_); + num_b_deletes_++; + } + + void IncrementNumCDeletes() { + base::AutoLock lock(lock_); + num_c_deletes_++; + } + + void set_tester_called_quit() { + base::AutoLock lock(lock_); + tester_called_quit_ = true; + } + + void set_a_called_quit() { + base::AutoLock lock(lock_); + a_called_quit_ = true; + } + + int num_b_calls() { + base::AutoLock lock(lock_); + return num_b_calls_; + } + int num_c_calls() { + base::AutoLock lock(lock_); + return num_c_calls_; + } + int num_a_deletes() { + base::AutoLock lock(lock_); + return num_a_deletes_; + } + int num_b_deletes() { + base::AutoLock lock(lock_); + return num_b_deletes_; + } + int num_c_deletes() { + base::AutoLock lock(lock_); + return num_c_deletes_; + } + bool tester_called_quit() { + base::AutoLock lock(lock_); + return tester_called_quit_; + } + bool a_called_quit() { + base::AutoLock lock(lock_); + return a_called_quit_; + } + + void QuitSoon() { + loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); + } + + private: + // lock_ protects all members except for loop_ which must be unchanged for the + // lifetime of this class. + base::Lock lock_; + int num_b_calls_; + int num_c_calls_; + int num_a_deletes_; + int num_b_deletes_; + int num_c_deletes_; + bool tester_called_quit_; + bool a_called_quit_; + + base::MessageLoop* loop_; +}; + +// Used to test that the requestor url will be correctly passed. +class TestAImpl : public TestA { + public: + TestAImpl(ApplicationImpl* app_impl, + TesterContext* test_context, + InterfaceRequest<TestA> request) + : test_context_(test_context), binding_(this, request.Pass()) { + app_impl->ConnectToApplication(kTestBURLString)->ConnectToService(&b_); + } + + ~TestAImpl() override { + test_context_->IncrementNumADeletes(); + if (base::MessageLoop::current()->is_running()) + Quit(); + } + + private: + void CallB() override { + b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this))); + } + + void CallCFromB() override { + b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this))); + } + + void Quit() { + base::MessageLoop::current()->Quit(); + test_context_->set_a_called_quit(); + test_context_->QuitSoon(); + } + + TesterContext* test_context_; + TestBPtr b_; + StrongBinding<TestA> binding_; +}; + +class TestBImpl : public TestB { + public: + TestBImpl(ApplicationConnection* connection, + TesterContext* test_context, + InterfaceRequest<TestB> request) + : test_context_(test_context), binding_(this, request.Pass()) { + connection->ConnectToService(&c_); + } + + ~TestBImpl() override { + test_context_->IncrementNumBDeletes(); + if (base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + test_context_->QuitSoon(); + } + + private: + void B(const Callback<void()>& callback) override { + test_context_->IncrementNumBCalls(); + callback.Run(); + } + + void CallC(const Callback<void()>& callback) override { + test_context_->IncrementNumBCalls(); + c_->C(callback); + } + + TesterContext* test_context_; + TestCPtr c_; + StrongBinding<TestB> binding_; +}; + +class TestCImpl : public TestC { + public: + TestCImpl(ApplicationConnection* connection, + TesterContext* test_context, + InterfaceRequest<TestC> request) + : test_context_(test_context), binding_(this, request.Pass()) {} + + ~TestCImpl() override { test_context_->IncrementNumCDeletes(); } + + private: + void C(const Callback<void()>& callback) override { + test_context_->IncrementNumCCalls(); + callback.Run(); + } + + TesterContext* test_context_; + StrongBinding<TestC> binding_; +}; + +class Tester : public ApplicationDelegate, + public ApplicationLoader, + public InterfaceFactory<TestA>, + public InterfaceFactory<TestB>, + public InterfaceFactory<TestC> { + public: + Tester(TesterContext* context, const std::string& requestor_url) + : context_(context), requestor_url_(requestor_url) {} + ~Tester() override {} + + private: + void Load(const GURL& url, + InterfaceRequest<Application> application_request) override { + app_.reset(new ApplicationImpl(this, application_request.Pass())); + } + + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + if (!requestor_url_.empty() && + requestor_url_ != connection->GetRemoteApplicationURL()) { + context_->set_tester_called_quit(); + context_->QuitSoon(); + base::MessageLoop::current()->Quit(); + return false; + } + // If we're coming from A, then add B, otherwise A. + if (connection->GetRemoteApplicationURL() == kTestAURLString) + connection->AddService<TestB>(this); + else + connection->AddService<TestA>(this); + return true; + } + + bool ConfigureOutgoingConnection(ApplicationConnection* connection) override { + // If we're connecting to B, then add C. + if (connection->GetRemoteApplicationURL() == kTestBURLString) + connection->AddService<TestC>(this); + return true; + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestA> request) override { + a_bindings_.push_back(new TestAImpl(app_.get(), context_, request.Pass())); + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestB> request) override { + new TestBImpl(connection, context_, request.Pass()); + } + + void Create(ApplicationConnection* connection, + InterfaceRequest<TestC> request) override { + new TestCImpl(connection, context_, request.Pass()); + } + + TesterContext* context_; + scoped_ptr<ApplicationImpl> app_; + std::string requestor_url_; + ScopedVector<TestAImpl> a_bindings_; +}; + +class TestDelegate : public ApplicationManager::Delegate { + public: + void AddMapping(const GURL& from, const GURL& to) { mappings_[from] = to; } + + // ApplicationManager::Delegate + GURL ResolveMappings(const GURL& url) override { + auto it = mappings_.find(url); + if (it != mappings_.end()) + return it->second; + return url; + } + + // ApplicationManager::Delegate + GURL ResolveURL(const GURL& url) override { + GURL mapped_url = ResolveMappings(url); + // The shell automatically map mojo URLs. + if (mapped_url.scheme() == "mojo") { + url::Replacements<char> replacements; + replacements.SetScheme("file", url::Component(0, 4)); + mapped_url = mapped_url.ReplaceComponents(replacements); + } + return mapped_url; + } + + private: + std::map<GURL, GURL> mappings_; +}; + +class TestExternal : public ApplicationDelegate { + public: + TestExternal() : configure_incoming_connection_called_(false) {} + + void Initialize(ApplicationImpl* app) override { + initialize_args_ = app->args(); + base::MessageLoop::current()->Quit(); + } + + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + configure_incoming_connection_called_ = true; + base::MessageLoop::current()->Quit(); + return true; + } + + const std::vector<std::string>& initialize_args() const { + return initialize_args_; + } + + bool configure_incoming_connection_called() const { + return configure_incoming_connection_called_; + } + + private: + std::vector<std::string> initialize_args_; + bool configure_incoming_connection_called_; +}; + +class ApplicationManagerTest : public testing::Test { + public: + ApplicationManagerTest() : tester_context_(&loop_) {} + + ~ApplicationManagerTest() override {} + + void SetUp() override { + application_manager_.reset(new ApplicationManager(&test_delegate_)); + test_loader_ = new TestApplicationLoader; + test_loader_->set_context(&context_); + application_manager_->set_default_loader( + scoped_ptr<ApplicationLoader>(test_loader_)); + + TestServicePtr service_proxy; + application_manager_->ConnectToService(GURL(kTestURLString), + &service_proxy); + test_client_.reset(new TestClient(service_proxy.Pass())); + } + + void TearDown() override { + test_client_.reset(); + application_manager_.reset(); + } + + void AddLoaderForURL(const GURL& url, const std::string& requestor_url) { + application_manager_->SetLoaderForURL( + make_scoped_ptr(new Tester(&tester_context_, requestor_url)), url); + } + + bool HasFactoryForTestURL() { + ApplicationManager::TestAPI manager_test_api(application_manager_.get()); + return manager_test_api.HasFactoryForURL(GURL(kTestURLString)); + } + + protected: + base::ShadowingAtExitManager at_exit_; + TestDelegate test_delegate_; + TestApplicationLoader* test_loader_; + TesterContext tester_context_; + TestContext context_; + base::MessageLoop loop_; + scoped_ptr<TestClient> test_client_; + scoped_ptr<ApplicationManager> application_manager_; + DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest); +}; + +TEST_F(ApplicationManagerTest, Basic) { + test_client_->Test("test"); + loop_.Run(); + EXPECT_EQ(std::string("test"), context_.last_test_string); +} + +// Confirm that no arguments are sent to an application by default. +TEST_F(ApplicationManagerTest, NoArgs) { + ApplicationManager am(&test_delegate_); + GURL test_url("test:test"); + TestApplicationLoader* loader = new TestApplicationLoader; + loader->set_context(&context_); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url); + TestServicePtr test_service; + am.ConnectToService(test_url, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + std::vector<std::string> app_args = loader->GetArgs(); + EXPECT_EQ(0U, app_args.size()); +} + +// Confirm that arguments are sent to an application. +TEST_F(ApplicationManagerTest, Args) { + ApplicationManager am(&test_delegate_); + GURL test_url("test:test"); + std::vector<std::string> args; + args.push_back("test_arg1"); + args.push_back("test_arg2"); + am.SetArgsForURL(args, test_url); + TestApplicationLoader* loader = new TestApplicationLoader; + loader->set_context(&context_); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url); + TestServicePtr test_service; + am.ConnectToService(test_url, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + std::vector<std::string> app_args = loader->GetArgs(); + ASSERT_EQ(args.size(), app_args.size()); + EXPECT_EQ(args[0], app_args[0]); + EXPECT_EQ(args[1], app_args[1]); +} + +// Confirm that arguments are aggregated through mappings. +TEST_F(ApplicationManagerTest, ArgsAndMapping) { + ApplicationManager am(&test_delegate_); + GURL test_url("test:test"); + GURL test_url2("test:test2"); + test_delegate_.AddMapping(test_url, test_url2); + std::vector<std::string> args; + args.push_back("test_arg1"); + args.push_back("test_arg2"); + am.SetArgsForURL(args, test_url); + std::vector<std::string> args2; + args2.push_back("test_arg3"); + args2.push_back("test_arg4"); + am.SetArgsForURL(args2, test_url2); + TestApplicationLoader* loader = new TestApplicationLoader; + loader->set_context(&context_); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url2); + { + // Connext to the mapped url + TestServicePtr test_service; + am.ConnectToService(test_url, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + std::vector<std::string> app_args = loader->GetArgs(); + ASSERT_EQ(args.size() + args2.size(), app_args.size()); + EXPECT_EQ(args[0], app_args[0]); + EXPECT_EQ(args[1], app_args[1]); + EXPECT_EQ(args2[0], app_args[2]); + EXPECT_EQ(args2[1], app_args[3]); + } + { + // Connext to the target url + TestServicePtr test_service; + am.ConnectToService(test_url2, &test_service); + TestClient test_client(test_service.Pass()); + test_client.Test("test"); + loop_.Run(); + std::vector<std::string> app_args = loader->GetArgs(); + ASSERT_EQ(args.size() + args2.size(), app_args.size()); + EXPECT_EQ(args[0], app_args[0]); + EXPECT_EQ(args[1], app_args[1]); + EXPECT_EQ(args2[0], app_args[2]); + EXPECT_EQ(args2[1], app_args[3]); + } +} + +TEST_F(ApplicationManagerTest, ClientError) { + test_client_->Test("test"); + EXPECT_TRUE(HasFactoryForTestURL()); + loop_.Run(); + EXPECT_EQ(1, context_.num_impls); + test_client_.reset(); + loop_.Run(); + EXPECT_EQ(0, context_.num_impls); + EXPECT_TRUE(HasFactoryForTestURL()); +} + +TEST_F(ApplicationManagerTest, Deletes) { + { + ApplicationManager am(&test_delegate_); + TestApplicationLoader* default_loader = new TestApplicationLoader; + default_loader->set_context(&context_); + TestApplicationLoader* url_loader1 = new TestApplicationLoader; + TestApplicationLoader* url_loader2 = new TestApplicationLoader; + url_loader1->set_context(&context_); + url_loader2->set_context(&context_); + TestApplicationLoader* scheme_loader1 = new TestApplicationLoader; + TestApplicationLoader* scheme_loader2 = new TestApplicationLoader; + scheme_loader1->set_context(&context_); + scheme_loader2->set_context(&context_); + am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader)); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1), + GURL("test:test1")); + am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2), + GURL("test:test1")); + am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader1), + "test"); + am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader2), + "test"); + } + EXPECT_EQ(5, context_.num_loader_deletes); +} + +// Confirm that both urls and schemes can have their loaders explicitly set. +TEST_F(ApplicationManagerTest, SetLoaders) { + TestApplicationLoader* default_loader = new TestApplicationLoader; + TestApplicationLoader* url_loader = new TestApplicationLoader; + TestApplicationLoader* scheme_loader = new TestApplicationLoader; + application_manager_->set_default_loader( + scoped_ptr<ApplicationLoader>(default_loader)); + application_manager_->SetLoaderForURL( + scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1")); + application_manager_->SetLoaderForScheme( + scoped_ptr<ApplicationLoader>(scheme_loader), "test"); + + // test::test1 should go to url_loader. + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("test:test1"), &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(0, scheme_loader->num_loads()); + EXPECT_EQ(0, default_loader->num_loads()); + + // test::test2 should go to scheme loader. + application_manager_->ConnectToService(GURL("test:test2"), &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); + EXPECT_EQ(0, default_loader->num_loads()); + + // http::test1 should go to default loader. + application_manager_->ConnectToService(GURL("http:test1"), &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); + EXPECT_EQ(1, default_loader->num_loads()); +} + +// Confirm that the url of a service is correctly passed to another service that +// it loads. +TEST_F(ApplicationManagerTest, ACallB) { + // Any url can load a. + AddLoaderForURL(GURL(kTestAURLString), std::string()); + + // Only a can load b. + AddLoaderForURL(GURL(kTestBURLString), kTestAURLString); + + TestAPtr a; + application_manager_->ConnectToService(GURL(kTestAURLString), &a); + a->CallB(); + loop_.Run(); + EXPECT_EQ(1, tester_context_.num_b_calls()); + EXPECT_TRUE(tester_context_.a_called_quit()); +} + +// A calls B which calls C. +TEST_F(ApplicationManagerTest, BCallC) { + // Any url can load a. + AddLoaderForURL(GURL(kTestAURLString), std::string()); + + // Only a can load b. + AddLoaderForURL(GURL(kTestBURLString), kTestAURLString); + + TestAPtr a; + application_manager_->ConnectToService(GURL(kTestAURLString), &a); + a->CallCFromB(); + loop_.Run(); + + EXPECT_EQ(1, tester_context_.num_b_calls()); + EXPECT_EQ(1, tester_context_.num_c_calls()); + EXPECT_TRUE(tester_context_.a_called_quit()); +} + +// Confirm that a service impl will be deleted if the app that connected to +// it goes away. +TEST_F(ApplicationManagerTest, BDeleted) { + AddLoaderForURL(GURL(kTestAURLString), std::string()); + AddLoaderForURL(GURL(kTestBURLString), std::string()); + + TestAPtr a; + application_manager_->ConnectToService(GURL(kTestAURLString), &a); + + a->CallB(); + loop_.Run(); + + // Kills the a app. + application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(), + GURL(kTestAURLString)); + loop_.Run(); + + EXPECT_EQ(1, tester_context_.num_b_deletes()); +} + +// Confirm that the url of a service is correctly passed to another service that +// it loads, and that it can be rejected. +TEST_F(ApplicationManagerTest, ANoLoadB) { + // Any url can load a. + AddLoaderForURL(GURL(kTestAURLString), std::string()); + + // Only c can load b, so this will fail. + AddLoaderForURL(GURL(kTestBURLString), "test:TestC"); + + TestAPtr a; + application_manager_->ConnectToService(GURL(kTestAURLString), &a); + a->CallB(); + loop_.Run(); + EXPECT_EQ(0, tester_context_.num_b_calls()); + + EXPECT_FALSE(tester_context_.a_called_quit()); + EXPECT_TRUE(tester_context_.tester_called_quit()); +} + +TEST_F(ApplicationManagerTest, NoServiceNoLoad) { + AddLoaderForURL(GURL(kTestAURLString), std::string()); + + // There is no TestC service implementation registered with + // ApplicationManager, so this cannot succeed (but also shouldn't crash). + TestCPtr c; + application_manager_->ConnectToService(GURL(kTestAURLString), &c); + QuitMessageLoopErrorHandler quitter; + c.set_error_handler(&quitter); + + loop_.Run(); + EXPECT_TRUE(c.encountered_error()); +} + +TEST_F(ApplicationManagerTest, MappedURLsShouldNotCauseDuplicateLoad) { + test_delegate_.AddMapping(GURL("foo:foo2"), GURL("foo:foo")); + // 1 because ApplicationManagerTest connects once at startup. + EXPECT_EQ(1, test_loader_->num_loads()); + + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("foo:foo"), &test_service); + EXPECT_EQ(2, test_loader_->num_loads()); + + TestServicePtr test_service2; + application_manager_->ConnectToService(GURL("foo:foo2"), &test_service2); + EXPECT_EQ(2, test_loader_->num_loads()); + + TestServicePtr test_service3; + application_manager_->ConnectToService(GURL("bar:bar"), &test_service2); + EXPECT_EQ(3, test_loader_->num_loads()); +} + +TEST_F(ApplicationManagerTest, MappedURLsShouldWorkWithLoaders) { + TestApplicationLoader* custom_loader = new TestApplicationLoader; + TestContext context; + custom_loader->set_context(&context); + application_manager_->SetLoaderForURL(make_scoped_ptr(custom_loader), + GURL("mojo:foo")); + test_delegate_.AddMapping(GURL("mojo:foo2"), GURL("mojo:foo")); + + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("mojo:foo2"), &test_service); + EXPECT_EQ(1, custom_loader->num_loads()); + custom_loader->set_context(nullptr); +} + +TEST_F(ApplicationManagerTest, ExternalApp) { + ApplicationPtr application; + TestExternal external; + std::vector<std::string> args; + args.push_back("test"); + ApplicationImpl app(&external, GetProxy(&application)); + application_manager_->RegisterExternalApplication(GURL("mojo:test"), args, + application.Pass()); + loop_.Run(); + EXPECT_EQ(args, external.initialize_args()); + application_manager_->ConnectToServiceByName(GURL("mojo:test"), + std::string()); + loop_.Run(); + EXPECT_TRUE(external.configure_incoming_connection_called()); +}; + +TEST_F(ApplicationManagerTest, TestQueryWithLoaders) { + TestApplicationLoader* url_loader = new TestApplicationLoader; + TestApplicationLoader* scheme_loader = new TestApplicationLoader; + application_manager_->SetLoaderForURL( + scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1")); + application_manager_->SetLoaderForScheme( + scoped_ptr<ApplicationLoader>(scheme_loader), "test"); + + // test::test1 should go to url_loader. + TestServicePtr test_service; + application_manager_->ConnectToService(GURL("test:test1?foo=bar"), + &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(0, scheme_loader->num_loads()); + + // test::test2 should go to scheme loader. + application_manager_->ConnectToService(GURL("test:test2?foo=bar"), + &test_service); + EXPECT_EQ(1, url_loader->num_loads()); + EXPECT_EQ(1, scheme_loader->num_loads()); +} + +TEST_F(ApplicationManagerTest, TestEndApplicationClosure) { + ClosingApplicationLoader* loader = new ClosingApplicationLoader(); + application_manager_->SetLoaderForScheme( + scoped_ptr<ApplicationLoader>(loader), "test"); + + bool called = false; + application_manager_->ConnectToApplication( + GURL("test:test"), GURL(), nullptr, nullptr, + base::Bind(&QuitClosure, base::Unretained(&called))); + loop_.Run(); + EXPECT_TRUE(called); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/data_pipe_peek.cc b/mojo/shell/application_manager/data_pipe_peek.cc new file mode 100644 index 0000000..9109b51 --- /dev/null +++ b/mojo/shell/application_manager/data_pipe_peek.cc @@ -0,0 +1,160 @@ +// 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 "mojo/shell/application_manager/data_pipe_peek.h" + +#include <stdint.h> + +#include "base/bind.h" +#include "base/macros.h" + +namespace mojo { +namespace shell { + +namespace { + +// Sleep for as long as max_sleep_micros if the deadline hasn't been reached +// and the number of bytes read is still increasing. Returns true if sleep +// was actually called. +// +// This class is a substitute for being able to wait until N bytes are available +// from a data pipe. The MaybeSleep method is called when num_bytes_read are +// available but more are needed by the Peek operation. If a second +// Peek operation finds the same number of bytes after sleeping we assume +// that there's no point in trying again. +// TODO(hansmuller): this heuristic is weak. crbug.com/429377 +class PeekSleeper { + public: + explicit PeekSleeper(MojoTimeTicks deadline) + : deadline_(deadline), + last_number_bytes_read_(0) {} + + bool MaybeSleep(uint32_t num_bytes_read) { + if (num_bytes_read > 0 && last_number_bytes_read_ >= num_bytes_read) + return false; + last_number_bytes_read_ = num_bytes_read; + + MojoTimeTicks now(GetTimeTicksNow()); + if (now > deadline_) + return false; + + MojoTimeTicks sleep_time = + (deadline_ == 0) ? kMaxSleepMicros + : std::min<int64>(deadline_ - now, kMaxSleepMicros); + base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(sleep_time)); + return true; + } + + private: + static const MojoTimeTicks kMaxSleepMicros = 1000 * 10; // 10 ms + + const MojoTimeTicks deadline_; // 0 => MOJO_DEADLINE_INDEFINITE + uint32_t last_number_bytes_read_; + + DISALLOW_COPY_AND_ASSIGN(PeekSleeper); +}; + +const MojoTimeTicks PeekSleeper::kMaxSleepMicros; + +enum PeekStatus { kSuccess, kFail, kKeepReading }; +typedef const base::Callback<PeekStatus(const void*, uint32_t, std::string*)>& + PeekFunc; + +// When data is available on source, call peek_func and then either return true +// and value, continue waiting for enough data to satisfy peek_func, or fail +// and return false. Fail if the timeout is exceeded. +// This function is not guaranteed to work correctly if applied to a data pipe +// that's already been read from. +bool BlockingPeekHelper(DataPipeConsumerHandle source, + std::string* value, + MojoDeadline timeout, + PeekFunc peek_func) { + DCHECK(value); + value->clear(); + + MojoTimeTicks deadline = + (timeout == MOJO_DEADLINE_INDEFINITE) + ? 0 + : 1 + GetTimeTicksNow() + static_cast<MojoTimeTicks>(timeout); + PeekSleeper sleeper(deadline); + + MojoResult result; + do { + const void* buffer; + uint32_t num_bytes; + result = + BeginReadDataRaw(source, &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + + if (result == MOJO_RESULT_OK) { + PeekStatus status = peek_func.Run(buffer, num_bytes, value); + CHECK_EQ(EndReadDataRaw(source, 0), MOJO_RESULT_OK); + switch (status) { + case PeekStatus::kSuccess: + return true; + case PeekStatus::kFail: + return false; + case PeekStatus::kKeepReading: + break; + } + if (!sleeper.MaybeSleep(num_bytes)) + return false; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + MojoTimeTicks now(GetTimeTicksNow()); + if (timeout == MOJO_DEADLINE_INDEFINITE || now < deadline) + result = + Wait(source, MOJO_HANDLE_SIGNAL_READABLE, deadline - now, nullptr); + } + } while (result == MOJO_RESULT_OK); + + return false; +} + +PeekStatus PeekLine(size_t max_line_length, + const void* buffer, + uint32_t buffer_num_bytes, + std::string* line) { + const char* p = static_cast<const char*>(buffer); + size_t max_p_index = std::min<size_t>(buffer_num_bytes, max_line_length); + for (size_t i = 0; i < max_p_index; i++) { + if (p[i] == '\n') { + *line = std::string(p, i + 1); // Include the trailing newline. + return PeekStatus::kSuccess; + } + } + return (buffer_num_bytes >= max_line_length) ? PeekStatus::kFail + : PeekStatus::kKeepReading; +} + +PeekStatus PeekNBytes(size_t bytes_length, + const void* buffer, + uint32_t buffer_num_bytes, + std::string* bytes) { + if (buffer_num_bytes >= bytes_length) { + const char* p = static_cast<const char*>(buffer); + *bytes = std::string(p, bytes_length); + return PeekStatus::kSuccess; + } + return PeekStatus::kKeepReading; +} + +} // namespace + +bool BlockingPeekNBytes(DataPipeConsumerHandle source, + std::string* bytes, + size_t bytes_length, + MojoDeadline timeout) { + PeekFunc peek_nbytes = base::Bind(PeekNBytes, bytes_length); + return BlockingPeekHelper(source, bytes, timeout, peek_nbytes); +} + +bool BlockingPeekLine(DataPipeConsumerHandle source, + std::string* line, + size_t max_line_length, + MojoDeadline timeout) { + PeekFunc peek_line = base::Bind(PeekLine, max_line_length); + return BlockingPeekHelper(source, line, timeout, peek_line); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/data_pipe_peek.h b/mojo/shell/application_manager/data_pipe_peek.h new file mode 100644 index 0000000..5ffbc5b --- /dev/null +++ b/mojo/shell/application_manager/data_pipe_peek.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef SHELL_APPLICATION_MANAGER_DATA_PIPE_SEEK_H_ +#define SHELL_APPLICATION_MANAGER_DATA_PIPE_SEEK_H_ + +#include <string> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace shell { + +// The Peek functions are only intended to be used by the +// DyanmicApplicationLoader class for discovering the type of a +// URL response. They are a stopgap to be replaced by real support +// in the DataPipe classes. + +// Return true and the first newline terminated line from source. Return false +// if more than max_line_length bytes are scanned without seeing a newline, or +// if the timeout is exceeded. +bool BlockingPeekLine(DataPipeConsumerHandle source, + std::string* line, + size_t max_line_length, + MojoDeadline timeout); + +// Return true and the first bytes_length bytes from source. Return false +// if the timeout is exceeded. +bool BlockingPeekNBytes(DataPipeConsumerHandle source, + std::string* bytes, + size_t bytes_length, + MojoDeadline timeout); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_DATA_PIPE_SEEK_H_ diff --git a/mojo/shell/application_manager/fetcher.cc b/mojo/shell/application_manager/fetcher.cc new file mode 100644 index 0000000..78fd125 --- /dev/null +++ b/mojo/shell/application_manager/fetcher.cc @@ -0,0 +1,38 @@ +// Copyright 2015 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 "mojo/shell/application_manager/fetcher.h" + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +const char Fetcher::kMojoMagic[] = "#!mojo "; +const size_t Fetcher::kMaxShebangLength = 2048; + +Fetcher::Fetcher(const FetchCallback& loader_callback) + : loader_callback_(loader_callback) { +} + +Fetcher::~Fetcher() { +} + +bool Fetcher::PeekContentHandler(std::string* mojo_shebang, + GURL* mojo_content_handler_url) { + // TODO(aa): I guess this should just go in ApplicationManager now. + std::string shebang; + if (HasMojoMagic() && PeekFirstLine(&shebang)) { + GURL url(shebang.substr(arraysize(kMojoMagic) - 1, std::string::npos)); + if (url.is_valid()) { + *mojo_shebang = shebang; + *mojo_content_handler_url = url; + return true; + } + } + return false; +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/fetcher.h b/mojo/shell/application_manager/fetcher.h new file mode 100644 index 0000000..2257f75 --- /dev/null +++ b/mojo/shell/application_manager/fetcher.h @@ -0,0 +1,76 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_FETCHER_H_ +#define SHELL_APPLICATION_MANAGER_FETCHER_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" + +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" + +class GURL; + +namespace base { +class FilePath; +class TaskRunner; +} + +namespace mojo { +namespace shell { + +// Fetcher abstracts getting an application by either file or http[s] URL. +// +// Although it is possible to use the Network implementation for http[s] URLs +// (because the underlying net library knows how to handle them), it is +// extremely slow because network responses must be copied to disk in order to +// get a file handle we can use with dlopen. +// +// Until this is solved, we use two different implementations so that +// performance isn't completely absymal. +class Fetcher { + public: + // The param will be null in the case where the content could not be fetched. + // Reasons include: + // - network error + // - 4x or 5x HTTP errors + typedef base::Callback<void(scoped_ptr<Fetcher>)> FetchCallback; + + Fetcher(const FetchCallback& fetch_callback); + virtual ~Fetcher(); + + // Returns the original URL that was fetched. + virtual const GURL& GetURL() const = 0; + + // If the fetch resulted in a redirect, this returns the final URL after all + // redirects. Otherwise, it returns an empty URL. + virtual GURL GetRedirectURL() const = 0; + + virtual URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) = 0; + + virtual void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) = 0; + + virtual std::string MimeType() = 0; + + virtual bool HasMojoMagic() = 0; + + virtual bool PeekFirstLine(std::string* line) = 0; + + bool PeekContentHandler(std::string* mojo_shebang, + GURL* mojo_content_handler_url); + + protected: + static const char kMojoMagic[]; + static const size_t kMaxShebangLength; + + FetchCallback loader_callback_; +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_FETCHER_H_ diff --git a/mojo/shell/application_manager/identity.cc b/mojo/shell/application_manager/identity.cc new file mode 100644 index 0000000..c54f5d8 --- /dev/null +++ b/mojo/shell/application_manager/identity.cc @@ -0,0 +1,28 @@ +// Copyright 2015 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 "mojo/shell/application_manager/identity.h" + +#include "mojo/shell/application_manager/query_util.h" + +namespace mojo { +namespace shell { + +Identity::Identity(const GURL& url, const std::string& qualifier) + : url(GetBaseURLAndQuery(url, nullptr)), qualifier(qualifier) { +} + +// explicit +Identity::Identity(const GURL& base_url) + : url(GetBaseURLAndQuery(base_url, nullptr)), qualifier(url.spec()) { +} + +bool Identity::operator<(const Identity& other) const { + if (url != other.url) + return url < other.url; + return qualifier < other.qualifier; +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/identity.h b/mojo/shell/application_manager/identity.h new file mode 100644 index 0000000..3d7e1d6 --- /dev/null +++ b/mojo/shell/application_manager/identity.h @@ -0,0 +1,31 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_IDENTITY_H_ +#define SHELL_APPLICATION_MANAGER_IDENTITY_H_ + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +/** + * Represents the identity of an application. |url| is the url of the + * application. |qualifier| is a string that allows to tie a specific instance + * of an application to another. It is used by content handlers that need to be + * run in the context of another application. + */ +struct Identity { + Identity(const GURL& url, const std::string& qualifier); + explicit Identity(const GURL& url); + bool operator<(const Identity& other) const; + + const GURL url; + const std::string qualifier; +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_IDENTITY_H_ diff --git a/mojo/shell/application_manager/local_fetcher.cc b/mojo/shell/application_manager/local_fetcher.cc new file mode 100644 index 0000000..a8b889d --- /dev/null +++ b/mojo/shell/application_manager/local_fetcher.cc @@ -0,0 +1,107 @@ +// Copyright 2015 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 "mojo/shell/application_manager/local_fetcher.h" + +#include "base/bind.h" +#include "base/files/file_util.h" +#include "base/format_macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/common/url_type_converters.h" +#include "url/url_util.h" + +namespace mojo { +namespace shell { + +namespace { + +void IgnoreResult(bool result) { +} + +} // namespace + +// A loader for local files. +LocalFetcher::LocalFetcher(const GURL& url, + const GURL& url_without_query, + const FetchCallback& loader_callback) + : Fetcher(loader_callback), url_(url), path_(UrlToFile(url_without_query)) { + TRACE_EVENT1("mojo_shell", "LocalFetcher::LocalFetcher", "url", url.spec()); + loader_callback_.Run(make_scoped_ptr(this)); +} + +base::FilePath LocalFetcher::UrlToFile(const GURL& url) { + DCHECK(url.SchemeIsFile()); + url::RawCanonOutputW<1024> output; + url::DecodeURLEscapeSequences(url.path().data(), + static_cast<int>(url.path().length()), &output); + base::string16 decoded_path = base::string16(output.data(), output.length()); +#if defined(OS_WIN) + base::TrimString(decoded_path, L"/", &decoded_path); + base::FilePath path(decoded_path); +#else + base::FilePath path(base::UTF16ToUTF8(decoded_path)); +#endif + return path; +} + +const GURL& LocalFetcher::GetURL() const { + return url_; +} + +GURL LocalFetcher::GetRedirectURL() const { + return GURL::EmptyGURL(); +} + +URLResponsePtr LocalFetcher::AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) { + URLResponsePtr response(URLResponse::New()); + response->url = String::From(url_); + DataPipe data_pipe; + response->body = data_pipe.consumer_handle.Pass(); + int64 file_size; + if (base::GetFileSize(path_, &file_size)) { + response->headers = Array<String>(1); + response->headers[0] = + base::StringPrintf("Content-Length: %" PRId64, file_size); + } + common::CopyFromFile(path_, data_pipe.producer_handle.Pass(), skip, + task_runner, base::Bind(&IgnoreResult)); + return response.Pass(); +} + +void LocalFetcher::AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) { + // Async for consistency with network case. + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, path_, base::PathExists(path_))); +} + +std::string LocalFetcher::MimeType() { + return ""; +} + +bool LocalFetcher::HasMojoMagic() { + std::string magic; + ReadFileToString(path_, &magic, strlen(kMojoMagic)); + return magic == kMojoMagic; +} + +bool LocalFetcher::PeekFirstLine(std::string* line) { + std::string start_of_file; + ReadFileToString(path_, &start_of_file, kMaxShebangLength); + size_t return_position = start_of_file.find('\n'); + if (return_position == std::string::npos) + return false; + *line = start_of_file.substr(0, return_position + 1); + return true; +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/local_fetcher.h b/mojo/shell/application_manager/local_fetcher.h new file mode 100644 index 0000000..843108f --- /dev/null +++ b/mojo/shell/application_manager/local_fetcher.h @@ -0,0 +1,51 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_LOCAL_FETCHER_H_ +#define SHELL_APPLICATION_MANAGER_LOCAL_FETCHER_H_ + +#include "base/files/file_path.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "mojo/shell/application_manager/fetcher.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// Implements Fetcher for file:// URLs. +class LocalFetcher : public Fetcher { + public: + LocalFetcher(const GURL& url, + const GURL& url_without_query, + const FetchCallback& loader_callback); + + private: + static base::FilePath UrlToFile(const GURL& url); + + const GURL& GetURL() const override; + GURL GetRedirectURL() const override; + + URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) override; + + void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) override; + + std::string MimeType() override; + + bool HasMojoMagic() override; + + bool PeekFirstLine(std::string* line) override; + + GURL url_; + base::FilePath path_; + + DISALLOW_COPY_AND_ASSIGN(LocalFetcher); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_LOCAL_FETCHER_H_ diff --git a/mojo/shell/application_manager/native_runner.h b/mojo/shell/application_manager/native_runner.h new file mode 100644 index 0000000..bc62b77 --- /dev/null +++ b/mojo/shell/application_manager/native_runner.h @@ -0,0 +1,58 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_NATIVE_RUNNER_H_ +#define SHELL_APPLICATION_MANAGER_NATIVE_RUNNER_H_ + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/interfaces/application/application.mojom.h" +#include "mojo/shell/native_application_support.h" + +namespace base { +class FilePath; +} + +namespace mojo { +namespace shell { + +// ApplicationManager requires implementations of NativeRunner and +// NativeRunnerFactory to run native applications. +class NativeRunner { + public: + virtual ~NativeRunner() {} + + // Loads the app in the file at |app_path| and runs it on some other + // thread/process. If |cleanup| is |DELETE|, this takes ownership of the file. + // |app_completed_callback| is posted (to the thread on which |Start()| was + // called) after |MojoMain()| completes. + // TODO(vtl): |app_path| and |cleanup| should probably be moved to the + // factory's Create(). Rationale: The factory may need information from the + // file to decide what kind of NativeRunner to make. + virtual void Start(const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) = 0; +}; + +class NativeRunnerFactory { + public: + // Options for running the native app. (This will contain, e.g., information + // about the sandbox profile, etc.) + struct Options { + // Constructs with default options. + Options() : force_in_process(false) {} + + bool force_in_process; + }; + + virtual ~NativeRunnerFactory() {} + virtual scoped_ptr<NativeRunner> Create(const Options& options) = 0; +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_NATIVE_RUNNER_H_ diff --git a/mojo/shell/application_manager/network_fetcher.cc b/mojo/shell/application_manager/network_fetcher.cc new file mode 100644 index 0000000..5bf3086 --- /dev/null +++ b/mojo/shell/application_manager/network_fetcher.cc @@ -0,0 +1,250 @@ +// Copyright 2015 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 "mojo/shell/application_manager/network_fetcher.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/trace_event/trace_event.h" +#include "crypto/secure_hash.h" +#include "crypto/sha2.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/services/network/public/interfaces/network_service.mojom.h" +#include "mojo/shell/application_manager/data_pipe_peek.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +NetworkFetcher::NetworkFetcher(bool disable_cache, + const GURL& url, + NetworkService* network_service, + const FetchCallback& loader_callback) + : Fetcher(loader_callback), + disable_cache_(false), + url_(url), + weak_ptr_factory_(this) { + StartNetworkRequest(url, network_service); +} + +NetworkFetcher::~NetworkFetcher() { +} + +const GURL& NetworkFetcher::GetURL() const { + return url_; +} + +GURL NetworkFetcher::GetRedirectURL() const { + if (!response_) + return GURL::EmptyGURL(); + + if (response_->redirect_url.is_null()) + return GURL::EmptyGURL(); + + return GURL(response_->redirect_url); +} + +URLResponsePtr NetworkFetcher::AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) { + if (skip != 0) { + MojoResult result = ReadDataRaw( + response_->body.get(), nullptr, &skip, + MOJO_READ_DATA_FLAG_ALL_OR_NONE | MOJO_READ_DATA_FLAG_DISCARD); + DCHECK_EQ(result, MOJO_RESULT_OK); + } + return response_.Pass(); +} + +void NetworkFetcher::RecordCacheToURLMapping(const base::FilePath& path, + const GURL& url) { + // This is used to extract symbols on android. + // TODO(eseidel): All users of this log should move to using the map file. + VLOG(INFO) << "Caching mojo app " << url << " at " << path.value(); + + base::FilePath temp_dir; + base::GetTempDir(&temp_dir); + base::ProcessId pid = base::Process::Current().Pid(); + std::string map_name = base::StringPrintf("mojo_shell.%d.maps", pid); + base::FilePath map_path = temp_dir.AppendASCII(map_name); + + // TODO(eseidel): Paths or URLs with spaces will need quoting. + std::string map_entry = + base::StringPrintf("%s %s\n", path.value().c_str(), url.spec().c_str()); + // TODO(eseidel): AppendToFile is missing O_CREAT, crbug.com/450696 + if (!PathExists(map_path)) { + base::WriteFile(map_path, map_entry.data(), + static_cast<int>(map_entry.length())); + } else { + base::AppendToFile(map_path, map_entry.data(), + static_cast<int>(map_entry.length())); + } +} + +// For remote debugging, GDB needs to be, a apriori, aware of the filename a +// library will be loaded from. AppIds should be be both predictable and unique, +// but any hash would work. Currently we use sha256 from crypto/secure_hash.h +bool NetworkFetcher::ComputeAppId(const base::FilePath& path, + std::string* digest_string) { + scoped_ptr<crypto::SecureHash> ctx( + crypto::SecureHash::Create(crypto::SecureHash::SHA256)); + base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); + if (!file.IsValid()) { + LOG(ERROR) << "Failed to open " << path.value() << " for computing AppId"; + return false; + } + char buf[1024]; + while (file.IsValid()) { + int bytes_read = file.ReadAtCurrentPos(buf, sizeof(buf)); + if (bytes_read == 0) + break; + ctx->Update(buf, bytes_read); + } + if (!file.IsValid()) { + LOG(ERROR) << "Error reading " << path.value(); + return false; + } + // The output is really a vector of unit8, we're cheating by using a string. + std::string output(crypto::kSHA256Length, 0); + ctx->Finish(string_as_array(&output), output.size()); + output = base::HexEncode(output.c_str(), output.size()); + // Using lowercase for compatiblity with sha256sum output. + *digest_string = base::StringToLowerASCII(output); + return true; +} + +bool NetworkFetcher::RenameToAppId(const GURL& url, + const base::FilePath& old_path, + base::FilePath* new_path) { + std::string app_id; + if (!ComputeAppId(old_path, &app_id)) + return false; + + // Using a hash of the url as a directory to prevent a race when the same + // bytes are downloaded from 2 different urls. In particular, if the same + // application is connected to twice concurrently with different query + // parameters, the directory will be different, which will prevent the + // collision. + std::string dirname = base::HexEncode( + crypto::SHA256HashString(url.spec()).data(), crypto::kSHA256Length); + + base::FilePath temp_dir; + base::GetTempDir(&temp_dir); + base::FilePath app_dir = temp_dir.AppendASCII(dirname); + // The directory is leaked, because it can be reused at any time if the same + // application is downloaded. Deleting it would be racy. This is only + // happening when --predictable-app-filenames is used. + bool result = base::CreateDirectoryAndGetError(app_dir, nullptr); + DCHECK(result); + std::string unique_name = base::StringPrintf("%s.mojo", app_id.c_str()); + *new_path = app_dir.AppendASCII(unique_name); + return base::Move(old_path, *new_path); +} + +void NetworkFetcher::CopyCompleted( + base::Callback<void(const base::FilePath&, bool)> callback, + bool success) { + if (success) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kPredictableAppFilenames)) { + // The copy completed, now move to $TMP/$APP_ID.mojo before the dlopen. + success = false; + base::FilePath new_path; + if (RenameToAppId(url_, path_, &new_path)) { + if (base::PathExists(new_path)) { + path_ = new_path; + success = true; + } + } + } + } + + if (success) + RecordCacheToURLMapping(path_, url_); + + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, path_, success)); +} + +void NetworkFetcher::AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) { + if (!path_.empty() || !response_) { + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, path_, base::PathExists(path_))); + return; + } + + base::CreateTemporaryFile(&path_); + common::CopyToFile(response_->body.Pass(), path_, task_runner, + base::Bind(&NetworkFetcher::CopyCompleted, + weak_ptr_factory_.GetWeakPtr(), callback)); +} + +std::string NetworkFetcher::MimeType() { + return response_->mime_type; +} + +bool NetworkFetcher::HasMojoMagic() { + std::string magic; + return BlockingPeekNBytes(response_->body.get(), &magic, strlen(kMojoMagic), + kPeekTimeout) && + magic == kMojoMagic; +} + +bool NetworkFetcher::PeekFirstLine(std::string* line) { + return BlockingPeekLine(response_->body.get(), line, kMaxShebangLength, + kPeekTimeout); +} + +void NetworkFetcher::StartNetworkRequest(const GURL& url, + NetworkService* network_service) { + TRACE_EVENT_ASYNC_BEGIN1("mojo_shell", "NetworkFetcher::NetworkRequest", this, + "url", url.spec()); + URLRequestPtr request(URLRequest::New()); + request->url = String::From(url); + request->auto_follow_redirects = false; + request->bypass_cache = disable_cache_; + + network_service->CreateURLLoader(GetProxy(&url_loader_)); + url_loader_->Start(request.Pass(), + base::Bind(&NetworkFetcher::OnLoadComplete, + weak_ptr_factory_.GetWeakPtr())); +} + +void NetworkFetcher::OnLoadComplete(URLResponsePtr response) { + TRACE_EVENT_ASYNC_END0("mojo_shell", "NetworkFetcher::NetworkRequest", this); + if (response->error) { + LOG(ERROR) << "Error (" << response->error->code << ": " + << response->error->description << ") while fetching " + << response->url; + loader_callback_.Run(nullptr); + return; + } + + if (response->status_code >= 400 && response->status_code < 600) { + LOG(ERROR) << "Error (" << response->status_code << ": " + << response->status_line << "): " + << "while fetching " << response->url; + loader_callback_.Run(nullptr); + return; + } + + response_ = response.Pass(); + loader_callback_.Run(make_scoped_ptr(this)); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/network_fetcher.h b/mojo/shell/application_manager/network_fetcher.h new file mode 100644 index 0000000..3ee90a9 --- /dev/null +++ b/mojo/shell/application_manager/network_fetcher.h @@ -0,0 +1,83 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_NETWORK_FETCHER_H_ +#define SHELL_APPLICATION_MANAGER_NETWORK_FETCHER_H_ + +#include "mojo/shell/application_manager/fetcher.h" + +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "mojo/services/network/public/interfaces/url_loader.mojom.h" +#include "url/gurl.h" + +namespace mojo { + +class NetworkService; + +namespace shell { + +// Implements Fetcher for http[s] files. +class NetworkFetcher : public Fetcher { + public: + NetworkFetcher(bool disable_cache, + const GURL& url, + NetworkService* network_service, + const FetchCallback& loader_callback); + + ~NetworkFetcher() override; + + private: + // TODO(hansmuller): Revisit this when a real peek operation is available. + static const MojoDeadline kPeekTimeout = MOJO_DEADLINE_INDEFINITE; + + const GURL& GetURL() const override; + GURL GetRedirectURL() const override; + + URLResponsePtr AsURLResponse(base::TaskRunner* task_runner, + uint32_t skip) override; + + static void RecordCacheToURLMapping(const base::FilePath& path, + const GURL& url); + + // AppIds should be be both predictable and unique, but any hash would work. + // Currently we use sha256 from crypto/secure_hash.h + static bool ComputeAppId(const base::FilePath& path, + std::string* digest_string); + + static bool RenameToAppId(const GURL& url, + const base::FilePath& old_path, + base::FilePath* new_path); + + void CopyCompleted(base::Callback<void(const base::FilePath&, bool)> callback, + bool success); + + void AsPath( + base::TaskRunner* task_runner, + base::Callback<void(const base::FilePath&, bool)> callback) override; + + std::string MimeType() override; + + bool HasMojoMagic() override; + + bool PeekFirstLine(std::string* line) override; + + void StartNetworkRequest(const GURL& url, NetworkService* network_service); + + void OnLoadComplete(URLResponsePtr response); + + bool disable_cache_; + const GURL url_; + URLLoaderPtr url_loader_; + URLResponsePtr response_; + base::FilePath path_; + base::WeakPtrFactory<NetworkFetcher> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NetworkFetcher); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_NETWORK_FETCHER_H_ diff --git a/mojo/shell/application_manager/query_util.cc b/mojo/shell/application_manager/query_util.cc new file mode 100644 index 0000000..252dcb6 --- /dev/null +++ b/mojo/shell/application_manager/query_util.cc @@ -0,0 +1,31 @@ +// Copyright 2015 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 "mojo/shell/application_manager/query_util.h" + +#include "base/strings/string_util.h" + +namespace mojo { +namespace shell { + +GURL GetBaseURLAndQuery(const GURL& url, std::string* query) { + if (!url.has_query()) { + if (query) + *query = ""; + return url; + } + + if (query) + *query = "?" + url.query(); + GURL::Replacements repl; + repl.SetQueryStr(""); + std::string result = url.ReplaceComponents(repl).spec(); + + // Remove the dangling '?' because it's ugly. + base::ReplaceChars(result, "?", "", &result); + return GURL(result); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/query_util.h b/mojo/shell/application_manager/query_util.h new file mode 100644 index 0000000..a4ca3f7e --- /dev/null +++ b/mojo/shell/application_manager/query_util.h @@ -0,0 +1,23 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_QUERY_UTIL_H_ +#define SHELL_APPLICATION_MANAGER_QUERY_UTIL_H_ + +#include <utility> + +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// Returns the base URL (without the query string). If |query| is not nullptr, +// set |*query| to the query string. If the url doesn't have a query string, +// |*query| is set to the empty string. +GURL GetBaseURLAndQuery(const GURL& url, std::string* query); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_QUERY_UTIL_H_ diff --git a/mojo/shell/application_manager/query_util_unittest.cc b/mojo/shell/application_manager/query_util_unittest.cc new file mode 100644 index 0000000..8a995c4 --- /dev/null +++ b/mojo/shell/application_manager/query_util_unittest.cc @@ -0,0 +1,47 @@ +// Copyright 2015 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 "mojo/shell/application_manager/query_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +TEST(QueryUtil, NoQuery) { + GURL url("http://www.example.com/"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, url); + EXPECT_EQ(query, ""); +} + +TEST(QueryUtil, WithEmptyQuery) { + GURL url("http://www.example.com/?"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("http://www.example.com/")); + EXPECT_EQ(query, "?"); +} + +TEST(QueryUtil, WithFullQuery) { + GURL url("http://www.example.com/?a=b&c=d"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("http://www.example.com/")); + EXPECT_EQ(query, "?a=b&c=d"); +} + +TEST(QueryUtil, ForFileURL) { + GURL url("file:///tmp/file?a=b&c=d"); + std::string query; + GURL base_url = GetBaseURLAndQuery(url, &query); + EXPECT_EQ(base_url, GURL("file:///tmp/file")); + EXPECT_EQ(query, "?a=b&c=d"); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/shell_impl.cc b/mojo/shell/application_manager/shell_impl.cc new file mode 100644 index 0000000..ed8c5ea --- /dev/null +++ b/mojo/shell/application_manager/shell_impl.cc @@ -0,0 +1,62 @@ +// Copyright 2015 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 "mojo/shell/application_manager/shell_impl.h" + +#include "mojo/common/common_type_converters.h" +#include "mojo/common/url_type_converters.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "third_party/mojo_services/src/content_handler/public/interfaces/content_handler.mojom.h" + +namespace mojo { +namespace shell { + +ShellImpl::ShellImpl(ApplicationPtr application, + ApplicationManager* manager, + const Identity& identity, + const base::Closure& on_application_end) + : manager_(manager), + identity_(identity), + on_application_end_(on_application_end), + application_(application.Pass()), + binding_(this) { + binding_.set_error_handler(this); +} + +ShellImpl::~ShellImpl() { +} + +void ShellImpl::InitializeApplication(Array<String> args) { + ShellPtr shell; + binding_.Bind(GetProxy(&shell)); + application_->Initialize(shell.Pass(), args.Pass(), identity_.url.spec()); +} + +void ShellImpl::ConnectToClient(const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + application_->AcceptConnection(String::From(requestor_url), services.Pass(), + exposed_services.Pass(), requested_url.spec()); +} + +// Shell implementation: +void ShellImpl::ConnectToApplication(const String& app_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + GURL app_gurl(app_url); + if (!app_gurl.is_valid()) { + LOG(ERROR) << "Error: invalid URL: " << app_url; + return; + } + manager_->ConnectToApplication(app_gurl, identity_.url, services.Pass(), + exposed_services.Pass(), base::Closure()); +} + +void ShellImpl::OnConnectionError() { + manager_->OnShellImplError(this); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/application_manager/shell_impl.h b/mojo/shell/application_manager/shell_impl.h new file mode 100644 index 0000000..ef557a6 --- /dev/null +++ b/mojo/shell/application_manager/shell_impl.h @@ -0,0 +1,62 @@ +// Copyright 2015 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. + +#ifndef SHELL_APPLICATION_MANAGER_SHELL_IMPL_H_ +#define SHELL_APPLICATION_MANAGER_SHELL_IMPL_H_ + +#include "base/callback.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/interfaces/application/application.mojom.h" +#include "mojo/public/interfaces/application/shell.mojom.h" +#include "mojo/shell/application_manager/identity.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +class ApplicationManager; + +class ShellImpl : public Shell, public ErrorHandler { + public: + ShellImpl(ApplicationPtr application, + ApplicationManager* manager, + const Identity& resolved_identity, + const base::Closure& on_application_end); + + ~ShellImpl() override; + + void InitializeApplication(Array<String> args); + + void ConnectToClient(const GURL& requested_url, + const GURL& requestor_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services); + + Application* application() { return application_.get(); } + const Identity& identity() const { return identity_; } + base::Closure on_application_end() const { return on_application_end_; } + + private: + // Shell implementation: + void ConnectToApplication(const String& app_url, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override; + + // ErrorHandler implementation: + void OnConnectionError() override; + + ApplicationManager* const manager_; + const Identity identity_; + base::Closure on_application_end_; + ApplicationPtr application_; + Binding<Shell> binding_; + + DISALLOW_COPY_AND_ASSIGN(ShellImpl); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_APPLICATION_MANAGER_SHELL_IMPL_H_ diff --git a/mojo/shell/application_manager/test.mojom b/mojo/shell/application_manager/test.mojom new file mode 100644 index 0000000..25700b5 --- /dev/null +++ b/mojo/shell/application_manager/test.mojom @@ -0,0 +1,23 @@ +// 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. + +module mojo.shell; + +interface TestService { + Test(string test_string) => (); +}; + +interface TestA { + CallB(); + CallCFromB(); +}; + +interface TestB { + B() => (); + CallC() => (); +}; + +interface TestC { + C() => (); +}; diff --git a/mojo/shell/child_process.cc b/mojo/shell/child_process.cc new file mode 100644 index 0000000..1140c14 --- /dev/null +++ b/mojo/shell/child_process.cc @@ -0,0 +1,40 @@ +// 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 "mojo/shell/child_process.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/shell/app_child_process.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +ChildProcess::~ChildProcess() { +} + +// static +scoped_ptr<ChildProcess> ChildProcess::Create( + const base::CommandLine& command_line) { + if (!command_line.HasSwitch(switches::kChildProcess)) + return scoped_ptr<ChildProcess>(); + + scoped_ptr<ChildProcess> rv(new AppChildProcess()); + if (!rv) + return nullptr; + + rv->platform_channel_ = + embedder::PlatformChannelPair::PassClientHandleFromParentProcess( + command_line); + CHECK(rv->platform_channel_.is_valid()); + return rv; +} + +ChildProcess::ChildProcess() { +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/child_process.h b/mojo/shell/child_process.h new file mode 100644 index 0000000..3da1c8f --- /dev/null +++ b/mojo/shell/child_process.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef SHELL_CHILD_PROCESS_H_ +#define SHELL_CHILD_PROCESS_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace shell { + +// A base class for child processes -- i.e., code that is actually run within +// the child process. (Instances are manufactured by |Create()|.) +class ChildProcess { + public: + virtual ~ChildProcess(); + + // Returns null if the command line doesn't indicate that this is a child + // process. |main()| should call this, and if it returns non-null it should + // call |Main()| (without a message loop on the current thread). + static scoped_ptr<ChildProcess> Create(const base::CommandLine& command_line); + + // To be implemented by subclasses. This is the "entrypoint" for a child + // process. Run with no message loop for the main thread. + virtual void Main() = 0; + + protected: + ChildProcess(); + + embedder::ScopedPlatformHandle* platform_channel() { + return &platform_channel_; + } + + private: + // Available in |Main()| (after a successful |Create()|). + embedder::ScopedPlatformHandle platform_channel_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcess); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_CHILD_PROCESS_H_ diff --git a/mojo/shell/child_process_host.cc b/mojo/shell/child_process_host.cc new file mode 100644 index 0000000..b8f7508 --- /dev/null +++ b/mojo/shell/child_process_host.cc @@ -0,0 +1,89 @@ +// 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 "mojo/shell/child_process_host.h" + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/process/kill.h" +#include "base/process/launch.h" +#include "base/task_runner.h" +#include "base/task_runner_util.h" +#include "mojo/shell/context.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +ChildProcessHost::ChildProcessHost(Context* context) : context_(context) { + platform_channel_ = platform_channel_pair_.PassServerHandle(); + CHECK(platform_channel_.is_valid()); +} + +ChildProcessHost::~ChildProcessHost() { + if (child_process_.IsValid()) { + LOG(WARNING) << "Destroying ChildProcessHost with unjoined child"; + child_process_.Close(); + } +} + +void ChildProcessHost::Start() { + DCHECK(!child_process_.IsValid()); + + WillStart(); + + CHECK(base::PostTaskAndReplyWithResult( + context_->task_runners()->blocking_pool(), FROM_HERE, + base::Bind(&ChildProcessHost::DoLaunch, base::Unretained(this)), + base::Bind(&ChildProcessHost::DidStart, base::Unretained(this)))); +} + +int ChildProcessHost::Join() { + DCHECK(child_process_.IsValid()); + int rv = -1; + LOG_IF(ERROR, !child_process_.WaitForExit(&rv)) + << "Failed to wait for child process"; + child_process_.Close(); + return rv; +} + +bool ChildProcessHost::DoLaunch() { + static const char* kForwardSwitches[] = { + switches::kTraceToConsole, switches::kV, switches::kVModule, + }; + + const base::CommandLine* parent_command_line = + base::CommandLine::ForCurrentProcess(); + base::CommandLine child_command_line(parent_command_line->GetProgram()); + child_command_line.CopySwitchesFrom(*parent_command_line, kForwardSwitches, + arraysize(kForwardSwitches)); + child_command_line.AppendSwitch(switches::kChildProcess); + + embedder::HandlePassingInformation handle_passing_info; + platform_channel_pair_.PrepareToPassClientHandleToChildProcess( + &child_command_line, &handle_passing_info); + + base::LaunchOptions options; +#if defined(OS_WIN) + options.start_hidden = true; + options.handles_to_inherit = &handle_passing_info; +#elif defined(OS_POSIX) + options.fds_to_remap = &handle_passing_info; +#endif + DVLOG(2) << "Launching child with command line: " + << child_command_line.GetCommandLineString(); + child_process_ = base::LaunchProcess(child_command_line, options); + if (!child_process_.IsValid()) + return false; + + platform_channel_pair_.ChildProcessLaunched(); + return true; +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/child_process_host.h b/mojo/shell/child_process_host.h new file mode 100644 index 0000000..21b3a4c8 --- /dev/null +++ b/mojo/shell/child_process_host.h @@ -0,0 +1,73 @@ +// 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. + +#ifndef SHELL_CHILD_PROCESS_HOST_H_ +#define SHELL_CHILD_PROCESS_HOST_H_ + +#include "base/macros.h" +#include "base/process/process.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/shell/child_process.h" // For |ChildProcess::Type|. + +namespace mojo { +namespace shell { + +class Context; + +// (Base) class for a "child process host". Handles launching and connecting a +// platform-specific "pipe" to the child, and supports joining the child +// process. +// +// This class is not thread-safe. It should be created/used/destroyed on a +// single thread. +// +// Note: Does not currently work on Windows before Vista. +class ChildProcessHost { + public: + explicit ChildProcessHost(Context* context); + virtual ~ChildProcessHost(); + + // |Start()|s the child process; calls |DidStart()| (on the thread on which + // |Start()| was called) when the child has been started (or failed to start). + // After calling |Start()|, this object must not be destroyed until + // |DidStart()| has been called. + // TODO(vtl): Consider using weak pointers and removing this requirement. + void Start(); + + // Waits for the child process to terminate, and returns its exit code. + // Note: If |Start()| has been called, this must not be called until the + // callback has been called. + int Join(); + + embedder::ScopedPlatformHandle* platform_channel() { + return &platform_channel_; + } + + virtual void WillStart() = 0; + virtual void DidStart(bool success) = 0; + + protected: + Context* context() const { return context_; } + + private: + bool DoLaunch(); + + Context* const context_; + + base::Process child_process_; + + embedder::PlatformChannelPair platform_channel_pair_; + + // Platform-specific "pipe" to the child process. Valid immediately after + // creation. + embedder::ScopedPlatformHandle platform_channel_; + + DISALLOW_COPY_AND_ASSIGN(ChildProcessHost); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_CHILD_PROCESS_HOST_H_ diff --git a/mojo/shell/command_line_util.cc b/mojo/shell/command_line_util.cc new file mode 100644 index 0000000..68bdfa4 --- /dev/null +++ b/mojo/shell/command_line_util.cc @@ -0,0 +1,90 @@ +// 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 "mojo/shell/command_line_util.h" + +#include <functional> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "mojo/shell/context.h" +#include "mojo/shell/switches.h" + +namespace mojo { +namespace shell { + +namespace { +GURL GetAppURLAndSetArgs(const std::string& app_url_and_args, + Context* context) { + std::vector<std::string> args; + GURL app_url = GetAppURLAndArgs(context, app_url_and_args, &args); + + if (args.size() > 1) + context->application_manager()->SetArgsForURL(args, app_url); + return app_url; +} +} // namespace + +bool ParseArgsFor(const std::string& arg, std::string* value) { + const std::string kArgsForSwitches[] = { + "-" + std::string(switches::kArgsFor) + "=", + "--" + std::string(switches::kArgsFor) + "=", + }; + for (size_t i = 0; i < arraysize(kArgsForSwitches); i++) { + const std::string& argsfor_switch = kArgsForSwitches[i]; + if (arg.compare(0, argsfor_switch.size(), argsfor_switch) == 0) { + *value = arg.substr(argsfor_switch.size(), std::string::npos); + return true; + } + } + return false; +} + +GURL GetAppURLAndArgs(Context* context, + const std::string& app_url_and_args, + std::vector<std::string>* args) { + // SplitString() returns empty strings for extra delimeter characters (' '). + base::SplitString(app_url_and_args, ' ', args); + args->erase(std::remove_if(args->begin(), args->end(), + std::mem_fun_ref(&std::string::empty)), + args->end()); + + if (args->empty()) + return GURL(); + GURL app_url = context->ResolveCommandLineURL((*args)[0]); + if (!app_url.is_valid()) { + LOG(ERROR) << "Error: invalid URL: " << (*args)[0]; + return app_url; + } + if (args->size() == 1) + args->clear(); + return app_url; +} + +void ApplyApplicationArgs(Context* context, const std::string& args) { + std::string args_for_value; + if (ParseArgsFor(args, &args_for_value)) + GetAppURLAndSetArgs(args_for_value, context); +} + +void RunCommandLineApps(Context* context) { + const auto& command_line = *base::CommandLine::ForCurrentProcess(); + for (const auto& arg : command_line.GetArgs()) { + std::string arg2; +#if defined(OS_WIN) + arg2 = base::UTF16ToUTF8(arg); +#else + arg2 = arg; +#endif + GURL url = GetAppURLAndSetArgs(arg2, context); + if (!url.is_valid()) + return; + context->Run(url); + } +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/command_line_util.h b/mojo/shell/command_line_util.h new file mode 100644 index 0000000..aa80002 --- /dev/null +++ b/mojo/shell/command_line_util.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef SHELL_COMMAND_LINE_UTIL_H_ +#define SHELL_COMMAND_LINE_UTIL_H_ + +#include "mojo/shell/context.h" + +namespace mojo { +namespace shell { + +// Parse the given arg, looking for an --args-for switch. If this is not the +// case, returns |false|. Otherwise, returns |true| and set |*value| to the +// value of the switch. +bool ParseArgsFor(const std::string& arg, std::string* value); + +// The value of app_url_and_args is "<mojo_app_url> [<args>...]", where args +// is a list of "configuration" arguments separated by spaces. If one or more +// arguments are specified they will be available when the Mojo application +// is initialized. This returns the mojo_app_url, and set args to the list of +// arguments. +GURL GetAppURLAndArgs(Context* context, + const std::string& app_url_and_args, + std::vector<std::string>* args); + +// Apply arguments for an application from a line with the following format: +// '--args-for=application_url arg1 arg2 arg3' +// This does nothing if the line has not the right format. +void ApplyApplicationArgs(Context* context, const std::string& args); + +// Run all application defined on the command line, using the given context. +void RunCommandLineApps(Context* context); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_COMMAND_LINE_UTIL_H_ diff --git a/mojo/shell/command_line_util_unittest.cc b/mojo/shell/command_line_util_unittest.cc new file mode 100644 index 0000000..547aa86 --- /dev/null +++ b/mojo/shell/command_line_util_unittest.cc @@ -0,0 +1,83 @@ +// 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 "mojo/shell/command_line_util.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +TEST(CommandLineUtil, ParseArgsFor) { + static const struct Expectation { + const char* args; + const char* value; + } EXPECTATIONS[] = { + {"", nullptr}, + {"hello", nullptr}, + {"args-for=mojo:app1", nullptr}, + {"--args-for", nullptr}, + {"--args-for=", ""}, + {"--args-for=mojo:app1", "mojo:app1"}, + {"--args-for=mojo:app1 hello world", "mojo:app1 hello world"}, + {"-args-for", nullptr}, + {"-args-for=", ""}, + {"-args-for=mojo:app1", "mojo:app1"}, + {"-args-for=mojo:app1 hello world", "mojo:app1 hello world"}}; + for (auto& expectation : EXPECTATIONS) { + std::string value; + bool result = ParseArgsFor(expectation.args, &value); + EXPECT_EQ(bool(expectation.value), result); + if (expectation.value && result) + EXPECT_EQ(value, expectation.value); + } +} + +TEST(CommandLineUtil, GetAppURLAndArgs) { + const char* NO_ARGUMENTS[] = {nullptr}; + const char* ONE_ARGUMENTS[] = {"1", nullptr}; + const char* TWO_ARGUMENTS[] = {"1", "two", nullptr}; + static const struct Expectation { + const char* args; + const char* url; + const char** values; + } EXPECTATIONS[] = { + {"", nullptr, nullptr}, + {"foo", "file:///root/foo", NO_ARGUMENTS}, + {"/foo", "file:///foo", NO_ARGUMENTS}, + {"file:foo", "file:///root/foo", NO_ARGUMENTS}, + {"file:///foo", "file:///foo", NO_ARGUMENTS}, + {"http://example.com", "http://example.com", NO_ARGUMENTS}, + {"http://example.com 1", "http://example.com", ONE_ARGUMENTS}, + {"http://example.com 1 ", "http://example.com", ONE_ARGUMENTS}, + {"http://example.com 1 ", "http://example.com", ONE_ARGUMENTS}, + {"http://example.com 1 two", "http://example.com", TWO_ARGUMENTS}, + {" http://example.com 1 two ", + "http://example.com", + TWO_ARGUMENTS}}; + Context context; + context.SetCommandLineCWD(base::FilePath(FILE_PATH_LITERAL("/root"))); + for (auto& expectation : EXPECTATIONS) { + std::vector<std::string> args; + GURL result(GetAppURLAndArgs(&context, expectation.args, &args)); + EXPECT_EQ(bool(expectation.url), result.is_valid()); + if (expectation.url && result.is_valid()) { + EXPECT_EQ(GURL(expectation.url), result); + std::vector<std::string> expected_args; + if (expectation.values) { + if (*expectation.values) + expected_args.push_back(expectation.url); + for (const char** value = expectation.values; *value; ++value) + expected_args.push_back(*value); + } + EXPECT_EQ(expected_args, args); + } + } +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/context.cc b/mojo/shell/context.cc new file mode 100644 index 0000000..fbde07d --- /dev/null +++ b/mojo/shell/context.cc @@ -0,0 +1,328 @@ +// 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. + +#include "mojo/shell/context.h" + +#include <vector> + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/lazy_instance.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "mojo/common/trace_controller_impl.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/simple_platform_support.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/services/tracing/tracing.mojom.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/command_line_util.h" +#include "mojo/shell/filename_util.h" +#include "mojo/shell/in_process_native_runner.h" +#include "mojo/shell/out_of_process_native_runner.h" +#include "mojo/shell/switches.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { +namespace { + +// Used to ensure we only init once. +class Setup { + public: + Setup() { + embedder::Init(make_scoped_ptr(new embedder::SimplePlatformSupport())); + } + + ~Setup() {} + + private: + DISALLOW_COPY_AND_ASSIGN(Setup); +}; + +bool ConfigureURLMappings(const base::CommandLine& command_line, + Context* context) { + URLResolver* resolver = context->url_resolver(); + + // Configure the resolution of unknown mojo: URLs. + GURL base_url; + if (command_line.HasSwitch(switches::kOrigin)) + base_url = GURL(command_line.GetSwitchValueASCII(switches::kOrigin)); + else + // Use the shell's file root if the base was not specified. + base_url = context->ResolveShellFileURL(""); + + if (!base_url.is_valid()) + return false; + + resolver->SetMojoBaseURL(base_url); + + // The network service must be loaded from the filesystem. + // This mapping is done before the command line URL mapping are processed, so + // that it can be overridden. + resolver->AddURLMapping( + GURL("mojo:network_service"), + context->ResolveShellFileURL("file:network_service.mojo")); + + // Command line URL mapping. + std::vector<URLResolver::OriginMapping> origin_mappings = + URLResolver::GetOriginMappings(command_line.argv()); + for (const auto& origin_mapping : origin_mappings) + resolver->AddOriginMapping(GURL(origin_mapping.origin), + GURL(origin_mapping.base_url)); + + if (command_line.HasSwitch(switches::kURLMappings)) { + const std::string mappings = + command_line.GetSwitchValueASCII(switches::kURLMappings); + + base::StringPairs pairs; + if (!base::SplitStringIntoKeyValuePairs(mappings, '=', ',', &pairs)) + return false; + using StringPair = std::pair<std::string, std::string>; + for (const StringPair& pair : pairs) { + const GURL from(pair.first); + const GURL to = context->ResolveCommandLineURL(pair.second); + if (!from.is_valid() || !to.is_valid()) + return false; + resolver->AddURLMapping(from, to); + } + } + return true; +} + +void InitContentHandlers(ApplicationManager* manager, + const base::CommandLine& command_line) { + // Default content handlers. + manager->RegisterContentHandler("application/pdf", GURL("mojo:pdf_viewer")); + manager->RegisterContentHandler("image/png", GURL("mojo:png_viewer")); + manager->RegisterContentHandler("text/html", GURL("mojo:html_viewer")); + + // Command-line-specified content handlers. + std::string handlers_spec = + command_line.GetSwitchValueASCII(switches::kContentHandlers); + if (handlers_spec.empty()) + return; + +#if defined(OS_ANDROID) + // TODO(eseidel): On Android we pass command line arguments is via the + // 'parameters' key on the intent, which we specify during 'am shell start' + // via --esa, however that expects comma-separated values and says: + // am shell --help: + // [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]] + // (to embed a comma into a string escape it using "\,") + // Whatever takes 'parameters' and constructs a CommandLine is failing to + // un-escape the commas, we need to move this fix to that file. + ReplaceSubstringsAfterOffset(&handlers_spec, 0, "\\,", ","); +#endif + + std::vector<std::string> parts; + base::SplitString(handlers_spec, ',', &parts); + if (parts.size() % 2 != 0) { + LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers + << ": must be a comma-separated list of mimetype/url pairs." + << handlers_spec; + return; + } + + for (size_t i = 0; i < parts.size(); i += 2) { + GURL url(parts[i + 1]); + if (!url.is_valid()) { + LOG(ERROR) << "Invalid value for switch " << switches::kContentHandlers + << ": '" << parts[i + 1] << "' is not a valid URL."; + return; + } + // TODO(eseidel): We should also validate that the mimetype is valid + // net/base/mime_util.h could do this, but we don't want to depend on net. + manager->RegisterContentHandler(parts[i], url); + } +} + +void InitNativeOptions(ApplicationManager* manager, + const base::CommandLine& command_line) { + std::vector<std::string> force_in_process_url_list; + base::SplitString(command_line.GetSwitchValueASCII(switches::kForceInProcess), + ',', &force_in_process_url_list); + for (const auto& force_in_process_url : force_in_process_url_list) { + GURL gurl(force_in_process_url); + if (!gurl.is_valid()) { + LOG(ERROR) << "Invalid value for switch " << switches::kForceInProcess + << ": '" << force_in_process_url << "'is not a valid URL."; + return; + } + + NativeRunnerFactory::Options options; + options.force_in_process = true; + manager->SetNativeOptionsForURL(options, gurl); + } +} + +class TracingServiceProvider : public ServiceProvider { + public: + explicit TracingServiceProvider(InterfaceRequest<ServiceProvider> request) + : binding_(this, request.Pass()) {} + ~TracingServiceProvider() override {} + + void ConnectToService(const String& service_name, + ScopedMessagePipeHandle client_handle) override { + if (service_name == tracing::TraceController::Name_) { + new TraceControllerImpl( + MakeRequest<tracing::TraceController>(client_handle.Pass())); + } + } + + private: + StrongBinding<ServiceProvider> binding_; + + DISALLOW_COPY_AND_ASSIGN(TracingServiceProvider); +}; + +} // namespace + +Context::Context() : application_manager_(this) { + DCHECK(!base::MessageLoop::current()); + + // By default assume that the local apps reside alongside the shell. + // TODO(ncbray): really, this should be passed in rather than defaulting. + // This default makes sense for desktop but not Android. + base::FilePath shell_dir; + PathService::Get(base::DIR_MODULE, &shell_dir); + SetShellFileRoot(shell_dir); + + base::FilePath cwd; + PathService::Get(base::DIR_CURRENT, &cwd); + SetCommandLineCWD(cwd); +} + +Context::~Context() { + DCHECK(!base::MessageLoop::current()); +} + +// static +void Context::EnsureEmbedderIsInitialized() { + static base::LazyInstance<Setup>::Leaky setup = LAZY_INSTANCE_INITIALIZER; + setup.Get(); +} + +void Context::SetShellFileRoot(const base::FilePath& path) { + shell_file_root_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path)); +} + +GURL Context::ResolveShellFileURL(const std::string& path) { + return shell_file_root_.Resolve(path); +} + +void Context::SetCommandLineCWD(const base::FilePath& path) { + command_line_cwd_ = AddTrailingSlashIfNeeded(FilePathToFileURL(path)); +} + +GURL Context::ResolveCommandLineURL(const std::string& path) { + return command_line_cwd_.Resolve(path); +} + +bool Context::Init() { + TRACE_EVENT0("mojo_shell", "Context::Init"); + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + + if (command_line.HasSwitch(switches::kWaitForDebugger)) + base::debug::WaitForDebugger(60, true); + + EnsureEmbedderIsInitialized(); + task_runners_.reset( + new TaskRunners(base::MessageLoop::current()->message_loop_proxy())); + + // TODO(vtl): Probably these failures should be checked before |Init()|, and + // this function simply shouldn't fail. + if (!shell_file_root_.is_valid()) + return false; + if (!ConfigureURLMappings(command_line, this)) + return false; + + // TODO(vtl): This should be MASTER, not NONE. + embedder::InitIPCSupport( + embedder::ProcessType::NONE, task_runners_->shell_runner(), this, + task_runners_->io_runner(), embedder::ScopedPlatformHandle()); + + scoped_ptr<NativeRunnerFactory> runner_factory; + if (command_line.HasSwitch(switches::kEnableMultiprocess)) + runner_factory.reset(new OutOfProcessNativeRunnerFactory(this)); + else + runner_factory.reset(new InProcessNativeRunnerFactory(this)); + application_manager_.set_blocking_pool(task_runners_->blocking_pool()); + application_manager_.set_native_runner_factory(runner_factory.Pass()); + application_manager_.set_disable_cache( + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableCache)); + + InitContentHandlers(&application_manager_, command_line); + InitNativeOptions(&application_manager_, command_line); + + ServiceProviderPtr tracing_service_provider_ptr; + new TracingServiceProvider(GetProxy(&tracing_service_provider_ptr)); + application_manager_.ConnectToApplication( + GURL("mojo:tracing"), GURL(""), nullptr, + tracing_service_provider_ptr.Pass(), base::Closure()); + + return true; +} + +void Context::Shutdown() { + TRACE_EVENT0("mojo_shell", "Context::Shutdown"); + DCHECK_EQ(base::MessageLoop::current()->task_runner(), + task_runners_->shell_runner()); + embedder::ShutdownIPCSupport(); + // We'll quit when we get OnShutdownComplete(). + base::MessageLoop::current()->Run(); +} + +GURL Context::ResolveURL(const GURL& url) { + return url_resolver_.ResolveMojoURL(url); +} + +GURL Context::ResolveMappings(const GURL& url) { + return url_resolver_.ApplyMappings(url); +} + +void Context::OnShutdownComplete() { + DCHECK_EQ(base::MessageLoop::current()->task_runner(), + task_runners_->shell_runner()); + base::MessageLoop::current()->Quit(); +} + +void Context::Run(const GURL& url) { + ServiceProviderPtr services; + ServiceProviderPtr exposed_services; + + app_urls_.insert(url); + application_manager_.ConnectToApplication( + url, GURL(), GetProxy(&services), exposed_services.Pass(), + base::Bind(&Context::OnApplicationEnd, base::Unretained(this), url)); +} + +void Context::OnApplicationEnd(const GURL& url) { + if (app_urls_.find(url) != app_urls_.end()) { + app_urls_.erase(url); + if (app_urls_.empty() && base::MessageLoop::current()->is_running()) { + DCHECK_EQ(base::MessageLoop::current()->task_runner(), + task_runners_->shell_runner()); + base::MessageLoop::current()->Quit(); + } + } +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/context.h b/mojo/shell/context.h new file mode 100644 index 0000000..4be704e --- /dev/null +++ b/mojo/shell/context.h @@ -0,0 +1,87 @@ +// 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. + +#ifndef SHELL_CONTEXT_H_ +#define SHELL_CONTEXT_H_ + +#include <string> + +#include "base/macros.h" +#include "mojo/edk/embedder/process_delegate.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/task_runners.h" +#include "mojo/shell/url_resolver.h" + +namespace mojo { +namespace shell { + +class NativeApplicationLoader; + +// The "global" context for the shell's main process. +class Context : public ApplicationManager::Delegate, + public embedder::ProcessDelegate { + public: + Context(); + ~Context() override; + + static void EnsureEmbedderIsInitialized(); + + // Point to the directory containing installed services, such as the network + // service. By default this directory is used as the base URL for resolving + // unknown mojo: URLs. The network service will be loaded from this directory, + // even when the base URL for unknown mojo: URLs is overridden. + void SetShellFileRoot(const base::FilePath& path); + + // Resolve an URL relative to the shell file root. This is a nop for + // everything but relative file URLs or URLs without a scheme. + GURL ResolveShellFileURL(const std::string& path); + + // Override the CWD, which is used for resolving file URLs passed in from the + // command line. + void SetCommandLineCWD(const base::FilePath& path); + + // Resolve an URL relative to the CWD mojo_shell was invoked from. This is a + // nop for everything but relative file URLs or URLs without a scheme. + GURL ResolveCommandLineURL(const std::string& path); + + // This must be called with a message loop set up for the current thread, + // which must remain alive until after Shutdown() is called. Returns true on + // success. + bool Init(); + + // If Init() was called and succeeded, this must be called before destruction. + void Shutdown(); + + void Run(const GURL& url); + + TaskRunners* task_runners() { return task_runners_.get(); } + ApplicationManager* application_manager() { return &application_manager_; } + URLResolver* url_resolver() { return &url_resolver_; } + + private: + class NativeViewportApplicationLoader; + + // ApplicationManager::Delegate overrides. + GURL ResolveURL(const GURL& url) override; + GURL ResolveMappings(const GURL& url) override; + + // ProcessDelegate implementation. + void OnShutdownComplete() override; + + void OnApplicationEnd(const GURL& url); + + std::set<GURL> app_urls_; + scoped_ptr<TaskRunners> task_runners_; + ApplicationManager application_manager_; + URLResolver url_resolver_; + GURL shell_file_root_; + GURL command_line_cwd_; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_CONTEXT_H_ diff --git a/mojo/shell/data_pipe_peek_unittest.cc b/mojo/shell/data_pipe_peek_unittest.cc new file mode 100644 index 0000000..126901b --- /dev/null +++ b/mojo/shell/data_pipe_peek_unittest.cc @@ -0,0 +1,113 @@ +// 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 "mojo/shell/application_manager/data_pipe_peek.h" + +#include "mojo/shell/context.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +TEST(DataPipePeek, PeekNBytes) { + Context::EnsureEmbedderIsInitialized(); + + DataPipe data_pipe; + DataPipeConsumerHandle consumer(data_pipe.consumer_handle.get()); + DataPipeProducerHandle producer(data_pipe.producer_handle.get()); + + // Inialize the pipe with 4 bytes. + + const char* s4 = "1234"; + uint32_t num_bytes4 = 4; + EXPECT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer, s4, &num_bytes4, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(4u, num_bytes4); + + // We're not consuming data, so peeking for 4 bytes should always succeed. + + std::string bytes; + MojoDeadline timeout = 0; + EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout)); + EXPECT_EQ(bytes, std::string(s4)); + + timeout = 1000; // 1ms + EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout)); + EXPECT_EQ(bytes, std::string(s4)); + + timeout = MOJO_DEADLINE_INDEFINITE; + EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes4, timeout)); + EXPECT_EQ(bytes, std::string(s4)); + + // Peeking for 5 bytes should fail, until another byte is written. + + uint32_t bytes1 = 1; + uint32_t num_bytes5 = 5; + const char* s1 = "5"; + const char* s5 = "12345"; + + timeout = 0; + EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout)); + + timeout = 500; // Should cause peek to timeout after about 0.5ms. + EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout)); + + EXPECT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer, s1, &bytes1, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(1u, bytes1); + + EXPECT_TRUE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout)); + EXPECT_EQ(bytes, std::string(s5)); + + // If the consumer side of the pipe is closed, peek should fail. + + data_pipe.consumer_handle.reset(); + timeout = 0; + EXPECT_FALSE(BlockingPeekNBytes(consumer, &bytes, num_bytes5, timeout)); +} + +TEST(DataPipePeek, PeekLine) { + Context::EnsureEmbedderIsInitialized(); + + DataPipe data_pipe; + DataPipeConsumerHandle consumer(data_pipe.consumer_handle.get()); + DataPipeProducerHandle producer(data_pipe.producer_handle.get()); + + // Inialize the pipe with 4 bytes and no newline. + + const char* s4 = "1234"; + uint32_t num_bytes4 = 4; + EXPECT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer, s4, &num_bytes4, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(4u, num_bytes4); + + // Peeking for a line should fail. + + std::string str; + size_t max_str_length = 5; + MojoDeadline timeout = 0; + EXPECT_FALSE(BlockingPeekLine(consumer, &str, max_str_length, timeout)); + + // Writing a newline should cause PeekLine to succeed. + + uint32_t bytes1 = 1; + const char* s1 = "\n"; + EXPECT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer, s1, &bytes1, MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(1u, bytes1); + + EXPECT_TRUE(BlockingPeekLine(consumer, &str, max_str_length, timeout)); + EXPECT_EQ(str, std::string(s4) + "\n"); + + // If the max_line_length parameter is less than the length of the + // newline terminated string, then peek should fail. + + max_str_length = 3; + EXPECT_FALSE(BlockingPeekLine(consumer, &str, max_str_length, timeout)); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/desktop/main.cc b/mojo/shell/desktop/main.cc new file mode 100644 index 0000000..9214985 --- /dev/null +++ b/mojo/shell/desktop/main.cc @@ -0,0 +1,185 @@ +// 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. + +#include <stdio.h> +#include <string.h> + +#include <algorithm> +#include <iostream> + +#include "base/at_exit.h" +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_util.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/trace_event/trace_event.h" +#include "mojo/shell/child_process.h" +#include "mojo/shell/command_line_util.h" +#include "mojo/shell/context.h" +#include "mojo/shell/init.h" +#include "mojo/shell/switches.h" + +namespace { + +void Usage() { + std::cerr << "Launch Mojo applications.\n"; + std::cerr + << "Usage: mojo_shell" + << " [--" << switches::kArgsFor << "=<mojo-app>]" + << " [--" << switches::kContentHandlers << "=<handlers>]" + << " [--" << switches::kEnableExternalApplications << "]" + << " [--" << switches::kDisableCache << "]" + << " [--" << switches::kEnableMultiprocess << "]" + << " [--" << switches::kOrigin << "=<url-lib-path>]" + << " [--" << switches::kTraceStartup << "]" + << " [--" << switches::kURLMappings << "=from1=to1,from2=to2]" + << " [--" << switches::kPredictableAppFilenames << "]" + << " [--" << switches::kWaitForDebugger << "]" + << " <mojo-app> ...\n\n" + << "A <mojo-app> is a Mojo URL or a Mojo URL and arguments within " + << "quotes.\n" + << "Example: mojo_shell \"mojo:js_standalone test.js\".\n" + << "<url-lib-path> is searched for shared libraries named by mojo URLs.\n" + << "The value of <handlers> is a comma separated list like:\n" + << "text/html,mojo:html_viewer," + << "application/javascript,mojo:js_content_handler\n"; +} + +// Whether we're currently tracing. +bool g_tracing = false; + +// Number of tracing blocks written. +uint32_t g_blocks = 0; + +// Trace file, if open. +FILE* g_trace_file = nullptr; + +void WriteTraceDataCollected( + base::WaitableEvent* event, + const scoped_refptr<base::RefCountedString>& events_str, + bool has_more_events) { + if (g_blocks) { + fwrite(",", 1, 1, g_trace_file); + } + + ++g_blocks; + fwrite(events_str->data().c_str(), 1, events_str->data().length(), + g_trace_file); + if (!has_more_events) { + static const char kEnd[] = "]}"; + fwrite(kEnd, 1, strlen(kEnd), g_trace_file); + PCHECK(fclose(g_trace_file) == 0); + g_trace_file = nullptr; + event->Signal(); + } +} + +void EndTraceAndFlush(base::WaitableEvent* event) { + g_trace_file = fopen("mojo_shell.trace", "w+"); + PCHECK(g_trace_file); + static const char kStart[] = "{\"traceEvents\":["; + fwrite(kStart, 1, strlen(kStart), g_trace_file); + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + base::trace_event::TraceLog::GetInstance()->Flush( + base::Bind(&WriteTraceDataCollected, base::Unretained(event))); +} + +void StopTracingAndFlushToDisk() { + g_tracing = false; + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + base::WaitableEvent flush_complete_event(false, false); + // TraceLog::Flush requires a message loop but we've already shut ours down. + // Spin up a new thread to flush things out. + base::Thread flush_thread("mojo_shell_trace_event_flush"); + flush_thread.Start(); + flush_thread.message_loop()->PostTask( + FROM_HERE, + base::Bind(EndTraceAndFlush, base::Unretained(&flush_complete_event))); + flush_complete_event.Wait(); +} + +} // namespace + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + base::CommandLine::Init(argc, argv); + + mojo::shell::InitializeLogging(); + + // TODO(vtl): Unify parent and child process cases to the extent possible. + if (scoped_ptr<mojo::shell::ChildProcess> child_process = + mojo::shell::ChildProcess::Create( + *base::CommandLine::ForCurrentProcess())) { + child_process->Main(); + } else { + // Only check the command line for the main process. + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + + const std::set<std::string> all_switches = switches::GetAllSwitches(); + const base::CommandLine::SwitchMap switches = command_line.GetSwitches(); + bool found_unknown_switch = false; + for (const auto& s : switches) { + if (all_switches.find(s.first) == all_switches.end()) { + std::cerr << "unknown switch: " << s.first << std::endl; + found_unknown_switch = true; + } + } + + if (found_unknown_switch || + (!command_line.HasSwitch(switches::kEnableExternalApplications) && + (command_line.HasSwitch(switches::kHelp) || + command_line.GetArgs().empty()))) { + Usage(); + return 0; + } + + if (command_line.HasSwitch(switches::kTraceStartup)) { + g_tracing = true; + base::trace_event::CategoryFilter category_filter( + command_line.GetSwitchValueASCII(switches::kTraceStartup)); + base::trace_event::TraceLog::GetInstance()->SetEnabled( + category_filter, base::trace_event::TraceLog::RECORDING_MODE, + base::trace_event::TraceOptions( + base::trace_event::RECORD_UNTIL_FULL)); + } + + // We want the shell::Context to outlive the MessageLoop so that pipes are + // all gracefully closed / error-out before we try to shut the Context down. + mojo::shell::Context shell_context; + { + base::MessageLoop message_loop; + if (!shell_context.Init()) { + Usage(); + return 0; + } + if (g_tracing) { + message_loop.PostDelayedTask(FROM_HERE, + base::Bind(StopTracingAndFlushToDisk), + base::TimeDelta::FromSeconds(5)); + } + + // The mojo_shell --args-for command-line switch is handled specially + // because it can appear more than once. The base::CommandLine class + // collapses multiple occurrences of the same switch. + for (int i = 1; i < argc; i++) { + ApplyApplicationArgs(&shell_context, argv[i]); + } + + message_loop.PostTask( + FROM_HERE, + base::Bind(&mojo::shell::RunCommandLineApps, &shell_context)); + message_loop.Run(); + + // Must be called before |message_loop| is destroyed. + shell_context.Shutdown(); + } + } + + if (g_tracing) + StopTracingAndFlushToDisk(); + return 0; +} diff --git a/mojo/shell/filename_util.cc b/mojo/shell/filename_util.cc new file mode 100644 index 0000000..073bdda --- /dev/null +++ b/mojo/shell/filename_util.cc @@ -0,0 +1,72 @@ +// 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 "mojo/shell/filename_util.h" + +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "url/gurl.h" +#include "url/url_canon_internal.h" +#include "url/url_util.h" + +namespace mojo { +namespace shell { + +// Prefix to prepend to get a file URL. +static const base::FilePath::CharType kFileURLPrefix[] = + FILE_PATH_LITERAL("file://"); + +GURL FilePathToFileURL(const base::FilePath& path) { + // Produce a URL like "file:///C:/foo" for a regular file, or + // "file://///server/path" for UNC. The URL canonicalizer will fix up the + // latter case to be the canonical UNC form: "file://server/path" + base::FilePath::StringType url_string(kFileURLPrefix); + if (!path.IsAbsolute()) { + base::FilePath current_dir; + PathService::Get(base::DIR_CURRENT, ¤t_dir); + url_string.append(current_dir.value()); + url_string.push_back(base::FilePath::kSeparators[0]); + } + url_string.append(path.value()); + + // Now do replacement of some characters. Since we assume the input is a + // literal filename, anything the URL parser might consider special should + // be escaped here. + + // This must be the first substitution since others will introduce percents as + // the escape character + ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("%"), + FILE_PATH_LITERAL("%25")); + + // A semicolon is supposed to be some kind of separator according to RFC 2396. + ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL(";"), + FILE_PATH_LITERAL("%3B")); + + ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("#"), + FILE_PATH_LITERAL("%23")); + + ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("?"), + FILE_PATH_LITERAL("%3F")); + +#if defined(OS_POSIX) + ReplaceSubstringsAfterOffset(&url_string, 0, FILE_PATH_LITERAL("\\"), + FILE_PATH_LITERAL("%5C")); +#endif + + return GURL(url_string); +} + +GURL AddTrailingSlashIfNeeded(const GURL& url) { + if (!url.has_path() || *url.path().rbegin() == '/') + return url; + + std::string path(url.path() + '/'); + GURL::Replacements replacements; + replacements.SetPathStr(path); + return url.ReplaceComponents(replacements); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/filename_util.h b/mojo/shell/filename_util.h new file mode 100644 index 0000000..28d0e78 --- /dev/null +++ b/mojo/shell/filename_util.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef SHELL_FILENAME_UTIL_H_ +#define SHELL_FILENAME_UTIL_H_ + +class GURL; + +namespace base { +class FilePath; +} + +namespace mojo { +namespace shell { + +// Given the full path to a file name, creates a file: URL. The returned URL +// may not be valid if the input is malformed. +GURL FilePathToFileURL(const base::FilePath& path); + +// This URL is going to be treated as a directory. Ensure there is a trailing +// slash so that GURL.Resolve(...) works correctly. +GURL AddTrailingSlashIfNeeded(const GURL& url); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_FILENAME_UTIL_H_ diff --git a/mojo/shell/in_process_native_runner.cc b/mojo/shell/in_process_native_runner.cc new file mode 100644 index 0000000..7431fe0 --- /dev/null +++ b/mojo/shell/in_process_native_runner.cc @@ -0,0 +1,72 @@ +// 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 "mojo/shell/in_process_native_runner.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/location.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/platform_thread.h" +#include "mojo/shell/native_application_support.h" + +namespace mojo { +namespace shell { + +InProcessNativeRunner::InProcessNativeRunner(Context* context) + : cleanup_(NativeApplicationCleanup::DONT_DELETE), app_library_(nullptr) { +} + +InProcessNativeRunner::~InProcessNativeRunner() { + // It is important to let the thread exit before unloading the DSO (when + // app_library_ is destructed), because the library may have registered + // thread-local data and destructors to run on thread termination. + if (thread_) { + DCHECK(thread_->HasBeenStarted()); + DCHECK(!thread_->HasBeenJoined()); + thread_->Join(); + } +} + +void InProcessNativeRunner::Start( + const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) { + app_path_ = app_path; + cleanup_ = cleanup; + + DCHECK(!application_request_.is_pending()); + application_request_ = application_request.Pass(); + + DCHECK(app_completed_callback_runner_.is_null()); + app_completed_callback_runner_ = + base::Bind(&base::TaskRunner::PostTask, base::MessageLoopProxy::current(), + FROM_HERE, app_completed_callback); + + DCHECK(!thread_); + thread_.reset(new base::DelegateSimpleThread(this, "app_thread")); + thread_->Start(); +} + +void InProcessNativeRunner::Run() { + DVLOG(2) << "Loading/running Mojo app in process from library: " + << app_path_.value() + << " thread id=" << base::PlatformThread::CurrentId(); + + // TODO(vtl): ScopedNativeLibrary doesn't have a .get() method! + base::NativeLibrary app_library = LoadNativeApplication(app_path_, cleanup_); + app_library_.Reset(app_library); + RunNativeApplication(app_library, application_request_.Pass()); + app_completed_callback_runner_.Run(); + app_completed_callback_runner_.Reset(); +} + +scoped_ptr<NativeRunner> InProcessNativeRunnerFactory::Create( + const Options& options) { + return make_scoped_ptr(new InProcessNativeRunner(context_)); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/in_process_native_runner.h b/mojo/shell/in_process_native_runner.h new file mode 100644 index 0000000..a5639f4 --- /dev/null +++ b/mojo/shell/in_process_native_runner.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef SHELL_IN_PROCESS_NATIVE_RUNNER_H_ +#define SHELL_IN_PROCESS_NATIVE_RUNNER_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/scoped_native_library.h" +#include "base/threading/simple_thread.h" +#include "mojo/shell/application_manager/native_runner.h" +#include "mojo/shell/native_application_support.h" + +namespace mojo { +namespace shell { + +class Context; + +// An implementation of |NativeRunner| that loads/runs the given app (from the +// file system) on a separate thread (in the current process). +class InProcessNativeRunner : public NativeRunner, + public base::DelegateSimpleThread::Delegate { + public: + explicit InProcessNativeRunner(Context* context); + ~InProcessNativeRunner() override; + + // |NativeRunner| method: + void Start(const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) override; + + private: + // |base::DelegateSimpleThread::Delegate| method: + void Run() override; + + base::FilePath app_path_; + NativeApplicationCleanup cleanup_; + InterfaceRequest<Application> application_request_; + base::Callback<bool(void)> app_completed_callback_runner_; + + base::ScopedNativeLibrary app_library_; + scoped_ptr<base::DelegateSimpleThread> thread_; + + DISALLOW_COPY_AND_ASSIGN(InProcessNativeRunner); +}; + +class InProcessNativeRunnerFactory : public NativeRunnerFactory { + public: + explicit InProcessNativeRunnerFactory(Context* context) : context_(context) {} + ~InProcessNativeRunnerFactory() override {} + + scoped_ptr<NativeRunner> Create(const Options& options) override; + + private: + Context* const context_; + + DISALLOW_COPY_AND_ASSIGN(InProcessNativeRunnerFactory); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_IN_PROCESS_NATIVE_RUNNER_H_ diff --git a/mojo/shell/in_process_native_runner_unittest.cc b/mojo/shell/in_process_native_runner_unittest.cc new file mode 100644 index 0000000..28608c8 --- /dev/null +++ b/mojo/shell/in_process_native_runner_unittest.cc @@ -0,0 +1,23 @@ +// 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 "mojo/shell/in_process_native_runner.h" + +#include "mojo/shell/context.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { + +TEST(InProcessNativeRunnerTest, NotStarted) { + Context context; + base::MessageLoop loop; + context.Init(); + InProcessNativeRunner runner(&context); + context.Shutdown(); + // Shouldn't crash or DCHECK on destruction. +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/init.cc b/mojo/shell/init.cc new file mode 100644 index 0000000..71aec63 --- /dev/null +++ b/mojo/shell/init.cc @@ -0,0 +1,24 @@ +// 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. + +#include "mojo/shell/init.h" + +#include "base/logging.h" + +namespace mojo { +namespace shell { + +void InitializeLogging() { + logging::LoggingSettings settings; + settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + logging::InitLogging(settings); + // To view log output with IDs and timestamps use "adb logcat -v threadtime". + logging::SetLogItems(false, // Process ID + false, // Thread ID + false, // Timestamp + false); // Tick count +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/init.h b/mojo/shell/init.h new file mode 100644 index 0000000..23a5140 --- /dev/null +++ b/mojo/shell/init.h @@ -0,0 +1,18 @@ +// 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. + +#ifndef SHELL_INIT_H_ +#define SHELL_INIT_H_ + +namespace mojo { +namespace shell { + +// Initialization routines shared by desktop and Android main functions. + +void InitializeLogging(); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_INIT_H_ diff --git a/mojo/shell/launcher_main.cc b/mojo/shell/launcher_main.cc new file mode 100644 index 0000000..dfe634c --- /dev/null +++ b/mojo/shell/launcher_main.cc @@ -0,0 +1,116 @@ +// 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 "base/at_exit.h" +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/process_delegate.h" +#include "mojo/edk/embedder/simple_platform_support.h" +#include "mojo/shell/in_process_native_runner.h" +#include "mojo/shell/init.h" +#include "mojo/shell/native_application_support.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +const char kAppArgs[] = "app-args"; +const char kAppPath[] = "app-path"; +const char kAppURL[] = "app-url"; +const char kShellPath[] = "shell-path"; + +class Launcher : public embedder::ProcessDelegate { + public: + explicit Launcher(const base::CommandLine& command_line) + : app_path_(command_line.GetSwitchValuePath(kAppPath)), + app_url_(command_line.GetSwitchValueASCII(kAppURL)), + loop_(base::MessageLoop::TYPE_IO) { + // TODO(vtl): I guess this should be SLAVE, not NONE? + embedder::InitIPCSupport(embedder::ProcessType::NONE, loop_.task_runner(), + this, loop_.task_runner(), + embedder::ScopedPlatformHandle()); + + base::SplitStringAlongWhitespace(command_line.GetSwitchValueASCII(kAppArgs), + &app_args_); + } + + ~Launcher() override { + DCHECK(!application_request_.is_pending()); + + embedder::ShutdownIPCSupportOnIOThread(); + } + + bool Connect() { return false; } + + bool Register() { + return application_request_.is_pending(); + } + + void Run() { + DCHECK(application_request_.is_pending()); + InProcessNativeRunner service_runner(nullptr); + base::RunLoop run_loop; + service_runner.Start(app_path_, NativeApplicationCleanup::DONT_DELETE, + application_request_.Pass(), run_loop.QuitClosure()); + run_loop.Run(); + } + + private: + void OnRegistered(base::RunLoop* run_loop, + InterfaceRequest<Application> application_request) { + application_request_ = application_request.Pass(); + run_loop->Quit(); + } + + // embedder::ProcessDelegate implementation: + void OnShutdownComplete() override { + NOTREACHED(); // Not called since we use ShutdownIPCSupportOnIOThread(). + } + + const base::FilePath app_path_; + const GURL app_url_; + std::vector<std::string> app_args_; + base::MessageLoop loop_; + InterfaceRequest<Application> application_request_; + + DISALLOW_COPY_AND_ASSIGN(Launcher); +}; + +} // namespace shell +} // namespace mojo + +int main(int argc, char** argv) { + base::AtExitManager at_exit; + mojo::embedder::Init( + make_scoped_ptr(new mojo::embedder::SimplePlatformSupport())); + + base::CommandLine::Init(argc, argv); + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + mojo::shell::InitializeLogging(); + + mojo::shell::Launcher launcher(*command_line); + if (!launcher.Connect()) { + LOG(ERROR) << "Failed to connect on socket " + << command_line->GetSwitchValueASCII(mojo::shell::kShellPath); + return 1; + } + + if (!launcher.Register()) { + LOG(ERROR) << "Error registering " + << command_line->GetSwitchValueASCII(mojo::shell::kAppURL); + return 1; + } + + launcher.Run(); + return 0; +} diff --git a/mojo/shell/native_application_support.cc b/mojo/shell/native_application_support.cc new file mode 100644 index 0000000..55ae2cf --- /dev/null +++ b/mojo/shell/native_application_support.cc @@ -0,0 +1,124 @@ +// 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 "mojo/shell/native_application_support.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "mojo/public/platform/native/gles2_impl_chromium_miscellaneous_thunks.h" +#include "mojo/public/platform/native/gles2_impl_chromium_sub_image_thunks.h" +#include "mojo/public/platform/native/gles2_impl_chromium_sync_point_thunks.h" +#include "mojo/public/platform/native/gles2_impl_chromium_texture_mailbox_thunks.h" +#include "mojo/public/platform/native/gles2_impl_occlusion_query_ext_thunks.h" +#include "mojo/public/platform/native/gles2_impl_thunks.h" +#include "mojo/public/platform/native/gles2_thunks.h" +#include "mojo/public/platform/native/system_thunks.h" + +namespace mojo { +namespace shell { + +namespace { + +template <typename Thunks> +bool SetThunks(Thunks (*make_thunks)(), + const char* function_name, + base::NativeLibrary library) { + typedef size_t (*SetThunksFn)(const Thunks* thunks); + SetThunksFn set_thunks = reinterpret_cast<SetThunksFn>( + base::GetFunctionPointerFromNativeLibrary(library, function_name)); + if (!set_thunks) + return false; + Thunks thunks = make_thunks(); + size_t expected_size = set_thunks(&thunks); + if (expected_size > sizeof(Thunks)) { + LOG(ERROR) << "Invalid app library: expected " << function_name + << " to return thunks of size: " << expected_size; + return false; + } + return true; +} + +} // namespace + +base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path, + NativeApplicationCleanup cleanup) { + DVLOG(2) << "Loading Mojo app in process from library: " << app_path.value(); + + base::NativeLibraryLoadError error; + base::NativeLibrary app_library = base::LoadNativeLibrary(app_path, &error); + if (cleanup == NativeApplicationCleanup::DELETE) + DeleteFile(app_path, false); + LOG_IF(ERROR, !app_library) + << "Failed to load app library (error: " << error.ToString() << ")"; + return app_library; +} + +bool RunNativeApplication(base::NativeLibrary app_library, + InterfaceRequest<Application> application_request) { + // Tolerate |app_library| being null, to make life easier for callers. + if (!app_library) + return false; + + if (!SetThunks(&MojoMakeSystemThunks, "MojoSetSystemThunks", app_library)) { + LOG(ERROR) << "MojoSetSystemThunks not found"; + return false; + } + + if (SetThunks(&MojoMakeGLES2ControlThunks, "MojoSetGLES2ControlThunks", + app_library)) { + // If we have the control thunks, we should also have the GLES2 + // implementation thunks. + if (!SetThunks(&MojoMakeGLES2ImplThunks, "MojoSetGLES2ImplThunks", + app_library)) { + LOG(ERROR) + << "MojoSetGLES2ControlThunks found, but not MojoSetGLES2ImplThunks"; + return false; + } + + // If the application is using GLES2 extension points, register those + // thunks. Applications may use or not use any of these, so don't warn if + // they are missing. + SetThunks(MojoMakeGLES2ImplChromiumMiscellaneousThunks, + "MojoSetGLES2ImplChromiumMiscellaneousThunks", app_library); + SetThunks(MojoMakeGLES2ImplChromiumSubImageThunks, + "MojoSetGLES2ImplChromiumSubImageThunks", app_library); + SetThunks(MojoMakeGLES2ImplChromiumTextureMailboxThunks, + "MojoSetGLES2ImplChromiumTextureMailboxThunks", app_library); + SetThunks(MojoMakeGLES2ImplChromiumSyncPointThunks, + "MojoSetGLES2ImplChromiumSyncPointThunks", app_library); + SetThunks(MojoMakeGLES2ImplOcclusionQueryExtThunks, + "MojoSetGLES2ImplOcclusionQueryExtThunks", app_library); + } + // Unlike system thunks, we don't warn on a lack of GLES2 thunks because + // not everything is a visual app. + + // Go shared library support requires us to initialize the runtime before we + // start running any go code. This is a temporary patch. + typedef void (*InitGoRuntimeFn)(); + InitGoRuntimeFn init_go_runtime = reinterpret_cast<InitGoRuntimeFn>( + base::GetFunctionPointerFromNativeLibrary(app_library, "InitGoRuntime")); + if (init_go_runtime) { + DVLOG(2) << "InitGoRuntime: Initializing Go Runtime found in app"; + init_go_runtime(); + } + + typedef MojoResult (*MojoMainFunction)(MojoHandle); + MojoMainFunction main_function = reinterpret_cast<MojoMainFunction>( + base::GetFunctionPointerFromNativeLibrary(app_library, "MojoMain")); + if (!main_function) { + LOG(ERROR) << "MojoMain not found"; + return false; + } + // |MojoMain()| takes ownership of the service handle. + MojoHandle handle = application_request.PassMessagePipe().release().value(); + MojoResult result = main_function(handle); + if (result < MOJO_RESULT_OK) { + LOG(ERROR) << "MojoMain returned error (result: " << result << ")"; + } + return true; +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/native_application_support.h b/mojo/shell/native_application_support.h new file mode 100644 index 0000000..572c089 --- /dev/null +++ b/mojo/shell/native_application_support.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef SHELL_NATIVE_APPLICATION_SUPPORT_H_ +#define SHELL_NATIVE_APPLICATION_SUPPORT_H_ + +#include "base/native_library.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +#if defined(OS_WIN) +#undef DELETE +#endif + +namespace base { +class FilePath; +} + +namespace mojo { + +class Application; + +namespace shell { + +enum class NativeApplicationCleanup { DELETE, DONT_DELETE }; + +// Loads the native Mojo application from the DSO specified by |app_path|. +// Returns the |base::NativeLibrary| for the application on success (or null on +// failure). If |cleanup| is |DELETE|, it will delete |app_path| (regardless of +// sucess or failure). +// +// Note: The caller may choose to eventually unload the returned DSO. If so, +// this should be done only after the thread on which |LoadNativeApplication()| +// and |RunNativeApplication()| were called has terminated, so that any +// thread-local destructors have been executed. +base::NativeLibrary LoadNativeApplication(const base::FilePath& app_path, + NativeApplicationCleanup cleanup); + +// Runs the native Mojo application from the DSO that was loaded using +// |LoadNativeApplication()|; this tolerates |app_library| being null. This +// should be called on the same thread as |LoadNativeApplication()|. Returns +// true if |MojoMain()| was called (even if it returns an error), and false +// otherwise. +// TODO(vtl): Maybe this should also have a |MojoResult| as an out parameter? +bool RunNativeApplication(base::NativeLibrary app_library, + InterfaceRequest<Application> application_request); + +} // namespace shell +} // namespace mojo + +#endif // SHELL_NATIVE_APPLICATION_SUPPORT_H_ diff --git a/mojo/shell/native_runner_unittest.cc b/mojo/shell/native_runner_unittest.cc new file mode 100644 index 0000000..4b8b5a0 --- /dev/null +++ b/mojo/shell/native_runner_unittest.cc @@ -0,0 +1,102 @@ +// 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 "base/files/scoped_temp_dir.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/context.h" +#include "mojo/shell/filename_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace { + +struct TestState { + TestState() + : runner_was_created(false), + runner_was_started(false), + runner_was_destroyed(false) {} + + bool runner_was_created; + bool runner_was_started; + bool runner_was_destroyed; +}; + +class TestNativeRunner : public NativeRunner { + public: + explicit TestNativeRunner(TestState* state) : state_(state) { + state_->runner_was_created = true; + } + ~TestNativeRunner() override { + state_->runner_was_destroyed = true; + base::MessageLoop::current()->Quit(); + } + void Start(const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) override { + state_->runner_was_started = true; + } + + private: + TestState* state_; +}; + +class TestNativeRunnerFactory : public NativeRunnerFactory { + public: + explicit TestNativeRunnerFactory(TestState* state) : state_(state) {} + ~TestNativeRunnerFactory() override {} + scoped_ptr<NativeRunner> Create(const Options& options) override { + return scoped_ptr<NativeRunner>(new TestNativeRunner(state_)); + } + + private: + TestState* state_; +}; + +class NativeApplicationLoaderTest : public testing::Test, + public ApplicationManager::Delegate { + public: + NativeApplicationLoaderTest() : application_manager_(this) {} + ~NativeApplicationLoaderTest() override {} + void SetUp() override { + context_.Init(); + scoped_ptr<NativeRunnerFactory> factory( + new TestNativeRunnerFactory(&state_)); + application_manager_.set_native_runner_factory(factory.Pass()); + application_manager_.set_blocking_pool( + context_.task_runners()->blocking_pool()); + } + void TearDown() override { context_.Shutdown(); } + + protected: + shell::Context context_; + base::MessageLoop loop_; + ApplicationManager application_manager_; + TestState state_; + + private: + // ApplicationManager::Delegate + GURL ResolveURL(const GURL& url) override { return url; } + + GURL ResolveMappings(const GURL& url) override { return url; } +}; + +TEST_F(NativeApplicationLoaderTest, DoesNotExist) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath nonexistent_file(FILE_PATH_LITERAL("nonexistent.txt")); + GURL url(FilePathToFileURL(temp_dir.path().Append(nonexistent_file))); + InterfaceRequest<ServiceProvider> services; + ServiceProviderPtr service_provider; + application_manager_.ConnectToApplication( + url, GURL(), services.Pass(), service_provider.Pass(), base::Closure()); + EXPECT_FALSE(state_.runner_was_created); + EXPECT_FALSE(state_.runner_was_started); + EXPECT_FALSE(state_.runner_was_destroyed); +} + +} // namespace +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/out_of_process_native_runner.cc b/mojo/shell/out_of_process_native_runner.cc new file mode 100644 index 0000000..4c87271 --- /dev/null +++ b/mojo/shell/out_of_process_native_runner.cc @@ -0,0 +1,72 @@ +// 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 "mojo/shell/out_of_process_native_runner.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "mojo/shell/app_child_process.mojom.h" +#include "mojo/shell/app_child_process_host.h" +#include "mojo/shell/in_process_native_runner.h" + +namespace mojo { +namespace shell { + +OutOfProcessNativeRunner::OutOfProcessNativeRunner(Context* context) + : context_(context) { +} + +OutOfProcessNativeRunner::~OutOfProcessNativeRunner() { + if (app_child_process_host_) { + // TODO(vtl): Race condition: If |AppChildProcessHost::DidStart()| hasn't + // been called yet, we shouldn't call |Join()| here. (Until |DidStart()|, we + // may not have a child process to wait on.) Probably we should fix + // |Join()|. + app_child_process_host_->Join(); + } +} + +void OutOfProcessNativeRunner::Start( + const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) { + app_path_ = app_path; + + DCHECK(app_completed_callback_.is_null()); + app_completed_callback_ = app_completed_callback; + + app_child_process_host_.reset(new AppChildProcessHost(context_)); + app_child_process_host_->Start(); + + // TODO(vtl): |app_path.AsUTF8Unsafe()| is unsafe. + app_child_process_host_->StartApp( + app_path.AsUTF8Unsafe(), cleanup == NativeApplicationCleanup::DELETE, + application_request.Pass(), + base::Bind(&OutOfProcessNativeRunner::AppCompleted, + base::Unretained(this))); +} + +void OutOfProcessNativeRunner::AppCompleted(int32_t result) { + DVLOG(2) << "OutOfProcessNativeRunner::AppCompleted(" << result << ")"; + + app_child_process_host_.reset(); + // This object may be deleted by this callback. + base::Closure app_completed_callback = app_completed_callback_; + app_completed_callback_.Reset(); + app_completed_callback.Run(); +} + +scoped_ptr<NativeRunner> OutOfProcessNativeRunnerFactory::Create( + const Options& options) { + if (options.force_in_process) + return make_scoped_ptr(new InProcessNativeRunner(context_)); + + return make_scoped_ptr(new OutOfProcessNativeRunner(context_)); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/out_of_process_native_runner.h b/mojo/shell/out_of_process_native_runner.h new file mode 100644 index 0000000..edbaaf0 --- /dev/null +++ b/mojo/shell/out_of_process_native_runner.h @@ -0,0 +1,65 @@ +// 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. + +#ifndef SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_ +#define SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/shell/application_manager/native_runner.h" + +namespace mojo { +namespace shell { + +class AppChildProcessHost; +class Context; + +// An implementation of |NativeRunner| that loads/runs the given app (from the +// file system) in a separate process (of its own). +class OutOfProcessNativeRunner : public NativeRunner { + public: + explicit OutOfProcessNativeRunner(Context* context); + ~OutOfProcessNativeRunner() override; + + // |NativeRunner| method: + void Start(const base::FilePath& app_path, + NativeApplicationCleanup cleanup, + InterfaceRequest<Application> application_request, + const base::Closure& app_completed_callback) override; + + private: + // |AppChildController::StartApp()| callback: + void AppCompleted(int32_t result); + + Context* const context_; + + base::FilePath app_path_; + base::Closure app_completed_callback_; + + scoped_ptr<AppChildProcessHost> app_child_process_host_; + + DISALLOW_COPY_AND_ASSIGN(OutOfProcessNativeRunner); +}; + +class OutOfProcessNativeRunnerFactory : public NativeRunnerFactory { + public: + explicit OutOfProcessNativeRunnerFactory(Context* context) + : context_(context) {} + ~OutOfProcessNativeRunnerFactory() override {} + + scoped_ptr<NativeRunner> Create(const Options& options) override; + + private: + Context* const context_; + + DISALLOW_COPY_AND_ASSIGN(OutOfProcessNativeRunnerFactory); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_OUT_OF_PROCESS_NATIVE_RUNNER_H_ diff --git a/mojo/shell/shell_apptest.cc b/mojo/shell/shell_apptest.cc new file mode 100644 index 0000000..1acdf7d --- /dev/null +++ b/mojo/shell/shell_apptest.cc @@ -0,0 +1,197 @@ +// Copyright 2015 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 "base/base_paths.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "mojo/public/cpp/system/macros.h" +#include "mojo/services/http_server/public/cpp/http_server_util.h" +#include "mojo/services/http_server/public/interfaces/http_server.mojom.h" +#include "mojo/services/http_server/public/interfaces/http_server_factory.mojom.h" +#include "mojo/services/network/public/interfaces/net_address.mojom.h" +#include "mojo/shell/kPingable.h" +#include "mojo/shell/test/pingable.mojom.h" + +namespace mojo { +namespace { + +std::string GetURL(uint16_t port, const std::string& path) { + return base::StringPrintf("http://127.0.0.1:%u/%s", + static_cast<unsigned>(port), path.c_str()); +} + +class GetHandler : public http_server::HttpHandler { + public: + GetHandler(InterfaceRequest<http_server::HttpHandler> request, uint16_t port) + : binding_(this, request.Pass()), port_(port) { + } + ~GetHandler() override {} + + private: + // http_server::HttpHandler: + void HandleRequest( + http_server::HttpRequestPtr request, + const Callback<void(http_server::HttpResponsePtr)>& callback) override { + http_server::HttpResponsePtr response; + if (StartsWithASCII(request->relative_url, "/app", true)) { + response = http_server::CreateHttpResponse( + 200, std::string(kPingable.data, kPingable.size)); + response->content_type = "application/octet-stream"; + } else if (request->relative_url == "/redirect") { + response = http_server::HttpResponse::New(); + response->status_code = 302; + response->custom_headers.insert("Location", GetURL(port_, "app")); + } else { + NOTREACHED(); + } + + callback.Run(response.Pass()); + } + + Binding<http_server::HttpHandler> binding_; + uint16_t port_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(GetHandler); +}; + +typedef test::ApplicationTestBase ShellAppTest; + +class ShellHTTPAppTest : public test::ApplicationTestBase { + public: + ShellHTTPAppTest() : ApplicationTestBase() {} + ~ShellHTTPAppTest() override {} + + protected: + // ApplicationTestBase: + void SetUp() override { + ApplicationTestBase::SetUp(); + + application_impl()->ConnectToService("mojo:http_server", + &http_server_factory_); + + NetAddressPtr local_address(NetAddress::New()); + local_address->family = NET_ADDRESS_FAMILY_IPV4; + local_address->ipv4 = NetAddressIPv4::New(); + local_address->ipv4->addr.resize(4); + local_address->ipv4->addr[0] = 127; + local_address->ipv4->addr[1] = 0; + local_address->ipv4->addr[2] = 0; + local_address->ipv4->addr[3] = 1; + local_address->ipv4->port = 0; + http_server_factory_->CreateHttpServer(GetProxy(&http_server_), + local_address.Pass()); + + http_server_->GetPort([this](uint16_t p) { port_ = p; }); + EXPECT_TRUE(http_server_.WaitForIncomingMethodCall()); + + InterfacePtr<http_server::HttpHandler> http_handler; + handler_.reset(new GetHandler(GetProxy(&http_handler).Pass(), port_)); + http_server_->SetHandler(".*", http_handler.Pass(), + [](bool result) { EXPECT_TRUE(result); }); + EXPECT_TRUE(http_server_.WaitForIncomingMethodCall()); + } + + std::string GetURL(const std::string& path) { + return ::mojo::GetURL(port_, path); + } + + http_server::HttpServerFactoryPtr http_server_factory_; + http_server::HttpServerPtr http_server_; + scoped_ptr<GetHandler> handler_; + uint16_t port_; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(ShellHTTPAppTest); +}; + +// Test that we can load apps over http. +TEST_F(ShellHTTPAppTest, Http) { + InterfacePtr<Pingable> pingable; + application_impl()->ConnectToService(GetURL("app"), &pingable); + pingable->Ping("hello", + [this](const String& app_url, const String& connection_url, + const String& message) { + EXPECT_EQ(GetURL("app"), app_url); + EXPECT_EQ(GetURL("app"), connection_url); + EXPECT_EQ("hello", message); + base::MessageLoop::current()->Quit(); + }); + base::RunLoop().Run(); +} + +// Test that redirects work. +// TODO(aa): Test that apps receive the correct URL parameters. +TEST_F(ShellHTTPAppTest, Redirect) { + InterfacePtr<Pingable> pingable; + application_impl()->ConnectToService(GetURL("redirect"), &pingable); + pingable->Ping("hello", + [this](const String& app_url, const String& connection_url, + const String& message) { + EXPECT_EQ(GetURL("app"), app_url); + EXPECT_EQ(GetURL("app"), connection_url); + EXPECT_EQ("hello", message); + base::MessageLoop::current()->Quit(); + }); + base::RunLoop().Run(); +} + +// Test that querystring is not considered when resolving http applications. +// TODO(aa|qsr): Fix this test on Linux ASAN http://crbug.com/463662 +#if defined(ADDRESS_SANITIZER) +#define MAYBE_QueryHandling DISABLED_QueryHandling +#else +#define MAYBE_QueryHandling QueryHandling +#endif // ADDRESS_SANITIZER +TEST_F(ShellHTTPAppTest, MAYBE_QueryHandling) { + InterfacePtr<Pingable> pingable1; + InterfacePtr<Pingable> pingable2; + application_impl()->ConnectToService(GetURL("app?foo"), &pingable1); + application_impl()->ConnectToService(GetURL("app?bar"), &pingable2); + + int num_responses = 0; + auto callback = [this, &num_responses](const String& app_url, + const String& connection_url, + const String& message) { + EXPECT_EQ(GetURL("app"), app_url); + EXPECT_EQ("hello", message); + ++num_responses; + if (num_responses == 1) { + EXPECT_EQ(GetURL("app?foo"), connection_url); + } else if (num_responses == 2) { + EXPECT_EQ(GetURL("app?bar"), connection_url); + base::MessageLoop::current()->Quit(); + } else { + CHECK(false); + } + }; + pingable1->Ping("hello", callback); + pingable2->Ping("hello", callback); + base::RunLoop().Run(); +} + +// mojo: URLs can have querystrings too +TEST_F(ShellAppTest, MojoURLQueryHandling) { + InterfacePtr<Pingable> pingable; + application_impl()->ConnectToService("mojo:pingable_app?foo", &pingable); + auto callback = [this](const String& app_url, const String& connection_url, + const String& message) { + EXPECT_TRUE(EndsWith(app_url, "/pingable_app.mojo", true)); + EXPECT_EQ(app_url.To<std::string>() + "?foo", connection_url); + EXPECT_EQ("hello", message); + base::MessageLoop::current()->Quit(); + }; + pingable->Ping("hello", callback); + base::RunLoop().Run(); +} + +} // namespace +} // namespace mojo diff --git a/mojo/shell/shell_test_base.cc b/mojo/shell/shell_test_base.cc new file mode 100644 index 0000000..da9a7d9 --- /dev/null +++ b/mojo/shell/shell_test_base.cc @@ -0,0 +1,73 @@ +// 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 "mojo/shell/shell_test_base.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "build/build_config.h" +#include "mojo/shell/filename_util.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { +namespace test { + +namespace { + +void QuitIfRunning() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) { + base::MessageLoop::current()->QuitWhenIdle(); + } +} + +} // namespace + +ShellTestBase::ShellTestBase() { +} + +ShellTestBase::~ShellTestBase() { +} + +void ShellTestBase::SetUp() { + CHECK(shell_context_.Init()); + SetUpTestApplications(); +} + +void ShellTestBase::TearDown() { + shell_context_.Shutdown(); +} + +ScopedMessagePipeHandle ShellTestBase::ConnectToService( + const GURL& application_url, + const std::string& service_name) { + ServiceProviderPtr services; + shell_context_.application_manager()->ConnectToApplication( + application_url, GURL(), GetProxy(&services), nullptr, + base::Bind(&QuitIfRunning)); + MessagePipe pipe; + services->ConnectToService(service_name, pipe.handle1.Pass()); + return pipe.handle0.Pass(); +} + +#if !defined(OS_ANDROID) +void ShellTestBase::SetUpTestApplications() { + // Set the URLResolver origin to be the same as the base file path for + // local files. This is primarily for test convenience, so that references + // to unknown mojo: urls that do not have specific local file or custom + // mappings registered on the URL resolver are treated as shared libraries. + base::FilePath service_dir; + CHECK(PathService::Get(base::DIR_MODULE, &service_dir)); + shell_context_.url_resolver()->SetMojoBaseURL(FilePathToFileURL(service_dir)); +} +#endif + +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/shell_test_base.h b/mojo/shell/shell_test_base.h new file mode 100644 index 0000000..e119a0a --- /dev/null +++ b/mojo/shell/shell_test_base.h @@ -0,0 +1,59 @@ +// 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. + +#ifndef SHELL_SHELL_TEST_BASE_H_ +#define SHELL_SHELL_TEST_BASE_H_ + +#include <string> + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/shell/context.h" +#include "testing/gtest/include/gtest/gtest.h" + +class GURL; + +namespace mojo { +namespace shell { +namespace test { + +class ShellTestBase : public testing::Test { + public: + ShellTestBase(); + ~ShellTestBase() override; + + void SetUp() override; + void TearDown() override; + + // |application_url| should typically be a mojo: URL (the origin will be set + // to an "appropriate" file: URL). + // TODO(tim): Should the test base be a ServiceProvider? + ScopedMessagePipeHandle ConnectToService(const GURL& application_url, + const std::string& service_name); + + template <typename Interface> + void ConnectToService(const GURL& application_url, + InterfacePtr<Interface>* ptr) { + ptr->Bind(ConnectToService(application_url, Interface::Name_).Pass()); + } + + base::MessageLoop* message_loop() { return &message_loop_; } + Context* shell_context() { return &shell_context_; } + + private: + // Set up the test applications so that mojo: URL resolves to those. + void SetUpTestApplications(); + + Context shell_context_; + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(ShellTestBase); +}; + +} // namespace test +} // namespace shell +} // namespace mojo + +#endif // SHELL_SHELL_TEST_BASE_H_ diff --git a/mojo/shell/shell_test_base_android.cc b/mojo/shell/shell_test_base_android.cc new file mode 100644 index 0000000..4b3b736 --- /dev/null +++ b/mojo/shell/shell_test_base_android.cc @@ -0,0 +1,47 @@ +// 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 "mojo/shell/shell_test_base.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "jni/ShellTestBase_jni.h" +#include "mojo/shell/filename_util.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { +namespace test { + +namespace { + +JNIEnv* InitEnv() { + JNIEnv* env = base::android::AttachCurrentThread(); + static bool initialized = false; + if (!initialized) { + RegisterNativesImpl(env); + initialized = true; + } + return env; +} + +} // namespace + +void ShellTestBase::SetUpTestApplications() { + // Extract mojo applications, and set the resolve base URL to the directory + // containing those. + JNIEnv* env = InitEnv(); + base::android::ScopedJavaLocalRef<jstring> service_dir( + Java_ShellTestBase_extractMojoApplications( + env, base::android::GetApplicationContext())); + shell_context_.url_resolver()->SetMojoBaseURL( + FilePathToFileURL(base::FilePath( + base::android::ConvertJavaStringToUTF8(env, service_dir.obj())))); +} + +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/shell_test_base_unittest.cc b/mojo/shell/shell_test_base_unittest.cc new file mode 100644 index 0000000..4e94511 --- /dev/null +++ b/mojo/shell/shell_test_base_unittest.cc @@ -0,0 +1,309 @@ +// 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 "mojo/shell/shell_test_base.h" + +#include "base/bind.h" +#include "base/i18n/time_formatting.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/services/test_service/test_request_tracker.mojom.h" +#include "mojo/services/test_service/test_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using mojo::test::ServiceReport; +using mojo::test::ServiceReportPtr; +using mojo::test::TestService; +using mojo::test::TestTimeService; +using mojo::test::TestServicePtr; +using mojo::test::TestTimeServicePtr; +using mojo::test::TestTrackedRequestService; +using mojo::test::TestTrackedRequestServicePtr; + +namespace mojo { +namespace shell { +namespace test { +namespace { + +void GetReportCallback(base::MessageLoop* loop, + std::vector<ServiceReport>* reports_out, + Array<ServiceReportPtr> report) { + for (size_t i = 0; i < report.size(); i++) + reports_out->push_back(*report[i]); + loop->QuitWhenIdle(); +} + +class ShellTestBaseTest : public ShellTestBase { + public: + // Convenience helpers for use as callbacks in tests. + template <typename T> + base::Callback<void()> SetAndQuit(T* val, T result) { + return base::Bind(&ShellTestBaseTest::SetAndQuitImpl<T>, + base::Unretained(this), val, result); + } + template <typename T> + base::Callback<void(T result)> SetAndQuit(T* val) { + return base::Bind(&ShellTestBaseTest::SetAndQuitImpl<T>, + base::Unretained(this), val); + } + static GURL test_app_url() { return GURL("mojo:test_app"); } + + void GetReport(std::vector<ServiceReport>* report) { + ConnectToService(GURL("mojo:test_request_tracker_app"), &request_tracking_); + request_tracking_->GetReport(base::Bind(&GetReportCallback, + base::Unretained(message_loop()), + base::Unretained(report))); + message_loop()->Run(); + } + + private: + template <typename T> + void SetAndQuitImpl(T* val, T result) { + *val = result; + message_loop()->QuitWhenIdle(); + } + TestTrackedRequestServicePtr request_tracking_; +}; + +class QuitMessageLoopErrorHandler : public ErrorHandler { + public: + QuitMessageLoopErrorHandler() {} + ~QuitMessageLoopErrorHandler() override {} + + // |ErrorHandler| implementation: + void OnConnectionError() override { + base::MessageLoop::current()->QuitWhenIdle(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler); +}; + +// Tests that we can connect to a single service within a single app. +TEST_F(ShellTestBaseTest, ConnectBasic) { + InterfacePtr<TestService> service; + ConnectToService(test_app_url(), &service); + + bool was_run = false; + service->Ping(SetAndQuit<bool>(&was_run, true)); + message_loop()->Run(); + EXPECT_TRUE(was_run); + EXPECT_FALSE(service.encountered_error()); + + service.reset(); + + // This will run until the test app has actually quit (which it will, + // since we killed the only connection to it). + message_loop()->Run(); +} + +// Tests that trying to connect to a service fails properly if the service +// doesn't exist. Implicit in this test is verification that the shell +// terminates if no services are running. +TEST_F(ShellTestBaseTest, ConnectInvalidService) { + InterfacePtr<TestService> test_service; + ConnectToService(GURL("mojo:non_existent_service"), &test_service); + + bool was_run = false; + test_service->Ping(SetAndQuit<bool>(&was_run, true)); + + // This will quit because there's nothing running. + message_loop()->Run(); + EXPECT_FALSE(was_run); + + // It may have quit before an error was processed. + if (!test_service.encountered_error()) { + QuitMessageLoopErrorHandler quitter; + test_service.set_error_handler(&quitter); + message_loop()->Run(); + EXPECT_TRUE(test_service.encountered_error()); + } + + test_service.reset(); +} + +// Tests that we can connect to a single service within a single app using +// a network based loader instead of local files. +// TODO(tim): Disabled because network service leaks NSS at exit, meaning +// subsequent tests can't init properly. +TEST_F(ShellTestBaseTest, DISABLED_ConnectBasicNetwork) { + InterfacePtr<TestService> service; + ConnectToService(test_app_url(), &service); + + bool was_run = false; + service->Ping(SetAndQuit<bool>(&was_run, true)); + message_loop()->Run(); + EXPECT_TRUE(was_run); + EXPECT_FALSE(service.encountered_error()); + + // Note that use of the network service is implicit in this test. + // Since TestService is not the only service in use, the shell won't auto + // magically exit when TestService is destroyed (unlike ConnectBasic). + // Tearing down the shell context will kill connections. The shell loop will + // exit as soon as no more apps are connected. + // TODO(tim): crbug.com/392685. Calling this explicitly shouldn't be + // necessary once the shell terminates if the primordial app exits, which + // we could enforce here by resetting |service|. + shell_context()->application_manager()->TerminateShellConnections(); + message_loop()->Run(); // Waits for all connections to die. +} + +// Tests that trying to connect to a service over network fails preoprly +// if the service doesn't exist. +// TODO(tim): Disabled because network service leaks NSS at exit, meaning +// subsequent tests can't init properly. +TEST_F(ShellTestBaseTest, DISABLED_ConnectInvalidServiceNetwork) { + InterfacePtr<TestService> test_service; + ConnectToService(GURL("http://example.com/non_existent_service"), + &test_service); + QuitMessageLoopErrorHandler quitter; + test_service.set_error_handler(&quitter); + bool was_run = false; + test_service->Ping(SetAndQuit<bool>(&was_run, true)); + message_loop()->Run(); + EXPECT_TRUE(test_service.encountered_error()); + + // TODO(tim): crbug.com/392685. Calling this explicitly shouldn't be + // necessary once the shell terminates if the primordial app exits, which + // we could enforce here by resetting |service|. + shell_context()->application_manager()->TerminateShellConnections(); + message_loop()->Run(); // Waits for all connections to die. +} + +// Similar to ConnectBasic, but causes the app to instantiate multiple +// service implementation objects and verifies the shell can reach both. +TEST_F(ShellTestBaseTest, ConnectMultipleInstancesPerApp) { + { + TestServicePtr service1, service2; + ConnectToService(test_app_url(), &service1); + ConnectToService(test_app_url(), &service2); + + bool was_run1 = false; + bool was_run2 = false; + service1->Ping(SetAndQuit<bool>(&was_run1, true)); + message_loop()->Run(); + service2->Ping(SetAndQuit<bool>(&was_run2, true)); + message_loop()->Run(); + EXPECT_TRUE(was_run1); + EXPECT_TRUE(was_run2); + EXPECT_FALSE(service1.encountered_error()); + EXPECT_FALSE(service2.encountered_error()); + } + message_loop()->Run(); +} + +// Tests that service A and service B, both in App 1, can talk to each other +// and parameters are passed around properly. +TEST_F(ShellTestBaseTest, ConnectDifferentServicesInSingleApp) { + // Have a TestService GetPartyTime on a TestTimeService in the same app. + int64 time_message; + TestServicePtr service; + ConnectToService(test_app_url(), &service); + service->ConnectToAppAndGetTime(test_app_url().spec(), + SetAndQuit<int64>(&time_message)); + message_loop()->Run(); + + // Verify by hitting the TimeService directly. + TestTimeServicePtr time_service; + ConnectToService(test_app_url(), &time_service); + int64 party_time; + time_service->GetPartyTime(SetAndQuit<int64>(&party_time)); + message_loop()->Run(); + + EXPECT_EQ(time_message, party_time); +} + +// Tests that a service A in App 1 can talk to service B in App 2 and +// parameters are passed around properly. +TEST_F(ShellTestBaseTest, ConnectDifferentServicesInDifferentApps) { + int64 time_message; + TestServicePtr service; + ConnectToService(test_app_url(), &service); + service->ConnectToAppAndGetTime("mojo:test_request_tracker_app", + SetAndQuit<int64>(&time_message)); + message_loop()->Run(); + + // Verify by hitting the TimeService in the request tracker app directly. + TestTimeServicePtr time_service; + ConnectToService(GURL("mojo:test_request_tracker_app"), &time_service); + int64 party_time; + time_service->GetPartyTime(SetAndQuit<int64>(&party_time)); + message_loop()->Run(); + + EXPECT_EQ(time_message, party_time); +} + +// Tests that service A in App 1 can be a client of service B in App 2. +TEST_F(ShellTestBaseTest, ConnectServiceAsClientOfSeparateApp) { + TestServicePtr service; + ConnectToService(test_app_url(), &service); + service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure()); + service->Ping(Callback<void()>()); + message_loop()->Run(); + + for (int i = 0; i < 8; i++) + service->Ping(Callback<void()>()); + service->Ping(message_loop()->QuitWhenIdleClosure()); + message_loop()->Run(); + + // If everything worked properly, the tracking service should report + // 10 pings to TestService. + std::vector<ServiceReport> reports; + GetReport(&reports); + ASSERT_EQ(1U, reports.size()); + EXPECT_EQ(TestService::Name_, reports[0].service_name); + EXPECT_EQ(10U, reports[0].total_requests); +} + +// Connect several services together and use the tracking service to verify +// communication. +TEST_F(ShellTestBaseTest, ConnectManyClientsAndServices) { + TestServicePtr service; + TestTimeServicePtr time_service; + + // Make a request to the TestService and have it contact TimeService in the + // tracking app. Do all this with tracking enabled, meaning both services + // are connected as clients of the TrackedRequestService. + ConnectToService(test_app_url(), &service); + service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure()); + message_loop()->Run(); + for (int i = 0; i < 5; i++) + service->Ping(Callback<void()>()); + int64 time_result; + service->ConnectToAppAndGetTime("mojo:test_request_tracker_app", + SetAndQuit<int64>(&time_result)); + message_loop()->Run(); + + // Also make a few requests to the TimeService in the test_app. + ConnectToService(test_app_url(), &time_service); + time_service->StartTrackingRequests(message_loop()->QuitWhenIdleClosure()); + time_service->GetPartyTime(Callback<void(uint64_t)>()); + message_loop()->Run(); + for (int i = 0; i < 18; i++) + time_service->GetPartyTime(Callback<void(uint64_t)>()); + // Flush the tasks with one more to quit. + int64 party_time = 0; + time_service->GetPartyTime(SetAndQuit<int64>(&party_time)); + message_loop()->Run(); + + std::vector<ServiceReport> reports; + GetReport(&reports); + ASSERT_EQ(3U, reports.size()); + EXPECT_EQ(TestService::Name_, reports[0].service_name); + EXPECT_EQ(6U, reports[0].total_requests); + EXPECT_EQ(TestTimeService::Name_, reports[1].service_name); + EXPECT_EQ(1U, reports[1].total_requests); + EXPECT_EQ(TestTimeService::Name_, reports[2].service_name); + EXPECT_EQ(20U, reports[2].total_requests); +} + +} // namespace +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/shell_test_helper.cc b/mojo/shell/shell_test_helper.cc new file mode 100644 index 0000000..0a467303 --- /dev/null +++ b/mojo/shell/shell_test_helper.cc @@ -0,0 +1,46 @@ +// 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 "mojo/shell/shell_test_helper.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "mojo/shell/filename_util.h" +#include "mojo/shell/init.h" +#include "mojo/shell/url_resolver.h" + +namespace mojo { +namespace shell { + +ShellTestHelper::ShellTestHelper() { + base::CommandLine::Init(0, nullptr); + InitializeLogging(); +} + +ShellTestHelper::~ShellTestHelper() { +} + +void ShellTestHelper::Init() { + context_.Init(); + test_api_.reset( + new ApplicationManager::TestAPI(context_.application_manager())); + base::FilePath service_dir; + CHECK(PathService::Get(base::DIR_MODULE, &service_dir)); + context_.url_resolver()->SetMojoBaseURL(FilePathToFileURL(service_dir)); +} + +void ShellTestHelper::SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, + const GURL& url) { + context_.application_manager()->SetLoaderForURL(loader.Pass(), url); +} + +void ShellTestHelper::AddURLMapping(const GURL& url, const GURL& resolved_url) { + context_.url_resolver()->AddURLMapping(url, resolved_url); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/shell_test_helper.h b/mojo/shell/shell_test_helper.h new file mode 100644 index 0000000..185d367 --- /dev/null +++ b/mojo/shell/shell_test_helper.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef SHELL_SHELL_TEST_HELPER_H_ +#define SHELL_SHELL_TEST_HELPER_H_ + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "mojo/shell/application_manager/application_loader.h" +#include "mojo/shell/context.h" + +class GURL; + +namespace mojo { +namespace shell { + +// ShellTestHelper is useful for tests to establish a connection to the +// ApplicationManager. Invoke Init() to establish the connection. Once done, +// application_manager() returns the ApplicationManager. +class ShellTestHelper { + public: + ShellTestHelper(); + ~ShellTestHelper(); + + void Init(); + + ApplicationManager* application_manager() { + return context_.application_manager(); + } + + // Sets a ApplicationLoader for the specified URL. |loader| is ultimately used + // on + // the thread this class spawns. + void SetLoaderForURL(scoped_ptr<ApplicationLoader> loader, const GURL& url); + + // Adds a mapping that is used when resolving mojo urls. See URLResolver + // for details. + void AddURLMapping(const GURL& url, const GURL& resolved_url); + + private: + Context context_; + base::MessageLoop shell_loop_; + scoped_ptr<ApplicationManager::TestAPI> test_api_; + DISALLOW_COPY_AND_ASSIGN(ShellTestHelper); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_SHELL_TEST_HELPER_H_ diff --git a/mojo/shell/shell_test_main.cc b/mojo/shell/shell_test_main.cc new file mode 100644 index 0000000..179eb41 --- /dev/null +++ b/mojo/shell/shell_test_main.cc @@ -0,0 +1,33 @@ +// 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 "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "mojo/shell/child_process.h" +#include "mojo/shell/switches.h" +#include "testing/gtest/include/gtest/gtest.h" + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + const base::CommandLine& command_line = + *base::CommandLine::ForCurrentProcess(); + + if (command_line.HasSwitch(switches::kChildProcess)) { + base::AtExitManager at_exit; + scoped_ptr<mojo::shell::ChildProcess> child_process = + mojo::shell::ChildProcess::Create(command_line); + CHECK(child_process); + child_process->Main(); + return 0; + } + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, argv, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/mojo/shell/switches.cc b/mojo/shell/switches.cc new file mode 100644 index 0000000..6aa5cda --- /dev/null +++ b/mojo/shell/switches.cc @@ -0,0 +1,106 @@ +// 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. + +#include "mojo/shell/switches.h" + +#include "base/basictypes.h" + +namespace switches { + +namespace { +// This controls logging verbosity. It's not strictly a switch for mojo_shell, +// and isn't included in the public switches, but is included here so that it +// doesn't trigger an error at startup. +const char kV[] = "v"; + +} // namespace + +// Specify configuration arguments for a Mojo application URL. For example: +// --args-for='mojo:wget http://www.google.com' +const char kArgsFor[] = "args-for"; + +// Used internally by the main process to indicate that a new process should be +// a child process. Not for user use. +const char kChildProcess[] = "child-process"; + +// Comma separated list like: +// text/html,mojo:html_viewer,application/bravo,https://abarth.com/bravo +const char kContentHandlers[] = "content-handlers"; + +// Force dynamically loaded apps / services to be loaded irrespective of cache +// instructions. +const char kDisableCache[] = "disable-cache"; + +// If set apps downloaded are not deleted. +const char kDontDeleteOnDownload[] = "dont-delete-on-download"; + +// Allow externally-running applications to discover, connect to, and register +// themselves with the shell. +// TODO(cmasone): Work in progress. Once we're sure this works, remove. +const char kEnableExternalApplications[] = "enable-external-applications"; + +// Load apps in separate processes. +// TODO(vtl): Work in progress; doesn't work. Flip this to "disable" (or maybe +// change it to "single-process") when it works. +const char kEnableMultiprocess[] = "enable-multiprocess"; + +// In multiprocess mode, force these apps to be loaded in the main process. +// Comma-separate list of URLs. Example: +// --force-in-process=mojo:native_viewport_service,mojo:network_service +const char kForceInProcess[] = "force-in-process"; + +// Print the usage message and exit. +const char kHelp[] = "help"; + +// Specify origin to map to base url. See url_resolver.cc for details. +// Can be used multiple times. +const char kMapOrigin[] = "map-origin"; + +// Map mojo: URLs to a shared library of similar name at this origin. See +// url_resolver.cc for details. +const char kOrigin[] = "origin"; + +// If set apps downloaded are saved in with a predictable filename, to help +// remote debugging: when gdb is used through gdbserver, it needs to be able to +// find locally any loaded library. For this, gdb use the filename of the +// library. When using this flag, the application are named with the sha256 of +// their content. +const char kPredictableAppFilenames[] = "predictable-app-filenames"; + +// Starts tracing when the shell starts up, saving a trace file on disk after 5 +// seconds or when the shell exits. +const char kTraceStartup[] = "trace-startup"; + +// Specifies a set of mappings to apply when resolving urls. The value is a set +// of ',' separated mappings, where each mapping consists of a pair of urls +// giving the to/from url to map. For example, 'a=b,c=d' contains two mappings, +// the first maps 'a' to 'b' and the second 'c' to 'd'. +const char kURLMappings[] = "url-mappings"; + +// Switches valid for the main process (i.e., that the user may pass in). +const char* kSwitchArray[] = {kV, + kArgsFor, + // |kChildProcess| not for user use. + kContentHandlers, + kDisableCache, + kDontDeleteOnDownload, + kEnableExternalApplications, + kEnableMultiprocess, + kForceInProcess, + kHelp, + kMapOrigin, + kOrigin, + kPredictableAppFilenames, + kTraceStartup, + kURLMappings}; + +const std::set<std::string> GetAllSwitches() { + std::set<std::string> switch_set; + + for (size_t i = 0; i < arraysize(kSwitchArray); ++i) + switch_set.insert(kSwitchArray[i]); + return switch_set; +} + +} // namespace switches diff --git a/mojo/shell/switches.h b/mojo/shell/switches.h new file mode 100644 index 0000000..5c8056f --- /dev/null +++ b/mojo/shell/switches.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef SHELL_SWITCHES_H_ +#define SHELL_SWITCHES_H_ + +#include <set> +#include <string> + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file and, as needed, +// in mojo_main's Usage() function. +extern const char kArgsFor[]; +extern const char kChildProcess[]; +extern const char kContentHandlers[]; +extern const char kDisableCache[]; +extern const char kDontDeleteOnDownload[]; +extern const char kEnableExternalApplications[]; +extern const char kEnableMultiprocess[]; +extern const char kForceInProcess[]; +extern const char kHelp[]; +extern const char kMapOrigin[]; +extern const char kOrigin[]; +extern const char kPredictableAppFilenames[]; +extern const char kTraceStartup[]; +extern const char kURLMappings[]; + +extern const std::set<std::string> GetAllSwitches(); + +} // namespace switches + +#endif // SHELL_SWITCHES_H_ diff --git a/mojo/shell/task_runners.cc b/mojo/shell/task_runners.cc new file mode 100644 index 0000000..cb157ee --- /dev/null +++ b/mojo/shell/task_runners.cc @@ -0,0 +1,39 @@ +// 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. + +#include "mojo/shell/task_runners.h" + +#include "base/threading/sequenced_worker_pool.h" + +namespace mojo { +namespace shell { + +namespace { + +const size_t kMaxBlockingPoolThreads = 3; + +scoped_ptr<base::Thread> CreateIOThread(const char* name) { + scoped_ptr<base::Thread> thread(new base::Thread(name)); + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_IO; + thread->StartWithOptions(options); + return thread.Pass(); +} + +} // namespace + +TaskRunners::TaskRunners( + const scoped_refptr<base::SingleThreadTaskRunner>& shell_runner) + : shell_runner_(shell_runner), + io_thread_(CreateIOThread("io_thread")), + blocking_pool_(new base::SequencedWorkerPool(kMaxBlockingPoolThreads, + "blocking_pool")) { +} + +TaskRunners::~TaskRunners() { + blocking_pool_->Shutdown(); +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/task_runners.h b/mojo/shell/task_runners.h new file mode 100644 index 0000000..891d3e1b --- /dev/null +++ b/mojo/shell/task_runners.h @@ -0,0 +1,53 @@ +// 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. + +#ifndef SHELL_TASK_RUNNERS_H_ +#define SHELL_TASK_RUNNERS_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/thread.h" + +namespace base { +class SequencedWorkerPool; +} + +namespace mojo { +namespace shell { + +// A context object that contains the common task runners for the shell's main +// process. +class TaskRunners { + public: + explicit TaskRunners( + const scoped_refptr<base::SingleThreadTaskRunner>& shell_runner); + ~TaskRunners(); + + base::SingleThreadTaskRunner* shell_runner() const { + return shell_runner_.get(); + } + + base::SingleThreadTaskRunner* io_runner() const { + return io_thread_->message_loop_proxy().get(); + } + + base::SequencedWorkerPool* blocking_pool() const { + return blocking_pool_.get(); + } + + private: + scoped_refptr<base::SingleThreadTaskRunner> shell_runner_; + scoped_ptr<base::Thread> io_thread_; + + scoped_refptr<base::SequencedWorkerPool> blocking_pool_; + + DISALLOW_COPY_AND_ASSIGN(TaskRunners); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_TASK_RUNNERS_H_ diff --git a/mojo/shell/test/BUILD.gn b/mojo/shell/test/BUILD.gn new file mode 100644 index 0000000..7ca52ff --- /dev/null +++ b/mojo/shell/test/BUILD.gn @@ -0,0 +1,28 @@ +import("//third_party/mojo/src/mojo/public/mojo.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +mojom("bindings") { + sources = [ + "pingable.mojom", + ] +} + +mojo_native_application("pingable_app") { + output_name = "pingable_app" + + testonly = true + + sources = [ + "pingable_app.cc", + ] + + deps = [ + ":bindings", + "//third_party/mojo/src/mojo/public/cpp/application:standalone", + "//third_party/mojo/src/mojo/public/cpp/bindings:callback", + "//third_party/mojo/src/mojo/public/cpp/environment", + "//third_party/mojo/src/mojo/public/cpp/system", + ] +} diff --git a/mojo/shell/test/pingable.mojom b/mojo/shell/test/pingable.mojom new file mode 100644 index 0000000..74ed38d --- /dev/null +++ b/mojo/shell/test/pingable.mojom @@ -0,0 +1,9 @@ +// Copyright 2015 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. + +module mojo; + +interface Pingable { + Ping(string message) => (string app_url, string connection_url, string message); +}; diff --git a/mojo/shell/test/pingable_app.cc b/mojo/shell/test/pingable_app.cc new file mode 100644 index 0000000..1849f71 --- /dev/null +++ b/mojo/shell/test/pingable_app.cc @@ -0,0 +1,69 @@ +// Copyright 2015 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 "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_runner.h" +#include "mojo/public/cpp/application/interface_factory.h" +#include "mojo/public/cpp/bindings/callback.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/shell/test/pingable.mojom.h" + +namespace mojo { + +class PingableImpl : public Pingable { + public: + PingableImpl(InterfaceRequest<Pingable> request, + const std::string& app_url, + const std::string& connection_url) + : binding_(this, request.Pass()), + app_url_(app_url), + connection_url_(connection_url) {} + + ~PingableImpl() override {} + + private: + void Ping(const String& message, + const Callback<void(String, String, String)>& callback) override { + callback.Run(app_url_, connection_url_, message); + } + + StrongBinding<Pingable> binding_; + std::string app_url_; + std::string connection_url_; +}; + +class PingableApp : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory<Pingable> { + public: + PingableApp() {} + ~PingableApp() override {} + + private: + // ApplicationDelegate: + void Initialize(ApplicationImpl* impl) override { app_url_ = impl->url(); } + + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + connection->AddService(this); + return true; + } + + // InterfaceFactory<Pingable>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<Pingable> request) override { + new PingableImpl(request.Pass(), app_url_, connection->GetConnectionURL()); + } + + std::string app_url_; +}; + +} // namespace mojo + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunner runner(new mojo::PingableApp); + return runner.Run(shell_handle); +} diff --git a/mojo/shell/url_resolver.cc b/mojo/shell/url_resolver.cc new file mode 100644 index 0000000..20cb000 --- /dev/null +++ b/mojo/shell/url_resolver.cc @@ -0,0 +1,118 @@ +// 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 "mojo/shell/url_resolver.h" + +#include "base/base_paths.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "mojo/shell/application_manager/query_util.h" +#include "mojo/shell/filename_util.h" +#include "mojo/shell/switches.h" +#include "url/url_util.h" + +namespace mojo { +namespace shell { + +URLResolver::URLResolver() { + // Needed to treat first component of mojo URLs as host, not path. + url::AddStandardScheme("mojo"); +} + +URLResolver::~URLResolver() { +} + +// static +std::vector<URLResolver::OriginMapping> URLResolver::GetOriginMappings( + const base::CommandLine::StringVector& args) { + std::vector<OriginMapping> origin_mappings; + const std::string kArgsForSwitches[] = { + "-" + std::string(switches::kMapOrigin) + "=", + "--" + std::string(switches::kMapOrigin) + "=", + }; + for (auto& arg : args) { + for (size_t i = 0; i < arraysize(kArgsForSwitches); i++) { + const std::string& argsfor_switch = kArgsForSwitches[i]; + std::string arg_string; +#if defined(OS_WIN) + arg_string = base::UTF16ToUTF8(arg); +#else + arg_string = arg; +#endif + if (arg_string.compare(0, argsfor_switch.size(), argsfor_switch) == 0) { + std::string value = + arg_string.substr(argsfor_switch.size(), std::string::npos); + size_t delim = value.find('='); + if (delim <= 0 || delim >= value.size()) + continue; + origin_mappings.push_back( + OriginMapping(value.substr(0, delim), + value.substr(delim + 1, std::string::npos))); + } + } + } + return origin_mappings; +} + +void URLResolver::AddURLMapping(const GURL& url, const GURL& mapped_url) { + url_map_[url] = mapped_url; +} + +void URLResolver::AddOriginMapping(const GURL& origin, const GURL& base_url) { + if (!origin.is_valid() || !base_url.is_valid() || + origin != origin.GetOrigin()) { + // Disallow invalid mappings. + LOG(ERROR) << "Invalid origin for mapping: " << origin; + return; + } + // Force both origin and base_url to have trailing slashes. + origin_map_[origin] = AddTrailingSlashIfNeeded(base_url); +} + +GURL URLResolver::ApplyMappings(const GURL& url) const { + std::string query; + GURL mapped_url = GetBaseURLAndQuery(url, &query); + for (;;) { + const auto& url_it = url_map_.find(mapped_url); + if (url_it != url_map_.end()) { + mapped_url = url_it->second; + continue; + } + + GURL origin = mapped_url.GetOrigin(); + const auto& origin_it = origin_map_.find(origin); + if (origin_it == origin_map_.end()) + break; + mapped_url = GURL(origin_it->second.spec() + + mapped_url.spec().substr(origin.spec().length())); + } + + if (query.length()) + mapped_url = GURL(mapped_url.spec() + query); + return mapped_url; +} + +void URLResolver::SetMojoBaseURL(const GURL& mojo_base_url) { + DCHECK(mojo_base_url.is_valid()); + // Force a trailing slash on the base_url to simplify resolving + // relative files and URLs below. + mojo_base_url_ = AddTrailingSlashIfNeeded(mojo_base_url); +} + +GURL URLResolver::ResolveMojoURL(const GURL& mojo_url) const { + if (mojo_url.scheme() != "mojo") { + // The mapping has produced some sort of non-mojo: URL - file:, http:, etc. + return mojo_url; + } else { + // It's still a mojo: URL, use the default mapping scheme. + std::string query; + GURL base_url = GetBaseURLAndQuery(mojo_url, &query); + std::string lib = base_url.host() + ".mojo" + query; + return mojo_base_url_.Resolve(lib); + } +} + +} // namespace shell +} // namespace mojo diff --git a/mojo/shell/url_resolver.h b/mojo/shell/url_resolver.h new file mode 100644 index 0000000..03f9076 --- /dev/null +++ b/mojo/shell/url_resolver.h @@ -0,0 +1,79 @@ +// 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. + +#ifndef SHELL_URL_RESOLVER_H_ +#define SHELL_URL_RESOLVER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "url/gurl.h" + +namespace mojo { +namespace shell { + +// This class supports the mapping of URLs to other URLs. +// It's commonly used with mojo: URL, to provide a physical location (i.e. +// file: or https:) but works with any URL. +// By default, "mojo:" URLs resolve to a file location, with ".mojo" appended, +// but that resolution can be customized via the AddCustomMapping method. +class URLResolver { + public: + URLResolver(); + ~URLResolver(); + + // Used for the return value of GetOriginMappings(). + struct OriginMapping { + OriginMapping(const std::string& origin, const std::string& base_url) + : origin(origin), base_url(base_url) {} + + std::string origin; + std::string base_url; + }; + + // Returns a list of origin mappings based on command line args. + // The switch --map-origin can be specified multiple times. Each occurance + // has the format of --map-origin={origin}={base_url} + // For example: + // --map-origin=http://domokit.org=file:///source/out + static std::vector<OriginMapping> GetOriginMappings( + const base::CommandLine::StringVector& argv); + + // Add a custom mapping for a particular URL. If |mapped_url| is + // itself a mojo url normal resolution rules apply. + void AddURLMapping(const GURL& url, const GURL& mapped_url); + + // Add a custom mapping for all urls rooted at |origin|. + void AddOriginMapping(const GURL& origin, const GURL& base_url); + + // Applies all custom mappings for |url|, returning the last non-mapped url. + // For example, if 'a' maps to 'b' and 'b' maps to 'c' calling this with 'a' + // returns 'c'. + GURL ApplyMappings(const GURL& url) const; + + // If specified, then "mojo:" URLs will be resolved relative to this + // URL. That is, the portion after the colon will be appeneded to + // |mojo_base_url| with .mojo appended. + void SetMojoBaseURL(const GURL& mojo_base_url); + + // Resolve the given "mojo:" URL to the URL that should be used to fetch the + // code for the corresponding Mojo App. + GURL ResolveMojoURL(const GURL& mojo_url) const; + + private: + using GURLToGURLMap = std::map<GURL, GURL>; + GURLToGURLMap url_map_; + GURLToGURLMap origin_map_; + GURL mojo_base_url_; + + DISALLOW_COPY_AND_ASSIGN(URLResolver); +}; + +} // namespace shell +} // namespace mojo + +#endif // SHELL_URL_RESOLVER_H_ diff --git a/mojo/shell/url_resolver_unittest.cc b/mojo/shell/url_resolver_unittest.cc new file mode 100644 index 0000000..f959321 --- /dev/null +++ b/mojo/shell/url_resolver_unittest.cc @@ -0,0 +1,151 @@ +// 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 "mojo/shell/url_resolver.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace shell { +namespace test { +namespace { + +typedef testing::Test URLResolverTest; + +TEST_F(URLResolverTest, MojoURLsFallThrough) { + URLResolver resolver; + resolver.AddURLMapping(GURL("mojo:test"), GURL("mojo:foo")); + const GURL base_url("file:/base"); + resolver.SetMojoBaseURL(base_url); + GURL mapped_url = resolver.ApplyMappings(GURL("mojo:test")); + std::string resolved(resolver.ResolveMojoURL(mapped_url).spec()); + // Resolved must start with |base_url|. + EXPECT_EQ(base_url.spec(), resolved.substr(0, base_url.spec().size())); + // And must contain foo. + EXPECT_NE(std::string::npos, resolved.find("foo")); +} + +TEST_F(URLResolverTest, MapURL) { + URLResolver resolver; + resolver.AddURLMapping(GURL("https://domokit.org/test.mojo"), + GURL("file:///mojo/src/out/Debug/test.mojo")); + GURL mapped_url = + resolver.ApplyMappings(GURL("https://domokit.org/test.mojo")); + EXPECT_EQ("file:///mojo/src/out/Debug/test.mojo", mapped_url.spec()); +} + +TEST_F(URLResolverTest, MultipleMapURL) { + URLResolver resolver; + resolver.AddURLMapping(GURL("https://a.org/foo"), + GURL("https://b.org/a/foo")); + resolver.AddURLMapping(GURL("https://b.org/a/foo"), + GURL("https://c.org/b/a/foo")); + GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo")); + EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec()); +} + +TEST_F(URLResolverTest, MapOrigin) { + URLResolver resolver; + resolver.AddOriginMapping(GURL("https://domokit.org"), + GURL("file:///mojo/src/out/Debug")); + GURL mapped_url = + resolver.ApplyMappings(GURL("https://domokit.org/test.mojo")); + EXPECT_EQ("file:///mojo/src/out/Debug/test.mojo", mapped_url.spec()); +} + +TEST_F(URLResolverTest, MultipleMapOrigin) { + URLResolver resolver; + resolver.AddOriginMapping(GURL("https://a.org"), GURL("https://b.org/a")); + resolver.AddOriginMapping(GURL("https://b.org"), GURL("https://c.org/b")); + GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo")); + EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec()); +} + +TEST_F(URLResolverTest, MapOriginThenURL) { + URLResolver resolver; + resolver.AddOriginMapping(GURL("https://a.org"), GURL("https://b.org/a")); + resolver.AddURLMapping(GURL("https://b.org/a/foo"), + GURL("https://c.org/b/a/foo")); + GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo")); + EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec()); +} + +TEST_F(URLResolverTest, MapURLThenOrigin) { + URLResolver resolver; + resolver.AddURLMapping(GURL("https://a.org/foo"), + GURL("https://b.org/a/foo")); + resolver.AddOriginMapping(GURL("https://b.org"), GURL("https://c.org/b")); + GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo")); + EXPECT_EQ("https://c.org/b/a/foo", mapped_url.spec()); +} + +#if defined(OS_POSIX) +#define ARG_LITERAL(x) x +#elif defined(OS_WIN) +#define ARG_LITERAL(x) L ## x +#endif + +TEST_F(URLResolverTest, GetOriginMappings) { + base::CommandLine::StringVector args; + args.push_back(ARG_LITERAL("--map-origin=https://a.org=https://b.org/a")); + std::vector<URLResolver::OriginMapping> mappings = + URLResolver::GetOriginMappings(args); + ASSERT_EQ(1U, mappings.size()); + EXPECT_EQ("https://a.org", mappings[0].origin); + EXPECT_EQ("https://b.org/a", mappings[0].base_url); + + args.clear(); + args.push_back(ARG_LITERAL("-map-origin=https://a.org=https://b.org/a")); + mappings = URLResolver::GetOriginMappings(args); + ASSERT_EQ(1U, mappings.size()); + EXPECT_EQ("https://a.org", mappings[0].origin); + EXPECT_EQ("https://b.org/a", mappings[0].base_url); + + args.clear(); + args.push_back(ARG_LITERAL("--map-origin")); + mappings = URLResolver::GetOriginMappings(args); + EXPECT_EQ(0U, mappings.size()); + + args.clear(); + args.push_back(ARG_LITERAL("--map-origin=")); + mappings = URLResolver::GetOriginMappings(args); + EXPECT_EQ(0U, mappings.size()); + + args.clear(); + args.push_back(ARG_LITERAL("mojo_shell")); + args.push_back(ARG_LITERAL("--args-for=https://a.org/foo --test")); + args.push_back(ARG_LITERAL("--map-origin=https://a.org=https://b.org/a")); + args.push_back(ARG_LITERAL("--map-origin=https://b.org=https://c.org/b")); + args.push_back(ARG_LITERAL("https://a.org/foo")); + mappings = URLResolver::GetOriginMappings(args); + ASSERT_EQ(2U, mappings.size()); + EXPECT_EQ("https://a.org", mappings[0].origin); + EXPECT_EQ("https://b.org/a", mappings[0].base_url); + EXPECT_EQ("https://b.org", mappings[1].origin); + EXPECT_EQ("https://c.org/b", mappings[1].base_url); +} + +TEST_F(URLResolverTest, TestQueryForURLMapping) { + URLResolver resolver; + resolver.SetMojoBaseURL(GURL("file:/base")); + resolver.AddURLMapping(GURL("https://a.org/foo"), + GURL("https://b.org/a/foo")); + resolver.AddURLMapping(GURL("https://b.org/a/foo"), + GURL("https://c.org/b/a/foo")); + GURL mapped_url = resolver.ApplyMappings(GURL("https://a.org/foo?a=b")); + EXPECT_EQ("https://c.org/b/a/foo?a=b", mapped_url.spec()); +} + +TEST_F(URLResolverTest, TestQueryForBaseURL) { + URLResolver resolver; + resolver.SetMojoBaseURL(GURL("file:///base")); + GURL mapped_url = resolver.ResolveMojoURL(GURL("mojo:foo?a=b")); + EXPECT_EQ("file:///base/foo.mojo?a=b", mapped_url.spec()); +} + +} // namespace +} // namespace test +} // namespace shell +} // namespace mojo diff --git a/third_party/mojo/src/mojo/public/mojo.gni b/third_party/mojo/src/mojo/public/mojo.gni index f2631a0..e7b078d 100644 --- a/third_party/mojo/src/mojo/public/mojo.gni +++ b/third_party/mojo/src/mojo/public/mojo.gni @@ -4,14 +4,6 @@ import("//build/module_args/mojo.gni") -# If using the prebuilt shell, gate its usage by the platforms for which it is -# published. -mojo_use_prebuilt_mojo_shell = false -if (!defined(mojo_build_mojo_shell_from_source) || - !mojo_build_mojo_shell_from_source) { - mojo_use_prebuilt_mojo_shell = is_linux || is_android -} - # If using the prebuilt network service, gate its usage by the platforms for # which it is published. mojo_use_prebuilt_network_service = false diff --git a/third_party/mojo/src/mojo/public/mojo_application.gni b/third_party/mojo/src/mojo/public/mojo_application.gni index 2c7ee4a..fd86ae6 100644 --- a/third_party/mojo/src/mojo/public/mojo_application.gni +++ b/third_party/mojo/src/mojo/public/mojo_application.gni @@ -77,10 +77,6 @@ template("mojo_native_application") { } # Copy any necessary prebuilt artifacts. - if (mojo_use_prebuilt_mojo_shell) { - data_deps += - [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ] - } if (mojo_use_prebuilt_network_service) { data_deps += [ rebase_path("mojo/public/tools:copy_network_service", ".", @@ -197,10 +193,6 @@ template("mojo_native_application") { } # Copy any necessary prebuilt artifacts. - if (mojo_use_prebuilt_mojo_shell) { - data_deps += - [ rebase_path("mojo/public/tools:copy_mojo_shell", ".", mojo_root) ] - } if (mojo_use_prebuilt_network_service) { data_deps += [ rebase_path("mojo/public/tools:copy_network_service", ".", diff --git a/third_party/mojo/src/mojo/public/tools/BUILD.gn b/third_party/mojo/src/mojo/public/tools/BUILD.gn index 9344cf3..607ee7a 100644 --- a/third_party/mojo/src/mojo/public/tools/BUILD.gn +++ b/third_party/mojo/src/mojo/public/tools/BUILD.gn @@ -5,29 +5,6 @@ import("//build/module_args/mojo.gni") import("../mojo.gni") -if (mojo_use_prebuilt_mojo_shell) { - copy("copy_mojo_shell") { - filename = "mojo_shell" - if (is_android) { - filename = "MojoShell.apk" - sources = [ - "prebuilt/shell/android-arm/$filename", - ] - outputs = [ - "$root_out_dir/apks/$filename", - ] - } else { - assert(is_linux) - sources = [ - "prebuilt/shell/linux-x64/$filename", - ] - outputs = [ - "$root_out_dir/$filename", - ] - } - } -} - if (mojo_use_prebuilt_network_service) { copy("copy_network_service") { filename = "network_service.mojo" |