diff options
author | Michal Klocek <michal.klocek@qt.io> | 2016-12-02 12:10:47 +0100 |
---|---|---|
committer | Michal Klocek <michal.klocek@qt.io> | 2016-12-07 15:36:22 +0000 |
commit | f0e73b6da23ab32db1edfbc912bc3ce989bf9a06 (patch) | |
tree | db26d95d495b8a471f9ddc4e4fe25dae6ed4d700 /chromium/components/mus | |
parent | 777da810b25f517d54dc4b7771e42a4ea38c355b (diff) | |
download | qtwebengine-chromium-f0e73b6da23ab32db1edfbc912bc3ce989bf9a06.tar.gz |
Add mus and catapult project files
These files are required for //content/public/browser
dependency.
Change-Id: I7fc774416006edc12cd43198af4a9fda0ce7b0d8
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/mus')
264 files changed, 37812 insertions, 0 deletions
diff --git a/chromium/components/mus/DEPS b/chromium/components/mus/DEPS new file mode 100644 index 00000000000..f3a2b3a7d97 --- /dev/null +++ b/chromium/components/mus/DEPS @@ -0,0 +1,12 @@ +include_rules = [ + "+cc", + "+ipc", + "+mojo/common", + "+mojo/converters", + "+mojo/public", + "+services/catalog/public", + "+services/shell", + "+services/tracing/public", + "+third_party/skia/include", + "+ui", +] diff --git a/chromium/components/mus/OWNERS b/chromium/components/mus/OWNERS new file mode 100644 index 00000000000..e6a7642f300 --- /dev/null +++ b/chromium/components/mus/OWNERS @@ -0,0 +1,4 @@ +ben@chromium.org +erg@chromium.org +msw@chromium.org +sky@chromium.org diff --git a/chromium/components/mus/clipboard/clipboard_impl.cc b/chromium/components/mus/clipboard/clipboard_impl.cc new file mode 100644 index 00000000000..a82d95e8f03 --- /dev/null +++ b/chromium/components/mus/clipboard/clipboard_impl.cc @@ -0,0 +1,108 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/clipboard/clipboard_impl.h" + +#include <string.h> +#include <utility> + +#include "base/macros.h" +#include "mojo/common/common_type_converters.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/string.h" + +using mojo::Array; +using mojo::Map; +using mojo::String; + +namespace mus { +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 ClipboardImpl::ClipboardData { + public: + ClipboardData() : sequence_number_(0) {} + ~ClipboardData() {} + + uint64_t sequence_number() const { + return sequence_number_; + } + + 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->first; + + return types; + } + + void SetData(Map<String, Array<uint8_t>> data) { + sequence_number_++; + data_types_ = std::move(data); + } + + 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->second.Clone(); + } + + private: + uint64_t sequence_number_; + Map<String, Array<uint8_t>> data_types_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardData); +}; + +ClipboardImpl::ClipboardImpl() { + for (int i = 0; i < kNumClipboards; ++i) + clipboard_state_[i].reset(new ClipboardData); +} + +ClipboardImpl::~ClipboardImpl() { +} + +void ClipboardImpl::AddBinding(mojom::ClipboardRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +void ClipboardImpl::GetSequenceNumber( + Clipboard::Type clipboard_type, + const GetSequenceNumberCallback& callback) { + callback.Run( + clipboard_state_[static_cast<int>(clipboard_type)]->sequence_number()); +} + +void ClipboardImpl::GetAvailableMimeTypes( + Clipboard::Type clipboard_type, + const GetAvailableMimeTypesCallback& callback) { + int clipboard_num = static_cast<int>(clipboard_type); + callback.Run(clipboard_state_[clipboard_num]->sequence_number(), + clipboard_state_[clipboard_num]->GetMimeTypes()); +} + +void ClipboardImpl::ReadClipboardData( + Clipboard::Type clipboard_type, + const String& mime_type, + const ReadClipboardDataCallback& callback) { + int clipboard_num = static_cast<int>(clipboard_type); + Array<uint8_t> mime_data(nullptr); + uint64_t sequence = clipboard_state_[clipboard_num]->sequence_number(); + clipboard_state_[clipboard_num]->GetData(mime_type, &mime_data); + callback.Run(sequence, std::move(mime_data)); +} + +void ClipboardImpl::WriteClipboardData( + Clipboard::Type clipboard_type, + Map<String, Array<uint8_t>> data, + const WriteClipboardDataCallback& callback) { + int clipboard_num = static_cast<int>(clipboard_type); + clipboard_state_[clipboard_num]->SetData(std::move(data)); + callback.Run(clipboard_state_[clipboard_num]->sequence_number()); +} + +} // namespace clipboard +} // namespace mus diff --git a/chromium/components/mus/clipboard/clipboard_impl.h b/chromium/components/mus/clipboard/clipboard_impl.h new file mode 100644 index 00000000000..7f62f1c1df5 --- /dev/null +++ b/chromium/components/mus/clipboard/clipboard_impl.h @@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_CLIPBOARD_CLIPBOARD_IMPL_H_ +#define COMPONENTS_MUS_CLIPBOARD_CLIPBOARD_IMPL_H_ + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "components/mus/public/interfaces/clipboard.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +namespace mus { +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 ClipboardImpl : public mojom::Clipboard { + public: + // mojom::Clipboard exposes three possible clipboards. + static const int kNumClipboards = 3; + + ClipboardImpl(); + ~ClipboardImpl() override; + + void AddBinding(mojom::ClipboardRequest request); + + // mojom::Clipboard implementation. + void GetSequenceNumber( + mojom::Clipboard::Type clipboard_type, + const GetSequenceNumberCallback& callback) override; + void GetAvailableMimeTypes( + mojom::Clipboard::Type clipboard_types, + const GetAvailableMimeTypesCallback& callback) override; + void ReadClipboardData(mojom::Clipboard::Type clipboard_type, + const mojo::String& mime_type, + const ReadClipboardDataCallback& callback) override; + void WriteClipboardData( + mojom::Clipboard::Type clipboard_type, + mojo::Map<mojo::String, mojo::Array<uint8_t>> data, + const WriteClipboardDataCallback& callback) override; + + private: + // Internal struct which stores the current state of the clipboard. + class ClipboardData; + + // The current clipboard state. This is what is read from. + std::unique_ptr<ClipboardData> clipboard_state_[kNumClipboards]; + mojo::BindingSet<mojom::Clipboard> bindings_; + + DISALLOW_COPY_AND_ASSIGN(ClipboardImpl); +}; + +} // namespace clipboard +} // namespace mus + +#endif // COMPONENTS_MUS_CLIPBOARD_CLIPBOARD_IMPL_H_ diff --git a/chromium/components/mus/clipboard/clipboard_unittest.cc b/chromium/components/mus/clipboard/clipboard_unittest.cc new file mode 100644 index 00000000000..764618395c7 --- /dev/null +++ b/chromium/components/mus/clipboard/clipboard_unittest.cc @@ -0,0 +1,145 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> +#include <utility> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "components/mus/public/interfaces/clipboard.mojom.h" +#include "mojo/common/common_type_converters.h" +#include "services/shell/public/cpp/shell_connection.h" +#include "services/shell/public/cpp/shell_test.h" + +using mojo::Array; +using mojo::Map; +using mojo::String; +using mus::mojom::Clipboard; + +namespace mus { +namespace clipboard { +namespace { + +const char* kUninitialized = "Uninitialized data"; +const char* kPlainTextData = "Some plain data"; +const char* kHtmlData = "<html>data</html>"; + +} // namespace + +class ClipboardAppTest : public shell::test::ShellTest { + public: + ClipboardAppTest() : ShellTest("exe:mus_clipboard_unittests") {} + ~ClipboardAppTest() override {} + + // Overridden from shell::test::ShellTest: + void SetUp() override { + ShellTest::SetUp(); + + connector()->ConnectToInterface("mojo:mus", &clipboard_); + ASSERT_TRUE(clipboard_); + } + + uint64_t GetSequenceNumber() { + uint64_t sequence_num = 999999; + EXPECT_TRUE(clipboard_->GetSequenceNumber( + Clipboard::Type::COPY_PASTE, &sequence_num)); + return sequence_num; + } + + std::vector<std::string> GetAvailableFormatMimeTypes() { + uint64_t sequence_num = 999999; + Array<String> types; + types.push_back(kUninitialized); + clipboard_->GetAvailableMimeTypes( + Clipboard::Type::COPY_PASTE, + &sequence_num, &types); + return types.To<std::vector<std::string>>(); + } + + bool GetDataOfType(const std::string& mime_type, std::string* data) { + bool valid = false; + Array<uint8_t> raw_data; + uint64_t sequence_number = 0; + clipboard_->ReadClipboardData(Clipboard::Type::COPY_PASTE, mime_type, + &sequence_number, &raw_data); + valid = !raw_data.is_null(); + *data = raw_data.is_null() ? "" : raw_data.To<std::string>(); + return valid; + } + + void SetStringText(const std::string& data) { + uint64_t sequence_number; + Map<String, Array<uint8_t>> mime_data; + mime_data[mojom::kMimeTypeText] = Array<uint8_t>::From(data); + clipboard_->WriteClipboardData(Clipboard::Type::COPY_PASTE, + std::move(mime_data), + &sequence_number); + } + + protected: + mojom::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(mojom::kMimeTypeText, &data)); +} + +TEST_F(ClipboardAppTest, CanReadBackText) { + std::string data; + EXPECT_EQ(0ul, GetSequenceNumber()); + EXPECT_FALSE(GetDataOfType(mojom::kMimeTypeText, &data)); + + SetStringText(kPlainTextData); + EXPECT_EQ(1ul, GetSequenceNumber()); + + EXPECT_TRUE(GetDataOfType(mojom::kMimeTypeText, &data)); + EXPECT_EQ(kPlainTextData, data); +} + +TEST_F(ClipboardAppTest, CanSetMultipleDataTypesAtOnce) { + Map<String, Array<uint8_t>> mime_data; + mime_data[mojom::kMimeTypeText] = + Array<uint8_t>::From(std::string(kPlainTextData)); + mime_data[mojom::kMimeTypeHTML] = + Array<uint8_t>::From(std::string(kHtmlData)); + + uint64_t sequence_num = 0; + clipboard_->WriteClipboardData(Clipboard::Type::COPY_PASTE, + std::move(mime_data), + &sequence_num); + EXPECT_EQ(1ul, sequence_num); + + std::string data; + EXPECT_TRUE(GetDataOfType(mojom::kMimeTypeText, &data)); + EXPECT_EQ(kPlainTextData, data); + EXPECT_TRUE(GetDataOfType(mojom::kMimeTypeHTML, &data)); + EXPECT_EQ(kHtmlData, data); +} + +TEST_F(ClipboardAppTest, CanClearClipboardWithZeroArray) { + std::string data; + SetStringText(kPlainTextData); + EXPECT_EQ(1ul, GetSequenceNumber()); + + EXPECT_TRUE(GetDataOfType(mojom::kMimeTypeText, &data)); + EXPECT_EQ(kPlainTextData, data); + + Map<String, Array<uint8_t>> mime_data; + uint64_t sequence_num = 0; + clipboard_->WriteClipboardData(Clipboard::Type::COPY_PASTE, + std::move(mime_data), + &sequence_num); + + EXPECT_EQ(2ul, sequence_num); + EXPECT_FALSE(GetDataOfType(mojom::kMimeTypeText, &data)); +} + +} // namespace clipboard +} // namespace mus diff --git a/chromium/components/mus/clipboard/test_manifest.json b/chromium/components/mus/clipboard/test_manifest.json new file mode 100644 index 00000000000..26e694cea02 --- /dev/null +++ b/chromium/components/mus/clipboard/test_manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 1, + "name": "exe:mus_clipboard_unittests", + "display_name": "Clipboard Service Unittests", + "capabilities": { + "required": { + "mojo:mus": { "interfaces": [ "mus::mojom::Clipboard" ] } + } + } +} diff --git a/chromium/components/mus/common/DEPS b/chromium/components/mus/common/DEPS new file mode 100644 index 00000000000..f77ec566793 --- /dev/null +++ b/chromium/components/mus/common/DEPS @@ -0,0 +1,12 @@ +include_rules = [ + "+gpu/config", + "+gpu/command_buffer", + "+gpu/ipc/client", + "+gpu/ipc/common", +] + +specific_include_rules = { + "run_all_shelltests\.cc": [ + "+mojo/edk/embedder/embedder.h", + ], +} diff --git a/chromium/components/mus/common/event_matcher_util.cc b/chromium/components/mus/common/event_matcher_util.cc new file mode 100644 index 00000000000..a82a12d3002 --- /dev/null +++ b/chromium/components/mus/common/event_matcher_util.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 "components/mus/common/event_matcher_util.h" + +namespace mus { + +mojom::EventMatcherPtr CreateKeyMatcher(ui::mojom::KeyboardCode code, + int flags) { + mojom::EventMatcherPtr matcher(mojom::EventMatcher::New()); + matcher->type_matcher = mojom::EventTypeMatcher::New(); + matcher->flags_matcher = mojom::EventFlagsMatcher::New(); + matcher->ignore_flags_matcher = mojom::EventFlagsMatcher::New(); + // Ignoring these makes most accelerator scenarios more straight forward. Code + // that needs to check them can override this setting. + matcher->ignore_flags_matcher->flags = ui::mojom::kEventFlagCapsLockOn | + ui::mojom::kEventFlagScrollLockOn | + ui::mojom::kEventFlagNumLockOn; + matcher->key_matcher = mojom::KeyEventMatcher::New(); + + matcher->type_matcher->type = ui::mojom::EventType::KEY_PRESSED; + matcher->flags_matcher->flags = flags; + matcher->key_matcher->keyboard_code = code; + return matcher; +} + +} // namespace mus diff --git a/chromium/components/mus/common/event_matcher_util.h b/chromium/components/mus/common/event_matcher_util.h new file mode 100644 index 00000000000..2b99729ea2a --- /dev/null +++ b/chromium/components/mus/common/event_matcher_util.h @@ -0,0 +1,22 @@ +// 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 COMPONENTS_MUS_COMMON_EVENT_MATCHER_UTIL_H_ +#define COMPONENTS_MUS_COMMON_EVENT_MATCHER_UTIL_H_ + +#include "components/mus/common/mus_common_export.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "ui/events/mojo/event_constants.mojom.h" +#include "ui/events/mojo/keyboard_codes.mojom.h" + +namespace mus { + +// |flags| is a bitfield of kEventFlag* and kMouseEventFlag* values in +// input_event_constants.mojom. +mojom::EventMatcherPtr MUS_COMMON_EXPORT +CreateKeyMatcher(ui::mojom::KeyboardCode code, int flags); + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_EVENT_MATCHER_UTIL_H_ diff --git a/chromium/components/mus/common/generic_shared_memory_id_generator.cc b/chromium/components/mus/common/generic_shared_memory_id_generator.cc new file mode 100644 index 00000000000..622a53940b2 --- /dev/null +++ b/chromium/components/mus/common/generic_shared_memory_id_generator.cc @@ -0,0 +1,21 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/common/generic_shared_memory_id_generator.h" + +#include "base/atomic_sequence_num.h" + +namespace mus { +namespace { + +// Global atomic to generate gpu memory buffer unique IDs. +base::StaticAtomicSequenceNumber g_next_generic_shared_memory_id; + +} // namespace + +gfx::GenericSharedMemoryId GetNextGenericSharedMemoryId() { + return gfx::GenericSharedMemoryId(g_next_generic_shared_memory_id.GetNext()); +} + +} // namespace mus diff --git a/chromium/components/mus/common/generic_shared_memory_id_generator.h b/chromium/components/mus/common/generic_shared_memory_id_generator.h new file mode 100644 index 00000000000..c462e093d3a --- /dev/null +++ b/chromium/components/mus/common/generic_shared_memory_id_generator.h @@ -0,0 +1,19 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_COMMON_GENERIC_SHARED_MEMORY_ID_GENERATOR_H_ +#define COMPONENTS_MUS_COMMON_GENERIC_SHARED_MEMORY_ID_GENERATOR_H_ + +#include "components/mus/common/mus_common_export.h" +#include "ui/gfx/generic_shared_memory_id.h" + +namespace mus { + +// Returns the next GenericSharedMemoryId for the current process. This should +// be used anywhere a new GenericSharedMemoryId is needed. +MUS_COMMON_EXPORT gfx::GenericSharedMemoryId GetNextGenericSharedMemoryId(); + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_GENERIC_SHARED_MEMORY_ID_GENERATOR_H_ diff --git a/chromium/components/mus/common/gpu_memory_buffer_impl.cc b/chromium/components/mus/common/gpu_memory_buffer_impl.cc new file mode 100644 index 00000000000..415f6548f14 --- /dev/null +++ b/chromium/components/mus/common/gpu_memory_buffer_impl.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 "components/mus/common/gpu_memory_buffer_impl.h" + +namespace mus { + +GpuMemoryBufferImpl::GpuMemoryBufferImpl(gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format) + : id_(id), size_(size), format_(format), mapped_(false) {} + +GpuMemoryBufferImpl::~GpuMemoryBufferImpl() { + DCHECK(!mapped_); +} + +// static +GpuMemoryBufferImpl* GpuMemoryBufferImpl::FromClientBuffer( + ClientBuffer buffer) { + return reinterpret_cast<GpuMemoryBufferImpl*>(buffer); +} + +gfx::Size GpuMemoryBufferImpl::GetSize() const { + return size_; +} + +gfx::BufferFormat GpuMemoryBufferImpl::GetFormat() const { + return format_; +} + +gfx::GpuMemoryBufferId GpuMemoryBufferImpl::GetId() const { + return id_; +} + +ClientBuffer GpuMemoryBufferImpl::AsClientBuffer() { + return reinterpret_cast<ClientBuffer>(this); +} + +#if defined(USE_OZONE) +scoped_refptr<ui::NativePixmap> GpuMemoryBufferImpl::GetNativePixmap() { + return scoped_refptr<ui::NativePixmap>(); +} +#endif + +} // namespace mus diff --git a/chromium/components/mus/common/gpu_memory_buffer_impl.h b/chromium/components/mus/common/gpu_memory_buffer_impl.h new file mode 100644 index 00000000000..84f1f9d90ad --- /dev/null +++ b/chromium/components/mus/common/gpu_memory_buffer_impl.h @@ -0,0 +1,61 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_COMMON_GPU_MEMORY_BUFFER_IMPL_H_ +#define COMPONENTS_MUS_COMMON_GPU_MEMORY_BUFFER_IMPL_H_ + +#include <memory> + +#include "base/callback.h" +#include "base/macros.h" +#include "components/mus/common/mus_common_export.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" + +#if defined(USE_OZONE) +#include "ui/ozone/public/native_pixmap.h" +#endif + +namespace mus { + +// Provides common implementation of a GPU memory buffer. +class MUS_COMMON_EXPORT GpuMemoryBufferImpl : public gfx::GpuMemoryBuffer { + public: + ~GpuMemoryBufferImpl() override; + + // Type-checking upcast routine. Returns an NULL on failure. + static GpuMemoryBufferImpl* FromClientBuffer(ClientBuffer buffer); + + // Overridden from gfx::GpuMemoryBuffer: + gfx::Size GetSize() const override; + gfx::BufferFormat GetFormat() const override; + gfx::GpuMemoryBufferId GetId() const override; + ClientBuffer AsClientBuffer() override; + + // Returns the type of this GpuMemoryBufferImpl. + virtual gfx::GpuMemoryBufferType GetBufferType() const = 0; + +#if defined(USE_OZONE) + // Returns a ui::NativePixmap when one is available. + virtual scoped_refptr<ui::NativePixmap> GetNativePixmap(); +#endif + + protected: + GpuMemoryBufferImpl(gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format); + + const gfx::GpuMemoryBufferId id_; + const gfx::Size size_; + const gfx::BufferFormat format_; + bool mapped_; + + private: + DISALLOW_COPY_AND_ASSIGN(GpuMemoryBufferImpl); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_GPU_MEMORY_BUFFER_IMPL_H_ diff --git a/chromium/components/mus/common/gpu_service.cc b/chromium/components/mus/common/gpu_service.cc new file mode 100644 index 00000000000..4eea3e45972 --- /dev/null +++ b/chromium/components/mus/common/gpu_service.cc @@ -0,0 +1,256 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/common/gpu_service.h" + +#include "base/command_line.h" +#include "base/memory/singleton.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/common/switches.h" +#include "components/mus/public/interfaces/gpu_service.mojom.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "services/shell/public/cpp/connector.h" + +namespace mus { + +namespace { + +void PostTask(scoped_refptr<base::SingleThreadTaskRunner> runner, + const tracked_objects::Location& from_here, + const base::Closure& callback) { + runner->PostTask(from_here, callback); +} + +GpuService* g_gpu_service = nullptr; +} + +GpuService::GpuService(shell::Connector* connector) + : main_task_runner_(base::ThreadTaskRunnerHandle::Get()), + connector_(connector), + shutdown_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + io_thread_("GPUIOThread"), + gpu_memory_buffer_manager_(new MojoGpuMemoryBufferManager), + is_establishing_(false), + establishing_condition_(&lock_) { + DCHECK(main_task_runner_); + DCHECK(connector_); + base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); + thread_options.priority = base::ThreadPriority::NORMAL; + CHECK(io_thread_.StartWithOptions(thread_options)); +} + +GpuService::~GpuService() { + DCHECK(IsMainThread()); + if (gpu_channel_) + gpu_channel_->DestroyChannel(); +} + +// static +bool GpuService::UseChromeGpuCommandBuffer() { +// TODO(penghuang): Kludge: Running with Chrome GPU command buffer by default +// breaks unit tests on Windows +#if defined(OS_WIN) + return false; +#else + return !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kUseMojoGpuCommandBufferInMus); +#endif +} + +// static +void GpuService::Initialize(shell::Connector* connector) { + DCHECK(!g_gpu_service); + g_gpu_service = new GpuService(connector); +} + +// static +void GpuService::Terminate() { + DCHECK(g_gpu_service); + delete g_gpu_service; + g_gpu_service = nullptr; +} + +// static +GpuService* GpuService::GetInstance() { + DCHECK(g_gpu_service); + return g_gpu_service; +} + +void GpuService::EstablishGpuChannel(const base::Closure& callback) { + base::AutoLock auto_lock(lock_); + auto runner = base::ThreadTaskRunnerHandle::Get(); + if (GetGpuChannelLocked()) { + runner->PostTask(FROM_HERE, callback); + return; + } + + base::Closure wrapper_callback = + IsMainThread() ? callback + : base::Bind(PostTask, runner, FROM_HERE, callback); + establish_callbacks_.push_back(wrapper_callback); + + if (!is_establishing_) { + is_establishing_ = true; + main_task_runner_->PostTask( + FROM_HERE, base::Bind(&GpuService::EstablishGpuChannelOnMainThread, + base::Unretained(this))); + } +} + +scoped_refptr<gpu::GpuChannelHost> GpuService::EstablishGpuChannelSync() { + base::AutoLock auto_lock(lock_); + if (GetGpuChannelLocked()) + return gpu_channel_; + + if (IsMainThread()) { + is_establishing_ = true; + EstablishGpuChannelOnMainThreadSyncLocked(); + } else { + if (!is_establishing_) { + // Create an establishing gpu channel task, if there isn't one. + is_establishing_ = true; + main_task_runner_->PostTask( + FROM_HERE, base::Bind(&GpuService::EstablishGpuChannelOnMainThread, + base::Unretained(this))); + } + + // Wait until the pending establishing task is finished. + do { + establishing_condition_.Wait(); + } while (is_establishing_); + } + return gpu_channel_; +} + +scoped_refptr<gpu::GpuChannelHost> GpuService::GetGpuChannel() { + base::AutoLock auto_lock(lock_); + return GetGpuChannelLocked(); +} + +scoped_refptr<gpu::GpuChannelHost> GpuService::GetGpuChannelLocked() { + if (gpu_channel_ && gpu_channel_->IsLost()) { + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&gpu::GpuChannelHost::DestroyChannel, gpu_channel_)); + gpu_channel_ = nullptr; + } + return gpu_channel_; +} + +void GpuService::EstablishGpuChannelOnMainThread() { + base::AutoLock auto_lock(lock_); + DCHECK(IsMainThread()); + + // In GpuService::EstablishGpuChannelOnMainThreadSyncLocked(), we use the sync + // mojo EstablishGpuChannel call, after that call the gpu_service_ will be + // reset immediatelly. So gpu_service_ should be always null here. + DCHECK(!gpu_service_); + + // is_establishing_ is false, it means GpuService::EstablishGpuChannelSync() + // has been used, and we don't need try to establish a new GPU channel + // anymore. + if (!is_establishing_) + return; + + connector_->ConnectToInterface("mojo:mus", &gpu_service_); + const bool locked = false; + gpu_service_->EstablishGpuChannel( + base::Bind(&GpuService::EstablishGpuChannelOnMainThreadDone, + base::Unretained(this), locked)); +} + +void GpuService::EstablishGpuChannelOnMainThreadSyncLocked() { + DCHECK(IsMainThread()); + DCHECK(is_establishing_); + + // In browser process, EstablishGpuChannelSync() is only used by testing & + // GpuProcessTransportFactory::GetGLHelper(). For GetGLHelper(), it expects + // the gpu channel has been established, so it should not reach here. + // For testing, the asyc method should not be used. + // In renderer process, we only use EstablishGpuChannelSync(). + // So the gpu_service_ should be null here. + DCHECK(!gpu_service_); + + int client_id = 0; + mojom::ChannelHandlePtr channel_handle; + mojom::GpuInfoPtr gpu_info; + connector_->ConnectToInterface("mojo:mus", &gpu_service_); + { + base::AutoUnlock auto_unlock(lock_); + mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; + if (!gpu_service_->EstablishGpuChannel(&client_id, &channel_handle, + &gpu_info)) { + DLOG(WARNING) + << "Channel encountered error while establishing gpu channel."; + return; + } + } + const bool locked = true; + EstablishGpuChannelOnMainThreadDone( + locked, client_id, std::move(channel_handle), std::move(gpu_info)); +} + +void GpuService::EstablishGpuChannelOnMainThreadDone( + bool locked, + int client_id, + mojom::ChannelHandlePtr channel_handle, + mojom::GpuInfoPtr gpu_info) { + DCHECK(IsMainThread()); + scoped_refptr<gpu::GpuChannelHost> gpu_channel; + if (client_id) { + // TODO(penghuang): Get the real gpu info from mus. + gpu_channel = gpu::GpuChannelHost::Create( + this, client_id, gpu::GPUInfo(), + channel_handle.To<IPC::ChannelHandle>(), &shutdown_event_, + gpu_memory_buffer_manager_.get()); + } + + auto auto_lock = base::WrapUnique<base::AutoLock>( + locked ? nullptr : new base::AutoLock(lock_)); + DCHECK(is_establishing_); + DCHECK(!gpu_channel_); + + is_establishing_ = false; + gpu_channel_ = gpu_channel; + establishing_condition_.Broadcast(); + + for (const auto& i : establish_callbacks_) + i.Run(); + establish_callbacks_.clear(); + gpu_service_.reset(); +} + +bool GpuService::IsMainThread() { + return main_task_runner_->BelongsToCurrentThread(); +} + +scoped_refptr<base::SingleThreadTaskRunner> +GpuService::GetIOThreadTaskRunner() { + return io_thread_.task_runner(); +} + +std::unique_ptr<base::SharedMemory> GpuService::AllocateSharedMemory( + size_t size) { + mojo::ScopedSharedBufferHandle handle = + mojo::SharedBufferHandle::Create(size); + if (!handle.is_valid()) + return nullptr; + + base::SharedMemoryHandle platform_handle; + size_t shared_memory_size; + bool readonly; + MojoResult result = mojo::UnwrapSharedMemoryHandle( + std::move(handle), &platform_handle, &shared_memory_size, &readonly); + if (result != MOJO_RESULT_OK) + return nullptr; + DCHECK_EQ(shared_memory_size, size); + + return base::MakeUnique<base::SharedMemory>(platform_handle, readonly); +} + +} // namespace mus diff --git a/chromium/components/mus/common/gpu_service.h b/chromium/components/mus/common/gpu_service.h new file mode 100644 index 00000000000..a3d5cd106f6 --- /dev/null +++ b/chromium/components/mus/common/gpu_service.h @@ -0,0 +1,86 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_COMMON_GPU_SERVICE_H_ +#define COMPONENTS_MUS_COMMON_GPU_SERVICE_H_ + +#include <stdint.h> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "components/mus/common/mojo_gpu_memory_buffer_manager.h" +#include "components/mus/common/mus_common_export.h" +#include "components/mus/public/interfaces/gpu_service.mojom.h" +#include "gpu/ipc/client/gpu_channel_host.h" + +namespace shell { +class Connector; +} + +namespace mus { + +class MUS_COMMON_EXPORT GpuService : public gpu::GpuChannelHostFactory { + public: + void EstablishGpuChannel(const base::Closure& callback); + scoped_refptr<gpu::GpuChannelHost> EstablishGpuChannelSync(); + scoped_refptr<gpu::GpuChannelHost> GetGpuChannel(); + gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager() const { + return gpu_memory_buffer_manager_.get(); + } + + static bool UseChromeGpuCommandBuffer(); + + // The GpuService has to be initialized in the main thread before establishing + // the gpu channel. + static void Initialize(shell::Connector* connector); + // The GpuService has to be terminated in the main thread. + static void Terminate(); + static GpuService* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits<GpuService>; + + explicit GpuService(shell::Connector* connector); + ~GpuService() override; + + scoped_refptr<gpu::GpuChannelHost> GetGpuChannelLocked(); + void EstablishGpuChannelOnMainThread(); + void EstablishGpuChannelOnMainThreadSyncLocked(); + void EstablishGpuChannelOnMainThreadDone( + bool locked, + int client_id, + mojom::ChannelHandlePtr channel_handle, + mojom::GpuInfoPtr gpu_info); + + // gpu::GpuChannelHostFactory overrides: + bool IsMainThread() override; + scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() override; + std::unique_ptr<base::SharedMemory> AllocateSharedMemory( + size_t size) override; + + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + shell::Connector* connector_; + base::WaitableEvent shutdown_event_; + base::Thread io_thread_; + std::unique_ptr<MojoGpuMemoryBufferManager> gpu_memory_buffer_manager_; + + // Lock for |gpu_channel_|, |establish_callbacks_| & |is_establishing_|. + base::Lock lock_; + bool is_establishing_; + mus::mojom::GpuServicePtr gpu_service_; + scoped_refptr<gpu::GpuChannelHost> gpu_channel_; + std::vector<base::Closure> establish_callbacks_; + base::ConditionVariable establishing_condition_; + + DISALLOW_COPY_AND_ASSIGN(GpuService); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_LIB_GPU_SERVICE_CONNECTION_H_ diff --git a/chromium/components/mus/common/gpu_type_converters.cc b/chromium/components/mus/common/gpu_type_converters.cc new file mode 100644 index 00000000000..7733c7d3bf7 --- /dev/null +++ b/chromium/components/mus/common/gpu_type_converters.cc @@ -0,0 +1,164 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/common/gpu_type_converters.h" + +#include "build/build_config.h" +#include "gpu/config/gpu_info.h" +#include "ipc/ipc_channel_handle.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "ui/gfx/gpu_memory_buffer.h" + +#if defined(USE_OZONE) +#include "ui/gfx/native_pixmap_handle_ozone.h" +#endif + +namespace mojo { + +// static +mus::mojom::ChannelHandlePtr +TypeConverter<mus::mojom::ChannelHandlePtr, IPC::ChannelHandle>::Convert( + const IPC::ChannelHandle& handle) { + mus::mojom::ChannelHandlePtr result = mus::mojom::ChannelHandle::New(); + result->name = handle.name; +#if defined(OS_WIN) + // On windows, a pipe handle Will NOT be marshalled over IPC. + DCHECK(handle.pipe.handle == NULL); +#else + DCHECK(handle.socket.auto_close || handle.socket.fd == -1); + base::PlatformFile platform_file = handle.socket.fd; + if (platform_file != -1) + result->socket = mojo::WrapPlatformFile(platform_file); +#endif + return result; +} + +// static +IPC::ChannelHandle +TypeConverter<IPC::ChannelHandle, mus::mojom::ChannelHandlePtr>::Convert( + const mus::mojom::ChannelHandlePtr& handle) { + if (handle.is_null()) + return IPC::ChannelHandle(); +#if defined(OS_WIN) + // On windows, a pipe handle Will NOT be marshalled over IPC. + DCHECK(!handle->socket.is_valid()); + return IPC::ChannelHandle(handle->name); +#else + base::PlatformFile platform_file = -1; + mojo::UnwrapPlatformFile(std::move(handle->socket), &platform_file); + return IPC::ChannelHandle(handle->name, + base::FileDescriptor(platform_file, true)); +#endif +} + +#if defined(USE_OZONE) +// static +mus::mojom::NativePixmapHandlePtr TypeConverter< + mus::mojom::NativePixmapHandlePtr, + gfx::NativePixmapHandle>::Convert(const gfx::NativePixmapHandle& handle) { + // TODO(penghuang); support NativePixmapHandle. + mus::mojom::NativePixmapHandlePtr result = + mus::mojom::NativePixmapHandle::New(); + return result; +} + +// static +gfx::NativePixmapHandle +TypeConverter<gfx::NativePixmapHandle, mus::mojom::NativePixmapHandlePtr>:: + Convert(const mus::mojom::NativePixmapHandlePtr& handle) { + // TODO(penghuang); support NativePixmapHandle. + gfx::NativePixmapHandle result; + return result; +} +#endif + +// static +mus::mojom::GpuMemoryBufferIdPtr TypeConverter< + mus::mojom::GpuMemoryBufferIdPtr, + gfx::GpuMemoryBufferId>::Convert(const gfx::GpuMemoryBufferId& id) { + mus::mojom::GpuMemoryBufferIdPtr result = + mus::mojom::GpuMemoryBufferId::New(); + result->id = id.id; + return result; +} + +// static +gfx::GpuMemoryBufferId +TypeConverter<gfx::GpuMemoryBufferId, mus::mojom::GpuMemoryBufferIdPtr>:: + Convert(const mus::mojom::GpuMemoryBufferIdPtr& id) { + return gfx::GpuMemoryBufferId(id->id); +} + +// static +mus::mojom::GpuMemoryBufferHandlePtr TypeConverter< + mus::mojom::GpuMemoryBufferHandlePtr, + gfx::GpuMemoryBufferHandle>::Convert(const gfx::GpuMemoryBufferHandle& + handle) { + DCHECK(handle.type == gfx::SHARED_MEMORY_BUFFER); + mus::mojom::GpuMemoryBufferHandlePtr result = + mus::mojom::GpuMemoryBufferHandle::New(); + result->type = static_cast<mus::mojom::GpuMemoryBufferType>(handle.type); + result->id = mus::mojom::GpuMemoryBufferId::From(handle.id); + base::PlatformFile platform_file; +#if defined(OS_WIN) + platform_file = handle.handle.GetHandle(); +#else + DCHECK(handle.handle.auto_close || handle.handle.fd == -1); + platform_file = handle.handle.fd; +#endif + result->buffer_handle = mojo::WrapPlatformFile(platform_file); + result->offset = handle.offset; + result->stride = handle.stride; +#if defined(USE_OZONE) + result->native_pixmap_handle = + mus::mojom::NativePixmapHandle::From(handle.native_pixmap_handle); +#endif + return result; +} + +// static +gfx::GpuMemoryBufferHandle TypeConverter<gfx::GpuMemoryBufferHandle, + mus::mojom::GpuMemoryBufferHandlePtr>:: + Convert(const mus::mojom::GpuMemoryBufferHandlePtr& handle) { + DCHECK(handle->type == mus::mojom::GpuMemoryBufferType::SHARED_MEMORY); + gfx::GpuMemoryBufferHandle result; + result.type = static_cast<gfx::GpuMemoryBufferType>(handle->type); + result.id = handle->id.To<gfx::GpuMemoryBufferId>(); + base::PlatformFile platform_file; + MojoResult unwrap_result = mojo::UnwrapPlatformFile( + std::move(handle->buffer_handle), &platform_file); + if (unwrap_result == MOJO_RESULT_OK) { +#if defined(OS_WIN) + result.handle = + base::SharedMemoryHandle(platform_file, base::GetCurrentProcId()); +#else + result.handle = base::SharedMemoryHandle(platform_file, true); +#endif + } + result.offset = handle->offset; + result.stride = handle->stride; +#if defined(USE_OZONE) + result.native_pixmap_handle = + handle->native_pixmap_handle.To<gfx::NativePixmapHandle>(); +#else + DCHECK(handle->native_pixmap_handle.is_null()); +#endif + return result; +} + +// static +mus::mojom::GpuInfoPtr +TypeConverter<mus::mojom::GpuInfoPtr, gpu::GPUInfo>::Convert( + const gpu::GPUInfo& input) { + mus::mojom::GpuInfoPtr result(mus::mojom::GpuInfo::New()); + result->vendor_id = input.gpu.vendor_id; + result->device_id = input.gpu.device_id; + result->vendor_info = mojo::String::From<std::string>(input.gl_vendor); + result->renderer_info = mojo::String::From<std::string>(input.gl_renderer); + result->driver_version = + mojo::String::From<std::string>(input.driver_version); + return result; +} + +} // namespace mojo diff --git a/chromium/components/mus/common/gpu_type_converters.h b/chromium/components/mus/common/gpu_type_converters.h new file mode 100644 index 00000000000..74c170c0437 --- /dev/null +++ b/chromium/components/mus/common/gpu_type_converters.h @@ -0,0 +1,95 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_COMMON_GPU_TYPE_CONVERTERS_H_ +#define COMPONENTS_MUS_COMMON_GPU_TYPE_CONVERTERS_H_ + +#include "build/build_config.h" +#include "components/mus/common/mus_common_export.h" +#include "components/mus/public/interfaces/channel_handle.mojom.h" +#include "components/mus/public/interfaces/gpu.mojom.h" +#include "components/mus/public/interfaces/gpu_memory_buffer.mojom.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace gfx { +struct GpuMemoryBufferHandle; +class GenericSharedMemoryId; +using GpuMemoryBufferId = GenericSharedMemoryId; +struct NativePixmapHandle; +} + +namespace gpu { +struct GPUInfo; +} + +namespace IPC { +struct ChannelHandle; +} + +namespace mojo { + +template <> +struct MUS_COMMON_EXPORT + TypeConverter<mus::mojom::ChannelHandlePtr, IPC::ChannelHandle> { + static mus::mojom::ChannelHandlePtr Convert(const IPC::ChannelHandle& handle); +}; + +template <> +struct MUS_COMMON_EXPORT + TypeConverter<IPC::ChannelHandle, mus::mojom::ChannelHandlePtr> { + static IPC::ChannelHandle Convert(const mus::mojom::ChannelHandlePtr& handle); +}; + +#if defined(USE_OZONE) +template <> +struct MUS_COMMON_EXPORT + TypeConverter<mus::mojom::NativePixmapHandlePtr, gfx::NativePixmapHandle> { + static mus::mojom::NativePixmapHandlePtr Convert( + const gfx::NativePixmapHandle& handle); +}; + +template <> +struct MUS_COMMON_EXPORT + TypeConverter<gfx::NativePixmapHandle, mus::mojom::NativePixmapHandlePtr> { + static gfx::NativePixmapHandle Convert( + const mus::mojom::NativePixmapHandlePtr& handle); +}; +#endif + +template <> +struct MUS_COMMON_EXPORT + TypeConverter<mus::mojom::GpuMemoryBufferIdPtr, gfx::GpuMemoryBufferId> { + static mus::mojom::GpuMemoryBufferIdPtr Convert( + const gfx::GpuMemoryBufferId& id); +}; + +template <> +struct MUS_COMMON_EXPORT + TypeConverter<gfx::GpuMemoryBufferId, mus::mojom::GpuMemoryBufferIdPtr> { + static gfx::GpuMemoryBufferId Convert( + const mus::mojom::GpuMemoryBufferIdPtr& id); +}; + +template <> +struct MUS_COMMON_EXPORT TypeConverter<mus::mojom::GpuMemoryBufferHandlePtr, + gfx::GpuMemoryBufferHandle> { + static mus::mojom::GpuMemoryBufferHandlePtr Convert( + const gfx::GpuMemoryBufferHandle& handle); +}; + +template <> +struct MUS_COMMON_EXPORT TypeConverter<gfx::GpuMemoryBufferHandle, + mus::mojom::GpuMemoryBufferHandlePtr> { + static gfx::GpuMemoryBufferHandle Convert( + const mus::mojom::GpuMemoryBufferHandlePtr& handle); +}; + +template <> +struct MUS_COMMON_EXPORT TypeConverter<mus::mojom::GpuInfoPtr, gpu::GPUInfo> { + static mus::mojom::GpuInfoPtr Convert(const gpu::GPUInfo& input); +}; + +} // namespace mojo + +#endif // COMPONENTS_MUS_COMMON_GPU_TYPE_CONVERTERS_H_ diff --git a/chromium/components/mus/common/gpu_type_converters_unittest.cc b/chromium/components/mus/common/gpu_type_converters_unittest.cc new file mode 100644 index 00000000000..012664309d9 --- /dev/null +++ b/chromium/components/mus/common/gpu_type_converters_unittest.cc @@ -0,0 +1,94 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/files/scoped_file.h" +#include "build/build_config.h" +#include "components/mus/common/gpu_type_converters.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_channel_handle.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" + +// Test for mojo TypeConverter of mus::mojom::ChannelHandle. +TEST(MusGpuTypeConvertersTest, ChannelHandle) { + { + const std::string channel_name = "test_channel_name"; + IPC::ChannelHandle handle(channel_name); + + mus::mojom::ChannelHandlePtr mojo_handle = + mus::mojom::ChannelHandle::From(handle); + ASSERT_EQ(mojo_handle->name, channel_name); + EXPECT_FALSE(mojo_handle->socket.is_valid()); + + handle = mojo_handle.To<IPC::ChannelHandle>(); + ASSERT_EQ(handle.name, channel_name); +#if defined(OS_POSIX) + ASSERT_EQ(handle.socket.fd, -1); +#endif + } + +#if defined(OS_POSIX) + { + const std::string channel_name = "test_channel_name"; + int fd1 = -1; + int fd2 = -1; + bool result = IPC::SocketPair(&fd1, &fd2); + EXPECT_TRUE(result); + + base::ScopedFD scoped_fd1(fd1); + base::ScopedFD scoped_fd2(fd2); + IPC::ChannelHandle handle(channel_name, + base::FileDescriptor(scoped_fd1.release(), true)); + + mus::mojom::ChannelHandlePtr mojo_handle = + mus::mojom::ChannelHandle::From(handle); + ASSERT_EQ(mojo_handle->name, channel_name); + EXPECT_TRUE(mojo_handle->socket.is_valid()); + + handle = mojo_handle.To<IPC::ChannelHandle>(); + ASSERT_EQ(handle.name, channel_name); + ASSERT_NE(handle.socket.fd, -1); + EXPECT_TRUE(handle.socket.auto_close); + base::ScopedFD socped_fd3(handle.socket.fd); + } +#endif +} + +// Test for mojo TypeConverter of mus::mojom::GpuMemoryBufferHandle +TEST(MusGpuTypeConvertersTest, GpuMemoryBufferHandle) { + const gfx::GpuMemoryBufferId kId(99); + const uint32_t kOffset = 126; + const int32_t kStride = 256; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.CreateAnonymous(1024)); + ASSERT_TRUE(shared_memory.Map(1024)); + + gfx::GpuMemoryBufferHandle handle; + handle.type = gfx::SHARED_MEMORY_BUFFER; + handle.id = kId; + handle.handle = base::SharedMemory::DuplicateHandle(shared_memory.handle()); + handle.offset = kOffset; + handle.stride = kStride; + + mus::mojom::GpuMemoryBufferHandlePtr gpu_handle = + mus::mojom::GpuMemoryBufferHandle::From<gfx::GpuMemoryBufferHandle>( + handle); + ASSERT_EQ(gpu_handle->type, mus::mojom::GpuMemoryBufferType::SHARED_MEMORY); + ASSERT_EQ(gpu_handle->id->id, 99); + ASSERT_EQ(gpu_handle->offset, kOffset); + ASSERT_EQ(gpu_handle->stride, kStride); + + handle = gpu_handle.To<gfx::GpuMemoryBufferHandle>(); + ASSERT_EQ(handle.type, gfx::SHARED_MEMORY_BUFFER); + ASSERT_EQ(handle.id, kId); + ASSERT_EQ(handle.offset, kOffset); + ASSERT_EQ(handle.stride, kStride); + + base::SharedMemory shared_memory1(handle.handle, true); + ASSERT_TRUE(shared_memory1.Map(1024)); +} diff --git a/chromium/components/mus/common/mojo_buffer_backing.cc b/chromium/components/mus/common/mojo_buffer_backing.cc new file mode 100644 index 00000000000..ba9da7fcb0d --- /dev/null +++ b/chromium/components/mus/common/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 "components/mus/common/mojo_buffer_backing.h" + +#include "base/logging.h" +#include "base/memory/ptr_util.h" + +namespace mus { + +MojoBufferBacking::MojoBufferBacking(mojo::ScopedSharedBufferMapping mapping, + size_t size) + : mapping_(std::move(mapping)), size_(size) {} + +MojoBufferBacking::~MojoBufferBacking() = default; + +// static +std::unique_ptr<gpu::BufferBacking> MojoBufferBacking::Create( + mojo::ScopedSharedBufferHandle handle, + size_t size) { + mojo::ScopedSharedBufferMapping mapping = handle->Map(size); + if (!mapping) + return nullptr; + return base::MakeUnique<MojoBufferBacking>(std::move(mapping), size); +} +void* MojoBufferBacking::GetMemory() const { + return mapping_.get(); +} +size_t MojoBufferBacking::GetSize() const { + return size_; +} + +} // namespace mus diff --git a/chromium/components/mus/common/mojo_buffer_backing.h b/chromium/components/mus/common/mojo_buffer_backing.h new file mode 100644 index 00000000000..3f6e33f721e --- /dev/null +++ b/chromium/components/mus/common/mojo_buffer_backing.h @@ -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. + +#ifndef COMPONENTS_MUS_COMMON_MOJO_BUFFER_BACKING_H_ +#define COMPONENTS_MUS_COMMON_MOJO_BUFFER_BACKING_H_ + +#include <stddef.h> + +#include <memory> + +#include "base/macros.h" +#include "components/mus/common/mus_common_export.h" +#include "gpu/command_buffer/common/buffer.h" +#include "mojo/public/cpp/system/core.h" + +namespace mus { + +class MUS_COMMON_EXPORT MojoBufferBacking : public gpu::BufferBacking { + public: + MojoBufferBacking(mojo::ScopedSharedBufferMapping mapping, size_t size); + ~MojoBufferBacking() override; + + static std::unique_ptr<gpu::BufferBacking> Create( + mojo::ScopedSharedBufferHandle handle, + size_t size); + + void* GetMemory() const override; + size_t GetSize() const override; + + private: + mojo::ScopedSharedBufferMapping mapping_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(MojoBufferBacking); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_MOJO_BUFFER_BACKING_H_ diff --git a/chromium/components/mus/common/mojo_gpu_memory_buffer.cc b/chromium/components/mus/common/mojo_gpu_memory_buffer.cc new file mode 100644 index 00000000000..00b046b068d --- /dev/null +++ b/chromium/components/mus/common/mojo_gpu_memory_buffer.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 "components/mus/common/mojo_gpu_memory_buffer.h" + +#include <stdint.h> + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/memory/shared_memory.h" +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "ui/gfx/buffer_format_util.h" + +namespace mus { + +MojoGpuMemoryBufferImpl::MojoGpuMemoryBufferImpl( + const gfx::Size& size, + gfx::BufferFormat format, + std::unique_ptr<base::SharedMemory> shared_memory) + : GpuMemoryBufferImpl(gfx::GenericSharedMemoryId(0), size, format), + shared_memory_(std::move(shared_memory)) {} + +// TODO(rjkroege): Support running a destructor callback as necessary. +MojoGpuMemoryBufferImpl::~MojoGpuMemoryBufferImpl() {} + +std::unique_ptr<gfx::GpuMemoryBuffer> MojoGpuMemoryBufferImpl::Create( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage) { + size_t bytes = gfx::BufferSizeForBufferFormat(size, format); + + mojo::ScopedSharedBufferHandle handle = + mojo::SharedBufferHandle::Create(bytes); + if (!handle.is_valid()) + return nullptr; + + base::SharedMemoryHandle platform_handle; + size_t shared_memory_size; + bool readonly; + MojoResult result = mojo::UnwrapSharedMemoryHandle( + std::move(handle), &platform_handle, &shared_memory_size, &readonly); + if (result != MOJO_RESULT_OK) + return nullptr; + DCHECK_EQ(shared_memory_size, bytes); + + auto shared_memory = + base::MakeUnique<base::SharedMemory>(platform_handle, readonly); + return base::WrapUnique<gfx::GpuMemoryBuffer>( + new MojoGpuMemoryBufferImpl(size, format, std::move(shared_memory))); +} + +MojoGpuMemoryBufferImpl* MojoGpuMemoryBufferImpl::FromClientBuffer( + ClientBuffer buffer) { + return reinterpret_cast<MojoGpuMemoryBufferImpl*>(buffer); +} + +const unsigned char* MojoGpuMemoryBufferImpl::GetMemory() const { + return static_cast<const unsigned char*>(shared_memory_->memory()); +} + +bool MojoGpuMemoryBufferImpl::Map() { + DCHECK(!mapped_); + if (!shared_memory_->Map(gfx::BufferSizeForBufferFormat(size_, format_))) + return false; + mapped_ = true; + return true; +} + +void* MojoGpuMemoryBufferImpl::memory(size_t plane) { + DCHECK(mapped_); + DCHECK_LT(plane, gfx::NumberOfPlanesForBufferFormat(format_)); + return reinterpret_cast<uint8_t*>(shared_memory_->memory()) + + gfx::BufferOffsetForBufferFormat(size_, format_, plane); +} + +void MojoGpuMemoryBufferImpl::Unmap() { + DCHECK(mapped_); + shared_memory_->Unmap(); + mapped_ = false; +} + +int MojoGpuMemoryBufferImpl::stride(size_t plane) const { + DCHECK_LT(plane, gfx::NumberOfPlanesForBufferFormat(format_)); + return base::checked_cast<int>(gfx::RowSizeForBufferFormat( + size_.width(), format_, static_cast<int>(plane))); +} + +gfx::GpuMemoryBufferHandle MojoGpuMemoryBufferImpl::GetHandle() const { + gfx::GpuMemoryBufferHandle handle; + handle.type = gfx::SHARED_MEMORY_BUFFER; + handle.handle = shared_memory_->handle(); + handle.offset = 0; + handle.stride = static_cast<int32_t>( + gfx::RowSizeForBufferFormat(size_.width(), format_, 0)); + + return handle; +} + +gfx::GpuMemoryBufferType MojoGpuMemoryBufferImpl::GetBufferType() const { + return gfx::SHARED_MEMORY_BUFFER; +} + +} // namespace mus diff --git a/chromium/components/mus/common/mojo_gpu_memory_buffer.h b/chromium/components/mus/common/mojo_gpu_memory_buffer.h new file mode 100644 index 00000000000..ffcbf049185 --- /dev/null +++ b/chromium/components/mus/common/mojo_gpu_memory_buffer.h @@ -0,0 +1,54 @@ +// 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 COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_H_ +#define COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_H_ + +#include <stddef.h> + +#include <memory> + +#include "base/macros.h" +#include "components/mus/common/gpu_memory_buffer_impl.h" +#include "components/mus/common/mus_common_export.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" + +namespace mus { + +class MUS_COMMON_EXPORT MojoGpuMemoryBufferImpl + : public mus::GpuMemoryBufferImpl { + public: + MojoGpuMemoryBufferImpl(const gfx::Size& size, + gfx::BufferFormat format, + std::unique_ptr<base::SharedMemory> shared_memory); + ~MojoGpuMemoryBufferImpl() override; + + static std::unique_ptr<gfx::GpuMemoryBuffer> Create(const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage); + + static MojoGpuMemoryBufferImpl* FromClientBuffer(ClientBuffer buffer); + + const unsigned char* GetMemory() const; + + // Overridden from gfx::GpuMemoryBuffer: + bool Map() override; + void* memory(size_t plane) override; + void Unmap() override; + int stride(size_t plane) const override; + gfx::GpuMemoryBufferHandle GetHandle() const override; + + // Overridden from gfx::GpuMemoryBufferImpl + gfx::GpuMemoryBufferType GetBufferType() const override; + + private: + std::unique_ptr<base::SharedMemory> shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(MojoGpuMemoryBufferImpl); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_H_ diff --git a/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.cc b/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.cc new file mode 100644 index 00000000000..51decafb326 --- /dev/null +++ b/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.cc @@ -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. + +#include "components/mus/common/mojo_gpu_memory_buffer_manager.h" + +#include "base/logging.h" +#include "components/mus/common/mojo_gpu_memory_buffer.h" + +namespace mus { + +MojoGpuMemoryBufferManager::MojoGpuMemoryBufferManager() {} + +MojoGpuMemoryBufferManager::~MojoGpuMemoryBufferManager() {} + +std::unique_ptr<gfx::GpuMemoryBuffer> +MojoGpuMemoryBufferManager::AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) { + return MojoGpuMemoryBufferImpl::Create(size, format, usage); +} + +std::unique_ptr<gfx::GpuMemoryBuffer> +MojoGpuMemoryBufferManager::CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) { + NOTIMPLEMENTED(); + return nullptr; +} + +gfx::GpuMemoryBuffer* +MojoGpuMemoryBufferManager::GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) { + return MojoGpuMemoryBufferImpl::FromClientBuffer(buffer); +} + +void MojoGpuMemoryBufferManager::SetDestructionSyncToken( + gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) { + NOTIMPLEMENTED(); +} + +} // namespace mus diff --git a/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.h b/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.h new file mode 100644 index 00000000000..e3ce95dfc97 --- /dev/null +++ b/chromium/components/mus/common/mojo_gpu_memory_buffer_manager.h @@ -0,0 +1,43 @@ +// 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 COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_MANAGER_H_ +#define COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_MANAGER_H_ + +#include <memory> + +#include "base/macros.h" +#include "components/mus/common/mus_common_export.h" +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" + +namespace mus { + +class MUS_COMMON_EXPORT MojoGpuMemoryBufferManager + : public gpu::GpuMemoryBufferManager { + public: + MojoGpuMemoryBufferManager(); + ~MojoGpuMemoryBufferManager() override; + + // Overridden from gpu::GpuMemoryBufferManager: + std::unique_ptr<gfx::GpuMemoryBuffer> AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) override; + std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) override; + gfx::GpuMemoryBuffer* GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) override; + void SetDestructionSyncToken(gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) override; + + private: + DISALLOW_COPY_AND_ASSIGN(MojoGpuMemoryBufferManager); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_MOJO_GPU_MEMORY_BUFFER_MANAGER_H_ diff --git a/chromium/components/mus/common/mus_common_export.h b/chromium/components/mus/common/mus_common_export.h new file mode 100644 index 00000000000..7e7526b6710 --- /dev/null +++ b/chromium/components/mus/common/mus_common_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_COMMON_MUS_COMMON_EXPORT_H_ +#define COMPONENTS_MUS_COMMON_MUS_COMMON_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MUS_COMMON_IMPLEMENTATION) +#define MUS_COMMON_EXPORT __declspec(dllexport) +#else +#define MUS_COMMON_EXPORT __declspec(dllimport) +#endif // defined(MUS_COMMON_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MUS_COMMON_IMPLEMENTATION) +#define MUS_COMMON_EXPORT __attribute__((visibility("default"))) +#else +#define MUS_COMMON_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MUS_COMMON_EXPORT +#endif + +#endif // COMPONENTS_MUS_COMMON_MUS_COMMON_EXPORT_H_ diff --git a/chromium/components/mus/common/mus_common_unittests_app_manifest.json b/chromium/components/mus/common/mus_common_unittests_app_manifest.json new file mode 100644 index 00000000000..5240ae9b6a9 --- /dev/null +++ b/chromium/components/mus/common/mus_common_unittests_app_manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 1, + "name": "mojo:mus_common_unittests_app", + "display_name": "Mus Common Unittests", + "capabilities": { + "required": { + "*": { "classes": [ "app" ] } + } + } +} diff --git a/chromium/components/mus/common/run_all_shelltests.cc b/chromium/components/mus/common/run_all_shelltests.cc new file mode 100644 index 00000000000..e795983ec58 --- /dev/null +++ b/chromium/components/mus/common/run_all_shelltests.cc @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/embedder.h" +#include "services/shell/background/background_shell_main.h" + +#if defined(USE_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#endif + +class MusGpuTestSuite : public base::TestSuite { + public: + MusGpuTestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {} + + private: + void Initialize() override { + base::TestSuite::Initialize(); +#if defined(USE_OZONE) + ui::OzonePlatform::InitializeForGPU(); +#endif + } +}; + +int MasterProcessMain(int argc, char** argv) { + MusGpuTestSuite test_suite(argc, argv); + mojo::edk::Init(); + return base::LaunchUnitTests( + argc, argv, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/chromium/components/mus/common/switches.cc b/chromium/components/mus/common/switches.cc new file mode 100644 index 00000000000..f9bead3ed54 --- /dev/null +++ b/chromium/components/mus/common/switches.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 "components/mus/common/switches.h" + +namespace mus { +namespace switches { + +// Use mojo GPU command buffer instead of Chrome GPU command buffer. +const char kUseMojoGpuCommandBufferInMus[] = + "use-mojo-gpu-command-buffer-in-mus"; + +// Initializes X11 in threaded mode, and sets the |override_redirect| flag when +// creating X11 windows. Also, exposes the WindowServerTest interface to clients +// when launched with this flag. +const char kUseTestConfig[] = "use-test-config"; + +} // namespace switches +} // namespace mus diff --git a/chromium/components/mus/common/switches.h b/chromium/components/mus/common/switches.h new file mode 100644 index 00000000000..492aa4c3113 --- /dev/null +++ b/chromium/components/mus/common/switches.h @@ -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. + +#ifndef COMPONENTS_MUS_COMMON_SWITCHES_H_ +#define COMPONENTS_MUS_COMMON_SWITCHES_H_ + +#include "components/mus/common/mus_common_export.h" + +namespace mus { +namespace switches { + +// All args in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char MUS_COMMON_EXPORT kUseMojoGpuCommandBufferInMus[]; +extern const char MUS_COMMON_EXPORT kUseTestConfig[]; + +} // namespace switches +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_SWITCHES_H_ diff --git a/chromium/components/mus/common/transient_window_utils.h b/chromium/components/mus/common/transient_window_utils.h new file mode 100644 index 00000000000..17eedc63a25 --- /dev/null +++ b/chromium/components/mus/common/transient_window_utils.h @@ -0,0 +1,126 @@ +// 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 COMPONENTS_MUS_COMMON_TRANSIENT_WINDOW_UTILS_H_ +#define COMPONENTS_MUS_COMMON_TRANSIENT_WINDOW_UTILS_H_ + +#include <stddef.h> + +#include <vector> + +#include "components/mus/public/interfaces/mus_constants.mojom.h" + +namespace mus { + +// Returns true if |window| has |ancestor| as a transient ancestor. A transient +// ancestor is found by following the transient parent chain of the window. +template <class T> +bool HasTransientAncestor(const T* window, const T* ancestor) { + const T* transient_parent = window->transient_parent(); + if (transient_parent == ancestor) + return true; + return transient_parent ? HasTransientAncestor(transient_parent, ancestor) + : false; +} + +// Populates |ancestors| with all transient ancestors of |window| that are +// siblings of |window|. Returns true if any ancestors were found, false if not. +template <class T> +bool GetAllTransientAncestors(T* window, std::vector<T*>* ancestors) { + T* parent = window->parent(); + for (; window; window = window->transient_parent()) { + if (window->parent() == parent) + ancestors->push_back(window); + } + return !ancestors->empty(); +} + +// Replaces |window1| and |window2| with their possible transient ancestors that +// are still siblings (have a common transient parent). |window1| and |window2| +// are not modified if such ancestors cannot be found. +template <class T> +void FindCommonTransientAncestor(T** window1, T** window2) { + DCHECK(window1); + DCHECK(window2); + DCHECK(*window1); + DCHECK(*window2); + // Assemble chains of ancestors of both windows. + std::vector<T*> ancestors1; + std::vector<T*> ancestors2; + if (!GetAllTransientAncestors(*window1, &ancestors1) || + !GetAllTransientAncestors(*window2, &ancestors2)) { + return; + } + // Walk the two chains backwards and look for the first difference. + auto it1 = ancestors1.rbegin(); + auto it2 = ancestors2.rbegin(); + for (; it1 != ancestors1.rend() && it2 != ancestors2.rend(); ++it1, ++it2) { + if (*it1 != *it2) { + *window1 = *it1; + *window2 = *it2; + break; + } + } +} + +template <class T> +bool AdjustStackingForTransientWindows(T** child, + T** target, + mojom::OrderDirection* direction, + T* stacking_target) { + if (stacking_target == *target) + return true; + + // For windows that have transient children stack the transient ancestors that + // are siblings. This prevents one transient group from being inserted in the + // middle of another. + FindCommonTransientAncestor(child, target); + + // When stacking above skip to the topmost transient descendant of the target. + if (*direction == mojom::OrderDirection::ABOVE && + !HasTransientAncestor(*child, *target)) { + const std::vector<T*>& siblings((*child)->parent()->children()); + size_t target_i = + std::find(siblings.begin(), siblings.end(), *target) - siblings.begin(); + while (target_i + 1 < siblings.size() && + HasTransientAncestor(siblings[target_i + 1], *target)) { + ++target_i; + } + *target = siblings[target_i]; + } + + return *child != *target; +} + +// Stacks transient descendants of |window| that are its siblings just above it. +// |GetStackingTarget| is a function that returns a marker associated with a +// Window that indicates the current Window being stacked. +// |Reorder| is a function that takes in two windows and orders the first +// relative to the second based on the provided OrderDirection. +template <class T> +void RestackTransientDescendants(T* window, + T** (*GetStackingTarget)(T*), + void (*Reorder)(T*, + T*, + mojom::OrderDirection)) { + T* parent = window->parent(); + if (!parent) + return; + + // stack any transient children that share the same parent to be in front of + // |window_|. the existing stacking order is preserved by iterating backwards + // and always stacking on top. + std::vector<T*> children(parent->children()); + for (auto it = children.rbegin(); it != children.rend(); ++it) { + if ((*it) != window && HasTransientAncestor(*it, window)) { + T* old_stacking_target = *GetStackingTarget(*it); + *GetStackingTarget(*it) = window; + Reorder(*it, window, mojom::OrderDirection::ABOVE); + *GetStackingTarget(*it) = old_stacking_target; + } + } +} +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_TRANSIENT_WINDOW_UTILS_H_ diff --git a/chromium/components/mus/common/types.h b/chromium/components/mus/common/types.h new file mode 100644 index 00000000000..93b96683e6b --- /dev/null +++ b/chromium/components/mus/common/types.h @@ -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. + +#ifndef COMPONENTS_MUS_COMMON_TYPES_H_ +#define COMPONENTS_MUS_COMMON_TYPES_H_ + +#include <stdint.h> + +// Typedefs for the transport types. These typedefs match that of the mojom +// file, see it for specifics. + +namespace mus { + +// Used to identify windows and change ids. +typedef uint32_t Id; + +// Used to identify a client as well as a client-specific window id. For +// example, the Id for a window consists of the ClientSpecificId of the client +// and the ClientSpecificId of the window. +typedef uint16_t ClientSpecificId; + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_TYPES_H_ diff --git a/chromium/components/mus/common/util.h b/chromium/components/mus/common/util.h new file mode 100644 index 00000000000..daa59efc6b1 --- /dev/null +++ b/chromium/components/mus/common/util.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 COMPONENTS_MUS_COMMON_UTIL_H_ +#define COMPONENTS_MUS_COMMON_UTIL_H_ + +#include <stdint.h> + +#include "components/mus/common/types.h" + +// TODO(beng): #$*&@#(@ MacOSX SDK! +#if defined(HiWord) +#undef HiWord +#endif +#if defined(LoWord) +#undef LoWord +#endif + +namespace mus { + +inline uint16_t HiWord(uint32_t id) { + return static_cast<uint16_t>((id >> 16) & 0xFFFF); +} + +inline uint16_t LoWord(uint32_t id) { + return static_cast<uint16_t>(id & 0xFFFF); +} + +} // namespace mus + +#endif // COMPONENTS_MUS_COMMON_UTIL_H_ diff --git a/chromium/components/mus/demo/DEPS b/chromium/components/mus/demo/DEPS new file mode 100644 index 00000000000..ce521506ba2 --- /dev/null +++ b/chromium/components/mus/demo/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+components/bitmap_uploader", +] + diff --git a/chromium/components/mus/demo/OWNERS b/chromium/components/mus/demo/OWNERS new file mode 100644 index 00000000000..47351aafac9 --- /dev/null +++ b/chromium/components/mus/demo/OWNERS @@ -0,0 +1,2 @@ +kylechar@chromium.org +rjkroege@chromium.org diff --git a/chromium/components/mus/demo/main.cc b/chromium/components/mus/demo/main.cc new file mode 100644 index 00000000000..62be1b7d81d --- /dev/null +++ b/chromium/components/mus/demo/main.cc @@ -0,0 +1,13 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/demo/mus_demo.h" +#include "mojo/public/c/system/main.h" +#include "services/shell/public/cpp/application_runner.h" + +MojoResult MojoMain(MojoHandle shell_handle) { + shell::ApplicationRunner runner(new mus_demo::MusDemo); + runner.set_message_loop_type(base::MessageLoop::TYPE_UI); + return runner.Run(shell_handle); +} diff --git a/chromium/components/mus/demo/manifest.json b/chromium/components/mus/demo/manifest.json new file mode 100644 index 00000000000..7971b79d238 --- /dev/null +++ b/chromium/components/mus/demo/manifest.json @@ -0,0 +1,14 @@ +{ + "manifest_version": 1, + "name": "mojo:mus_demo", + "display_name": "MUS Demo", + "capabilities": { + "required": { + "mojo:mus": { + "interfaces": [ "mus::mojom::WindowManagerWindowTreeFactory"], + "classes": [ "app" ] + } + } + } +} + diff --git a/chromium/components/mus/demo/mus_demo.cc b/chromium/components/mus/demo/mus_demo.cc new file mode 100644 index 00000000000..0853f5ab48c --- /dev/null +++ b/chromium/components/mus/demo/mus_demo.cc @@ -0,0 +1,179 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/demo/mus_demo.h" + +#include "base/time/time.h" +#include "components/bitmap_uploader/bitmap_uploader.h" +#include "components/mus/common/gpu_service.h" +#include "components/mus/public/cpp/window.h" +#include "components/mus/public/cpp/window_tree_client.h" +#include "services/shell/public/cpp/connector.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkRect.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus_demo { + +namespace { + +// Milliseconds between frames. +const int64_t kFrameDelay = 33; + +// Size of square in pixels to draw. +const int kSquareSize = 300; + +const SkColor kBgColor = SK_ColorRED; +const SkColor kFgColor = SK_ColorYELLOW; + +void DrawSquare(const gfx::Rect& bounds, double angle, SkCanvas* canvas) { + // Create SkRect to draw centered inside the bounds. + gfx::Point top_left = bounds.CenterPoint(); + top_left.Offset(-kSquareSize / 2, -kSquareSize / 2); + SkRect rect = + SkRect::MakeXYWH(top_left.x(), top_left.y(), kSquareSize, kSquareSize); + + // Set SkPaint to fill solid color. + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(kFgColor); + + // Rotate the canvas. + const gfx::Size canvas_size = bounds.size(); + if (angle != 0.0) { + canvas->translate(SkFloatToScalar(canvas_size.width() * 0.5f), + SkFloatToScalar(canvas_size.height() * 0.5f)); + canvas->rotate(angle); + canvas->translate(-SkFloatToScalar(canvas_size.width() * 0.5f), + -SkFloatToScalar(canvas_size.height() * 0.5f)); + } + + canvas->drawRect(rect, paint); +} + +} // namespace + +MusDemo::MusDemo() {} + +MusDemo::~MusDemo() { + delete window_tree_client_; +} + +void MusDemo::Initialize(shell::Connector* connector, + const shell::Identity& identity, + uint32_t id) { + connector_ = connector; + mus::GpuService::Initialize(connector_); + window_tree_client_ = new mus::WindowTreeClient(this, this, nullptr); + window_tree_client_->ConnectAsWindowManager(connector); +} + +bool MusDemo::AcceptConnection(shell::Connection* connection) { + return true; +} + +void MusDemo::OnEmbed(mus::Window* window) { + // Not called for the WindowManager. + NOTREACHED(); +} + +void MusDemo::OnWindowTreeClientDestroyed(mus::WindowTreeClient* client) { + window_tree_client_ = nullptr; + timer_.Stop(); +} + +void MusDemo::OnEventObserved(const ui::Event& event, mus::Window* target) {} + +void MusDemo::SetWindowManagerClient(mus::WindowManagerClient* client) {} + +bool MusDemo::OnWmSetBounds(mus::Window* window, gfx::Rect* bounds) { + return true; +} + +bool MusDemo::OnWmSetProperty(mus::Window* window, + const std::string& name, + std::unique_ptr<std::vector<uint8_t>>* new_data) { + return true; +} + +mus::Window* MusDemo::OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) { + return nullptr; +} + +void MusDemo::OnWmClientJankinessChanged( + const std::set<mus::Window*>& client_windows, + bool janky) { + // Don't care +} + +void MusDemo::OnWmNewDisplay(mus::Window* window, + const display::Display& display) { + DCHECK(!window_); // Only support one display. + window_ = window; + + // Initialize bitmap uploader for sending frames to MUS. + uploader_.reset(new bitmap_uploader::BitmapUploader(window_)); + uploader_->Init(connector_); + + // Draw initial frame and start the timer to regularly draw frames. + DrawFrame(); + timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kFrameDelay), + base::Bind(&MusDemo::DrawFrame, base::Unretained(this))); +} + +void MusDemo::OnAccelerator(uint32_t id, const ui::Event& event) { + // Don't care +} + +void MusDemo::AllocBitmap() { + const gfx::Rect bounds = window_->GetBoundsInRoot(); + + // Allocate bitmap the same size as the window for drawing. + bitmap_.reset(); + SkImageInfo image_info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), + kPremul_SkAlphaType); + bitmap_.allocPixels(image_info); +} + +void MusDemo::DrawFrame() { + angle_ += 2.0; + if (angle_ >= 360.0) + angle_ = 0.0; + + const gfx::Rect bounds = window_->GetBoundsInRoot(); + + // Check that bitmap and window sizes match, otherwise reallocate bitmap. + const SkImageInfo info = bitmap_.info(); + if (info.width() != bounds.width() || info.height() != bounds.height()) { + AllocBitmap(); + } + + // Draw the rotated square on background in bitmap. + SkCanvas canvas(bitmap_); + canvas.clear(kBgColor); + // TODO(kylechar): Add GL drawing instead of software rasterization in future. + DrawSquare(bounds, angle_, &canvas); + canvas.flush(); + + // Copy pixels data into vector that will be passed to BitmapUploader. + // TODO(rjkroege): Make a 1/0-copy bitmap uploader for the contents of a + // SkBitmap. + bitmap_.lockPixels(); + const unsigned char* addr = + static_cast<const unsigned char*>(bitmap_.getPixels()); + const int bytes = bounds.width() * bounds.height() * 4; + std::unique_ptr<std::vector<unsigned char>> data( + new std::vector<unsigned char>(addr, addr + bytes)); + bitmap_.unlockPixels(); + + // Send frame to MUS via BitmapUploader. + uploader_->SetBitmap(bounds.width(), bounds.height(), std::move(data), + bitmap_uploader::BitmapUploader::BGRA); +} + +} // namespace mus_demo diff --git a/chromium/components/mus/demo/mus_demo.h b/chromium/components/mus/demo/mus_demo.h new file mode 100644 index 00000000000..dbb62647294 --- /dev/null +++ b/chromium/components/mus/demo/mus_demo.h @@ -0,0 +1,92 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_DEMO_MUS_DEMO_H_ +#define COMPONENTS_MUS_DEMO_MUS_DEMO_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/timer/timer.h" +#include "components/mus/public/cpp/window_manager_delegate.h" +#include "components/mus/public/cpp/window_tree_client_delegate.h" +#include "services/shell/public/cpp/shell_client.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace bitmap_uploader { +class BitmapUploader; +} + +namespace mus_demo { + +// A simple MUS Demo mojo app. This app connects to the mojo:mus, creates a new +// window and draws a spinning square in the center of the window. Provides a +// simple way to demonstrate that the graphic stack works as intended. +class MusDemo : public shell::ShellClient, + public mus::WindowTreeClientDelegate, + public mus::WindowManagerDelegate { + public: + MusDemo(); + ~MusDemo() override; + + private: + // shell::ShellClient: + void Initialize(shell::Connector* connector, + const shell::Identity& identity, + uint32_t id) override; + bool AcceptConnection(shell::Connection* connection) override; + + // WindowTreeClientDelegate: + void OnEmbed(mus::Window* root) override; + void OnWindowTreeClientDestroyed(mus::WindowTreeClient* client) override; + void OnEventObserved(const ui::Event& event, mus::Window* target) override; + + // WindowManagerDelegate: + void SetWindowManagerClient(mus::WindowManagerClient* client) override; + bool OnWmSetBounds(mus::Window* window, gfx::Rect* bounds) override; + bool OnWmSetProperty( + mus::Window* window, + const std::string& name, + std::unique_ptr<std::vector<uint8_t>>* new_data) override; + mus::Window* OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) override; + void OnWmClientJankinessChanged(const std::set<mus::Window*>& client_windows, + bool janky) override; + void OnWmNewDisplay(mus::Window* window, + const display::Display& display) override; + void OnAccelerator(uint32_t id, const ui::Event& event) override; + + // Allocate a bitmap the same size as the window to draw into. + void AllocBitmap(); + + // Draws one frame, incrementing the rotation angle. + void DrawFrame(); + + shell::Connector* connector_ = nullptr; + + mus::Window* window_ = nullptr; + mus::WindowTreeClient* window_tree_client_ = nullptr; + + // Used to send frames to mus. + std::unique_ptr<bitmap_uploader::BitmapUploader> uploader_; + + // Bitmap that is the same size as our client window area. + SkBitmap bitmap_; + + // Timer for calling DrawFrame(). + base::RepeatingTimer timer_; + + // Current rotation angle for drawing. + double angle_ = 0.0; + + DISALLOW_COPY_AND_ASSIGN(MusDemo); +}; + +} // namespace mus_demo + +#endif // COMPONENTS_MUS_DEMO_MUS_DEMO_H_ diff --git a/chromium/components/mus/gles2/DEPS b/chromium/components/mus/gles2/DEPS new file mode 100644 index 00000000000..2ad979f2443 --- /dev/null +++ b/chromium/components/mus/gles2/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+cc", + "+components/gpu", + "+gpu", + "+mojo/converters", + "+mojo/public", + "+ui", +] diff --git a/chromium/components/mus/gles2/OWNERS b/chromium/components/mus/gles2/OWNERS new file mode 100644 index 00000000000..1f1af6bcc54 --- /dev/null +++ b/chromium/components/mus/gles2/OWNERS @@ -0,0 +1,2 @@ +fsamuel@chromium.org +rjkroege@chromium.org diff --git a/chromium/components/mus/gles2/command_buffer_driver.cc b/chromium/components/mus/gles2/command_buffer_driver.cc new file mode 100644 index 00000000000..1c35904e9f6 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_driver.cc @@ -0,0 +1,568 @@ +// 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 "components/mus/gles2/command_buffer_driver.h" + +#include <stddef.h> +#include <utility> + +#include "base/bind.h" +#include "base/memory/shared_memory.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "components/mus/common/mojo_buffer_backing.h" +#include "components/mus/gles2/gl_surface_adapter.h" +#include "components/mus/gles2/gpu_memory_tracker.h" +#include "components/mus/gles2/gpu_state.h" +#include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "gpu/command_buffer/service/command_buffer_service.h" +#include "gpu/command_buffer/service/command_executor.h" +#include "gpu/command_buffer/service/context_group.h" +#include "gpu/command_buffer/service/gles2_cmd_decoder.h" +#include "gpu/command_buffer/service/image_manager.h" +#include "gpu/command_buffer/service/mailbox_manager.h" +#include "gpu/command_buffer/service/query_manager.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "gpu/command_buffer/service/transfer_buffer_manager.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/vsync_provider.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_image_shared_memory.h" +#include "ui/gl/gl_surface.h" +#include "ui/gl/init/gl_factory.h" + +#if defined(USE_OZONE) +#include "ui/gl/gl_image_ozone_native_pixmap.h" +#endif + +namespace mus { + +namespace { + +// The first time polling a fence, delay some extra time to allow other +// stubs to process some work, or else the timing of the fences could +// allow a pattern of alternating fast and slow frames to occur. +const int64_t kHandleMoreWorkPeriodMs = 2; +const int64_t kHandleMoreWorkPeriodBusyMs = 1; + +// Prevents idle work from being starved. +const int64_t kMaxTimeSinceIdleMs = 10; + +} // namespace + +CommandBufferDriver::Client::~Client() {} + +CommandBufferDriver::CommandBufferDriver( + gpu::CommandBufferNamespace command_buffer_namespace, + gpu::CommandBufferId command_buffer_id, + gfx::AcceleratedWidget widget, + scoped_refptr<GpuState> gpu_state) + : command_buffer_namespace_(command_buffer_namespace), + command_buffer_id_(command_buffer_id), + widget_(widget), + client_(nullptr), + gpu_state_(gpu_state), + previous_processed_num_(0), + weak_factory_(this) { + DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), + gpu_state_->command_buffer_task_runner()->task_runner()); +} + +CommandBufferDriver::~CommandBufferDriver() { + DCHECK(CalledOnValidThread()); + DestroyDecoder(); +} + +bool CommandBufferDriver::Initialize( + mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs) { + DCHECK(CalledOnValidThread()); + gpu::gles2::ContextCreationAttribHelper attrib_helper; + if (!attrib_helper.Parse(attribs.storage())) + return false; + // TODO(piman): attribs can't currently represent gpu_preference. + + const bool offscreen = widget_ == gfx::kNullAcceleratedWidget; + if (offscreen) { + surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size(0, 0)); + } else { +#if defined(USE_OZONE) + scoped_refptr<gl::GLSurface> underlying_surface = + gl::init::CreateSurfacelessViewGLSurface(widget_); + if (!underlying_surface) + underlying_surface = gl::init::CreateViewGLSurface(widget_); +#else + scoped_refptr<gl::GLSurface> underlying_surface = + gl::init::CreateViewGLSurface(widget_); +#endif + scoped_refptr<GLSurfaceAdapterMus> surface_adapter = + new GLSurfaceAdapterMus(underlying_surface); + surface_adapter->SetGpuCompletedSwapBuffersCallback( + base::Bind(&CommandBufferDriver::OnGpuCompletedSwapBuffers, + weak_factory_.GetWeakPtr())); + surface_ = surface_adapter; + + gfx::VSyncProvider* vsync_provider = + surface_ ? surface_->GetVSyncProvider() : nullptr; + if (vsync_provider) { + vsync_provider->GetVSyncParameters( + base::Bind(&CommandBufferDriver::OnUpdateVSyncParameters, + weak_factory_.GetWeakPtr())); + } + } + + if (!surface_.get()) + return false; + + // TODO(piman): virtual contexts. + context_ = gl::init::CreateGLContext( + gpu_state_->share_group(), surface_.get(), attrib_helper.gpu_preference); + 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. + const bool bind_generates_resource = attrib_helper.bind_generates_resource; + scoped_refptr<gpu::gles2::FeatureInfo> feature_info = + new gpu::gles2::FeatureInfo(gpu_state_->gpu_driver_bug_workarounds()); + // TODO(erikchen): The ContextGroup needs a reference to the + // GpuMemoryBufferManager. + scoped_refptr<gpu::gles2::ContextGroup> context_group = + new gpu::gles2::ContextGroup( + gpu_state_->gpu_preferences(), gpu_state_->mailbox_manager(), + new GpuMemoryTracker, + new gpu::gles2::ShaderTranslatorCache(gpu_state_->gpu_preferences()), + new gpu::gles2::FramebufferCompletenessCache, feature_info, + bind_generates_resource, nullptr); + + command_buffer_.reset( + new gpu::CommandBufferService(context_group->transfer_buffer_manager())); + + decoder_.reset(::gpu::gles2::GLES2Decoder::Create(context_group.get())); + executor_.reset(new gpu::CommandExecutor(command_buffer_.get(), + decoder_.get(), decoder_.get())); + sync_point_order_data_ = gpu::SyncPointOrderData::Create(); + sync_point_client_ = gpu_state_->sync_point_manager()->CreateSyncPointClient( + sync_point_order_data_, GetNamespaceID(), command_buffer_id_); + decoder_->set_engine(executor_.get()); + decoder_->SetFenceSyncReleaseCallback(base::Bind( + &CommandBufferDriver::OnFenceSyncRelease, base::Unretained(this))); + decoder_->SetWaitFenceSyncCallback(base::Bind( + &CommandBufferDriver::OnWaitFenceSync, base::Unretained(this))); + decoder_->SetDescheduleUntilFinishedCallback(base::Bind( + &CommandBufferDriver::OnDescheduleUntilFinished, base::Unretained(this))); + decoder_->SetRescheduleAfterFinishedCallback(base::Bind( + &CommandBufferDriver::OnRescheduleAfterFinished, base::Unretained(this))); + + gpu::gles2::DisallowedFeatures disallowed_features; + + if (!decoder_->Initialize(surface_, context_, offscreen, disallowed_features, + attrib_helper)) + return false; + + command_buffer_->SetPutOffsetChangeCallback(base::Bind( + &gpu::CommandExecutor::PutChanged, base::Unretained(executor_.get()))); + command_buffer_->SetGetBufferChangeCallback(base::Bind( + &gpu::CommandExecutor::SetGetBuffer, base::Unretained(executor_.get()))); + command_buffer_->SetParseErrorCallback( + base::Bind(&CommandBufferDriver::OnParseError, base::Unretained(this))); + + // TODO(piman): other callbacks + + const size_t kSize = sizeof(gpu::CommandBufferSharedState); + std::unique_ptr<gpu::BufferBacking> backing( + MojoBufferBacking::Create(std::move(shared_state), kSize)); + if (!backing) + return false; + + command_buffer_->SetSharedStateBuffer(std::move(backing)); + gpu_state_->driver_manager()->AddDriver(this); + return true; +} + +void CommandBufferDriver::SetGetBuffer(int32_t buffer) { + DCHECK(CalledOnValidThread()); + command_buffer_->SetGetBuffer(buffer); +} + +void CommandBufferDriver::Flush(int32_t put_offset) { + DCHECK(CalledOnValidThread()); + if (!MakeCurrent()) + return; + + command_buffer_->Flush(put_offset); + ProcessPendingAndIdleWork(); +} + +void CommandBufferDriver::RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + DCHECK(CalledOnValidThread()); + // Take ownership of the memory and map it into this process. + // This validates the size. + std::unique_ptr<gpu::BufferBacking> backing( + MojoBufferBacking::Create(std::move(transfer_buffer), size)); + if (!backing) { + DVLOG(0) << "Failed to map shared memory."; + return; + } + command_buffer_->RegisterTransferBuffer(id, std::move(backing)); +} + +void CommandBufferDriver::DestroyTransferBuffer(int32_t id) { + DCHECK(CalledOnValidThread()); + command_buffer_->DestroyTransferBuffer(id); +} + +void CommandBufferDriver::CreateImage(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format) { + DCHECK(CalledOnValidThread()); + if (!MakeCurrent()) + return; + + gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager(); + if (image_manager->LookupImage(id)) { + LOG(ERROR) << "Image already exists with same ID."; + return; + } + + gfx::BufferFormat gpu_format = static_cast<gfx::BufferFormat>(format); + if (!gpu::IsGpuMemoryBufferFormatSupported(gpu_format, + decoder_->GetCapabilities())) { + LOG(ERROR) << "Format is not supported."; + return; + } + + if (!gpu::IsImageSizeValidForGpuMemoryBufferFormat(size, gpu_format)) { + LOG(ERROR) << "Invalid image size for format."; + return; + } + + if (!gpu::IsImageFormatCompatibleWithGpuMemoryBufferFormat(internal_format, + gpu_format)) { + LOG(ERROR) << "Incompatible image format."; + return; + } + + if (type != gfx::SHARED_MEMORY_BUFFER) { + NOTIMPLEMENTED(); + return; + } + + base::PlatformFile platform_file; + MojoResult unwrap_result = mojo::UnwrapPlatformFile(std::move(memory_handle), + &platform_file); + if (unwrap_result != MOJO_RESULT_OK) { + NOTREACHED(); + return; + } + +#if defined(OS_WIN) + base::SharedMemoryHandle handle(platform_file, base::GetCurrentProcId()); +#else + base::FileDescriptor handle(platform_file, false); +#endif + + scoped_refptr<gl::GLImageSharedMemory> image = + new gl::GLImageSharedMemory(size, internal_format); + // TODO(jam): also need a mojo enum for this enum + if (!image->Initialize( + handle, gfx::GpuMemoryBufferId(id), gpu_format, 0, + gfx::RowSizeForBufferFormat(size.width(), gpu_format, 0))) { + NOTREACHED(); + return; + } + + image_manager->AddImage(image.get(), id); +} + +// TODO(rjkroege): It is conceivable that this code belongs in +// ozone_gpu_memory_buffer.cc +void CommandBufferDriver::CreateImageNativeOzone(int32_t id, + int32_t type, + gfx::Size size, + gfx::BufferFormat format, + uint32_t internal_format, + ui::NativePixmap* pixmap) { +#if defined(USE_OZONE) + gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager(); + if (image_manager->LookupImage(id)) { + LOG(ERROR) << "Image already exists with same ID."; + return; + } + + scoped_refptr<gl::GLImageOzoneNativePixmap> image = + new gl::GLImageOzoneNativePixmap(size, internal_format); + if (!image->Initialize(pixmap, format)) { + NOTREACHED(); + return; + } + + image_manager->AddImage(image.get(), id); +#endif +} + +void CommandBufferDriver::DestroyImage(int32_t id) { + DCHECK(CalledOnValidThread()); + gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager(); + if (!image_manager->LookupImage(id)) { + LOG(ERROR) << "Image with ID doesn't exist."; + return; + } + if (!MakeCurrent()) + return; + image_manager->RemoveImage(id); +} + +bool CommandBufferDriver::IsScheduled() const { + DCHECK(CalledOnValidThread()); + DCHECK(executor_); + return executor_->scheduled(); +} + +bool CommandBufferDriver::HasUnprocessedCommands() const { + DCHECK(CalledOnValidThread()); + if (command_buffer_) { + gpu::CommandBuffer::State state = GetLastState(); + return command_buffer_->GetPutOffset() != state.get_offset && + !gpu::error::IsError(state.error); + } + return false; +} + +gpu::Capabilities CommandBufferDriver::GetCapabilities() const { + DCHECK(CalledOnValidThread()); + return decoder_->GetCapabilities(); +} + +gpu::CommandBuffer::State CommandBufferDriver::GetLastState() const { + DCHECK(CalledOnValidThread()); + return command_buffer_->GetLastState(); +} + +uint32_t CommandBufferDriver::GetUnprocessedOrderNum() const { + DCHECK(CalledOnValidThread()); + return sync_point_order_data_->unprocessed_order_num(); +} + +uint32_t CommandBufferDriver::GetProcessedOrderNum() const { + DCHECK(CalledOnValidThread()); + return sync_point_order_data_->processed_order_num(); +} + +bool CommandBufferDriver::MakeCurrent() { + DCHECK(CalledOnValidThread()); + if (!decoder_) + return false; + if (decoder_->MakeCurrent()) + return true; + DLOG(ERROR) << "Context lost because MakeCurrent failed."; + gpu::error::ContextLostReason reason = + static_cast<gpu::error::ContextLostReason>( + decoder_->GetContextLostReason()); + command_buffer_->SetContextLostReason(reason); + command_buffer_->SetParseError(gpu::error::kLostContext); + OnContextLost(reason); + return false; +} + +void CommandBufferDriver::ProcessPendingAndIdleWork() { + DCHECK(CalledOnValidThread()); + executor_->ProcessPendingQueries(); + ScheduleDelayedWork( + base::TimeDelta::FromMilliseconds(kHandleMoreWorkPeriodMs)); +} + +void CommandBufferDriver::ScheduleDelayedWork(base::TimeDelta delay) { + DCHECK(CalledOnValidThread()); + const bool has_more_work = + executor_->HasPendingQueries() || executor_->HasMoreIdleWork(); + if (!has_more_work) { + last_idle_time_ = base::TimeTicks(); + return; + } + + const base::TimeTicks current_time = base::TimeTicks::Now(); + // |process_delayed_work_time_| is set if processing of delayed work is + // already scheduled. Just update the time if already scheduled. + if (!process_delayed_work_time_.is_null()) { + process_delayed_work_time_ = current_time + delay; + return; + } + + // Idle when no messages are processed between now and when PollWork is + // called. + previous_processed_num_ = + gpu_state_->driver_manager()->GetProcessedOrderNum(); + + if (last_idle_time_.is_null()) + last_idle_time_ = current_time; + + // scheduled() returns true after passing all unschedule fences and this is + // when we can start performing idle work. Idle work is done synchronously + // so we can set delay to 0 and instead poll for more work at the rate idle + // work is performed. This also ensures that idle work is done as + // efficiently as possible without any unnecessary delays. + if (executor_->scheduled() && executor_->HasMoreIdleWork()) + delay = base::TimeDelta(); + + process_delayed_work_time_ = current_time + delay; + gpu_state_->command_buffer_task_runner()->task_runner()->PostDelayedTask( + FROM_HERE, + base::Bind(&CommandBufferDriver::PollWork, weak_factory_.GetWeakPtr()), + delay); +} + +void CommandBufferDriver::PollWork() { + DCHECK(CalledOnValidThread()); + // Post another delayed task if we have not yet reached the time at which + // we should process delayed work. + base::TimeTicks current_time = base::TimeTicks::Now(); + DCHECK(!process_delayed_work_time_.is_null()); + if (process_delayed_work_time_ > current_time) { + gpu_state_->command_buffer_task_runner()->task_runner()->PostDelayedTask( + FROM_HERE, + base::Bind(&CommandBufferDriver::PollWork, weak_factory_.GetWeakPtr()), + process_delayed_work_time_ - current_time); + return; + } + process_delayed_work_time_ = base::TimeTicks(); + PerformWork(); +} + +void CommandBufferDriver::PerformWork() { + DCHECK(CalledOnValidThread()); + if (!MakeCurrent()) + return; + + if (executor_) { + const uint32_t current_unprocessed_num = + gpu_state_->driver_manager()->GetUnprocessedOrderNum(); + // We're idle when no messages were processed or scheduled. + bool is_idle = (previous_processed_num_ == current_unprocessed_num); + if (!is_idle && !last_idle_time_.is_null()) { + base::TimeDelta time_since_idle = + base::TimeTicks::Now() - last_idle_time_; + base::TimeDelta max_time_since_idle = + base::TimeDelta::FromMilliseconds(kMaxTimeSinceIdleMs); + // Force idle when it's been too long since last time we were idle. + if (time_since_idle > max_time_since_idle) + is_idle = true; + } + + if (is_idle) { + last_idle_time_ = base::TimeTicks::Now(); + executor_->PerformIdleWork(); + } + executor_->ProcessPendingQueries(); + } + + ScheduleDelayedWork( + base::TimeDelta::FromMilliseconds(kHandleMoreWorkPeriodBusyMs)); +} + +void CommandBufferDriver::DestroyDecoder() { + DCHECK(CalledOnValidThread()); + if (decoder_) { + gpu_state_->driver_manager()->RemoveDriver(this); + bool have_context = decoder_->MakeCurrent(); + decoder_->Destroy(have_context); + decoder_.reset(); + } +} + +void CommandBufferDriver::OnUpdateVSyncParameters( + const base::TimeTicks timebase, + const base::TimeDelta interval) { + DCHECK(CalledOnValidThread()); + if (client_) + client_->UpdateVSyncParameters(timebase, interval); +} + +void CommandBufferDriver::OnFenceSyncRelease(uint64_t release) { + DCHECK(CalledOnValidThread()); + if (!sync_point_client_->client_state()->IsFenceSyncReleased(release)) + sync_point_client_->ReleaseFenceSync(release); +} + +bool CommandBufferDriver::OnWaitFenceSync( + gpu::CommandBufferNamespace namespace_id, + gpu::CommandBufferId command_buffer_id, + uint64_t release) { + DCHECK(CalledOnValidThread()); + DCHECK(IsScheduled()); + gpu::SyncPointManager* sync_point_manager = gpu_state_->sync_point_manager(); + DCHECK(sync_point_manager); + + scoped_refptr<gpu::SyncPointClientState> release_state = + sync_point_manager->GetSyncPointClientState(namespace_id, + command_buffer_id); + + if (!release_state) + return true; + + executor_->SetScheduled(false); + sync_point_client_->Wait(release_state.get(), release, + base::Bind(&gpu::CommandExecutor::SetScheduled, + executor_->AsWeakPtr(), true)); + return executor_->scheduled(); +} + +void CommandBufferDriver::OnDescheduleUntilFinished() { + DCHECK(CalledOnValidThread()); + DCHECK(IsScheduled()); + DCHECK(executor_->HasMoreIdleWork()); + + executor_->SetScheduled(false); +} + +void CommandBufferDriver::OnRescheduleAfterFinished() { + DCHECK(CalledOnValidThread()); + DCHECK(!executor_->scheduled()); + + executor_->SetScheduled(true); +} + +void CommandBufferDriver::OnParseError() { + DCHECK(CalledOnValidThread()); + gpu::CommandBuffer::State state = GetLastState(); + OnContextLost(state.context_lost_reason); +} + +void CommandBufferDriver::OnContextLost(uint32_t reason) { + DCHECK(CalledOnValidThread()); + if (client_) + client_->DidLoseContext(reason); +} + +void CommandBufferDriver::SignalQuery(uint32_t query_id, + const base::Closure& callback) { + DCHECK(CalledOnValidThread()); + + gpu::gles2::QueryManager* query_manager = decoder_->GetQueryManager(); + gpu::gles2::QueryManager::Query* query = query_manager->GetQuery(query_id); + if (query) + query->AddCallback(callback); + else + callback.Run(); +} + +void CommandBufferDriver::OnGpuCompletedSwapBuffers(gfx::SwapResult result) { + DCHECK(CalledOnValidThread()); + if (client_) { + client_->OnGpuCompletedSwapBuffers(result); + } +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/command_buffer_driver.h b/chromium/components/mus/gles2/command_buffer_driver.h new file mode 100644 index 00000000000..bb08ee255fe --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_driver.h @@ -0,0 +1,173 @@ +// 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 COMPONENTS_MUS_GLES2_COMMAND_BUFFER_DRIVER_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_DRIVER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" +#include "gpu/command_buffer/common/capabilities.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/command_buffer_id.h" +#include "gpu/command_buffer/common/constants.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/system/buffer.h" +#include "ui/gfx/buffer_types.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/swap_result.h" + +namespace gl { +class GLContext; +class GLSurface; +} + +namespace gpu { +class CommandBufferService; +class CommandExecutor; +class SyncPointClient; +class SyncPointOrderData; + +namespace gles2 { +class GLES2Decoder; +} // namespace gles2 + +} // namespace gpu + +namespace ui { +class NativePixmap; +} + +namespace mus { + +class GpuState; + +// This class receives CommandBuffer messages on the same thread as the native +// viewport. +class CommandBufferDriver : base::NonThreadSafe { + public: + class Client { + public: + virtual ~Client(); + virtual void DidLoseContext(uint32_t reason) = 0; + virtual void UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) = 0; + virtual void OnGpuCompletedSwapBuffers(gfx::SwapResult result) = 0; + }; + CommandBufferDriver(gpu::CommandBufferNamespace command_buffer_namespace, + gpu::CommandBufferId command_buffer_id, + gfx::AcceleratedWidget widget, + scoped_refptr<GpuState> gpu_state); + ~CommandBufferDriver(); + + // The class owning the CommandBufferDriver instance (e.g. CommandBufferLocal) + // is itself the Client implementation so CommandBufferDriver does not own the + // client. + void set_client(Client* client) { client_ = client; } + + bool Initialize(mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs); + void SetGetBuffer(int32_t buffer); + void Flush(int32_t put_offset); + void RegisterTransferBuffer(int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size); + void DestroyTransferBuffer(int32_t id); + void CreateImage(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format); + void CreateImageNativeOzone(int32_t id, + int32_t type, + gfx::Size size, + gfx::BufferFormat format, + uint32_t internal_format, + ui::NativePixmap* pixmap); + void DestroyImage(int32_t id); + bool IsScheduled() const; + bool HasUnprocessedCommands() const; + gpu::Capabilities GetCapabilities() const; + gpu::CommandBuffer::State GetLastState() const; + gpu::CommandBufferNamespace GetNamespaceID() const { + return command_buffer_namespace_; + } + gpu::CommandBufferId GetCommandBufferID() const { return command_buffer_id_; } + gpu::SyncPointOrderData* sync_point_order_data() { + return sync_point_order_data_.get(); + } + uint32_t GetUnprocessedOrderNum() const; + uint32_t GetProcessedOrderNum() const; + void SignalQuery(uint32_t query_id, const base::Closure& callback); + + private: + bool MakeCurrent(); + + // Process pending queries and call |ScheduleDelayedWork| to schedule + // processing of delayed work. + void ProcessPendingAndIdleWork(); + + // Schedule processing of delayed work. This updates the time at which + // delayed work should be processed. |process_delayed_work_time_| is + // updated to current time + delay. Call this after processing some amount + // of delayed work. + void ScheduleDelayedWork(base::TimeDelta delay); + + // Poll the command buffer to execute work. + void PollWork(); + void PerformWork(); + + void DestroyDecoder(); + + // Callbacks: + void OnUpdateVSyncParameters(const base::TimeTicks timebase, + const base::TimeDelta interval); + void OnFenceSyncRelease(uint64_t release); + bool OnWaitFenceSync(gpu::CommandBufferNamespace namespace_id, + gpu::CommandBufferId command_buffer_id, + uint64_t release); + void OnDescheduleUntilFinished(); + void OnRescheduleAfterFinished(); + void OnParseError(); + void OnContextLost(uint32_t reason); + void OnGpuCompletedSwapBuffers(gfx::SwapResult result); + + const gpu::CommandBufferNamespace command_buffer_namespace_; + const gpu::CommandBufferId command_buffer_id_; + gfx::AcceleratedWidget widget_; + Client* client_; // NOT OWNED. + std::unique_ptr<gpu::CommandBufferService> command_buffer_; + std::unique_ptr<gpu::gles2::GLES2Decoder> decoder_; + std::unique_ptr<gpu::CommandExecutor> executor_; + scoped_refptr<gpu::SyncPointOrderData> sync_point_order_data_; + std::unique_ptr<gpu::SyncPointClient> sync_point_client_; + scoped_refptr<gl::GLContext> context_; + scoped_refptr<gl::GLSurface> surface_; + scoped_refptr<GpuState> gpu_state_; + + scoped_refptr<base::SingleThreadTaskRunner> context_lost_task_runner_; + base::Callback<void(int32_t)> context_lost_callback_; + + base::TimeTicks process_delayed_work_time_; + uint32_t previous_processed_num_; + base::TimeTicks last_idle_time_; + + base::WeakPtrFactory<CommandBufferDriver> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferDriver); +}; + +} // namespace mus + +#endif // COMPONENTS_GLES2_COMMAND_BUFFER_DRIVER_H_ diff --git a/chromium/components/mus/gles2/command_buffer_driver_manager.cc b/chromium/components/mus/gles2/command_buffer_driver_manager.cc new file mode 100644 index 00000000000..c8ff3b95a2f --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_driver_manager.cc @@ -0,0 +1,50 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gles2/command_buffer_driver_manager.h" + +#include <algorithm> + +#include "components/mus/gles2/command_buffer_driver.h" + +namespace mus { + +CommandBufferDriverManager::CommandBufferDriverManager() {} + +CommandBufferDriverManager::~CommandBufferDriverManager() {} + +void CommandBufferDriverManager::AddDriver(CommandBufferDriver* driver) { + DCHECK(CalledOnValidThread()); + DCHECK(std::find(drivers_.begin(), drivers_.end(), driver) == drivers_.end()); + drivers_.push_back(driver); +} + +void CommandBufferDriverManager::RemoveDriver(CommandBufferDriver* driver) { + DCHECK(CalledOnValidThread()); + auto it = std::find(drivers_.begin(), drivers_.end(), driver); + DCHECK(it != drivers_.end()); + drivers_.erase(it); +} + +uint32_t CommandBufferDriverManager::GetUnprocessedOrderNum() const { + DCHECK(CalledOnValidThread()); + uint32_t unprocessed_order_num = 0; + for (auto& d : drivers_) { + unprocessed_order_num = + std::max(unprocessed_order_num, d->GetUnprocessedOrderNum()); + } + return unprocessed_order_num; +} + +uint32_t CommandBufferDriverManager::GetProcessedOrderNum() const { + DCHECK(CalledOnValidThread()); + uint32_t processed_order_num = 0; + for (auto& d : drivers_) { + processed_order_num = + std::max(processed_order_num, d->GetProcessedOrderNum()); + } + return processed_order_num; +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/command_buffer_driver_manager.h b/chromium/components/mus/gles2/command_buffer_driver_manager.h new file mode 100644 index 00000000000..b2f8e7c0900 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_driver_manager.h @@ -0,0 +1,46 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GLES2_COMMAND_BUFFER_DRIVER_MANAGER_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_DRIVER_MANAGER_H_ + +#include <stdint.h> +#include <vector> + +#include "base/macros.h" +#include "base/threading/non_thread_safe.h" + +namespace mus { + +class CommandBufferDriver; + +// This class manages all initialized |CommandBufferDriver|s. +class CommandBufferDriverManager : base::NonThreadSafe { + public: + CommandBufferDriverManager(); + ~CommandBufferDriverManager(); + + // Add a new initialized driver to the manager. + void AddDriver(CommandBufferDriver* driver); + + // Remove a driver from the manager. + void RemoveDriver(CommandBufferDriver* driver); + + // Return the global order number for the last unprocessed flush + // (|CommandBufferDriver::Flush|). + uint32_t GetUnprocessedOrderNum() const; + + // Return the global order number for the last processed flush + // (|CommandBufferDriver::Flush|). + uint32_t GetProcessedOrderNum() const; + + private: + std::vector<CommandBufferDriver*> drivers_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferDriverManager); +}; + +} // namespace mus + +#endif // COMPONENTS_GLES2_COMMAND_BUFFER_DRIVER_MANAGER_H_ diff --git a/chromium/components/mus/gles2/command_buffer_impl.cc b/chromium/components/mus/gles2/command_buffer_impl.cc new file mode 100644 index 00000000000..70de3c2aab6 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_impl.cc @@ -0,0 +1,301 @@ +// 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 "components/mus/gles2/command_buffer_impl.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/gles2/command_buffer_driver.h" +#include "components/mus/gles2/gpu_state.h" +#include "gpu/command_buffer/service/sync_point_manager.h" + +namespace mus { + +namespace { + +uint64_t g_next_command_buffer_id = 0; + +void RunInitializeCallback( + const mojom::CommandBuffer::InitializeCallback& callback, + mojom::CommandBufferInitializeResultPtr result) { + callback.Run(std::move(result)); +} + +void RunMakeProgressCallback( + const mojom::CommandBuffer::MakeProgressCallback& callback, + const gpu::CommandBuffer::State& state) { + callback.Run(state); +} + +} // namespace + +CommandBufferImpl::CommandBufferImpl( + mojo::InterfaceRequest<mus::mojom::CommandBuffer> request, + scoped_refptr<GpuState> gpu_state) + : gpu_state_(gpu_state) { + // Bind |CommandBufferImpl| to the |request| in the GPU control thread. + gpu_state_->control_task_runner()->PostTask( + FROM_HERE, + base::Bind(&CommandBufferImpl::BindToRequest, + base::Unretained(this), base::Passed(&request))); +} + +void CommandBufferImpl::DidLoseContext(uint32_t reason) { + driver_->set_client(nullptr); + client_->Destroyed(reason, gpu::error::kLostContext); +} + +void CommandBufferImpl::UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) { +} + +void CommandBufferImpl::OnGpuCompletedSwapBuffers(gfx::SwapResult result) {} + +CommandBufferImpl::~CommandBufferImpl() { +} + +void CommandBufferImpl::Initialize( + mus::mojom::CommandBufferClientPtr client, + mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs, + const mojom::CommandBuffer::InitializeCallback& callback) { + gpu_state_->command_buffer_task_runner()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&CommandBufferImpl::InitializeOnGpuThread, + base::Unretained(this), base::Passed(&client), + base::Passed(&shared_state), base::Passed(&attribs), + base::Bind(&RunInitializeCallback, callback))); +} + +void CommandBufferImpl::SetGetBuffer(int32_t buffer) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferImpl::SetGetBufferOnGpuThread, + base::Unretained(this), buffer)); +} + +void CommandBufferImpl::Flush(int32_t put_offset) { + gpu::SyncPointManager* sync_point_manager = gpu_state_->sync_point_manager(); + const uint32_t order_num = driver_->sync_point_order_data() + ->GenerateUnprocessedOrderNumber(sync_point_manager); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferImpl::FlushOnGpuThread, + base::Unretained(this), put_offset, order_num)); +} + +void CommandBufferImpl::MakeProgress( + int32_t last_get_offset, + const mojom::CommandBuffer::MakeProgressCallback& callback) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferImpl::MakeProgressOnGpuThread, + base::Unretained(this), last_get_offset, + base::Bind(&RunMakeProgressCallback, + callback))); +} + +void CommandBufferImpl::RegisterTransferBuffer( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferImpl::RegisterTransferBufferOnGpuThread, + base::Unretained(this), id, base::Passed(&transfer_buffer), + size)); +} + +void CommandBufferImpl::DestroyTransferBuffer(int32_t id) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferImpl::DestroyTransferBufferOnGpuThread, + base::Unretained(this), id)); +} + +void CommandBufferImpl::CreateImage(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferImpl::CreateImageOnGpuThread, + base::Unretained(this), id, base::Passed(&memory_handle), type, + size, format, internal_format)); +} + +void CommandBufferImpl::DestroyImage(int32_t id) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferImpl::DestroyImageOnGpuThread, + base::Unretained(this), id)); +} + +void CommandBufferImpl::CreateStreamTexture( + uint32_t client_texture_id, + const mojom::CommandBuffer::CreateStreamTextureCallback& callback) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::TakeFrontBuffer(const gpu::Mailbox& mailbox) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::ReturnFrontBuffer(const gpu::Mailbox& mailbox, + bool is_lost) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::SignalQuery(uint32_t query, uint32_t signal_id) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::SignalSyncToken(const gpu::SyncToken& sync_token, + uint32_t signal_id) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::WaitForGetOffsetInRange( + int32_t start, int32_t end, + const mojom::CommandBuffer::WaitForGetOffsetInRangeCallback& callback) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::WaitForTokenInRange( + int32_t start, int32_t end, + const mojom::CommandBuffer::WaitForGetOffsetInRangeCallback& callback) { + NOTIMPLEMENTED(); +} + +void CommandBufferImpl::BindToRequest( + mojo::InterfaceRequest<mus::mojom::CommandBuffer> request) { + binding_.reset( + new mojo::Binding<mus::mojom::CommandBuffer>(this, std::move(request))); + binding_->set_connection_error_handler( + base::Bind(&CommandBufferImpl::OnConnectionError, + base::Unretained(this))); +} + +void CommandBufferImpl::InitializeOnGpuThread( + mojom::CommandBufferClientPtr client, + mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs, + const base::Callback< + void(mojom::CommandBufferInitializeResultPtr)>& callback) { + DCHECK(!driver_); + driver_.reset(new CommandBufferDriver( + gpu::CommandBufferNamespace::MOJO, + gpu::CommandBufferId::FromUnsafeValue(++g_next_command_buffer_id), + gfx::kNullAcceleratedWidget, gpu_state_)); + driver_->set_client(this); + client_ = mojo::MakeProxy(client.PassInterface()); + bool result = + driver_->Initialize(std::move(shared_state), std::move(attribs)); + mojom::CommandBufferInitializeResultPtr initialize_result; + if (result) { + initialize_result = mojom::CommandBufferInitializeResult::New(); + initialize_result->command_buffer_namespace = driver_->GetNamespaceID(); + initialize_result->command_buffer_id = + driver_->GetCommandBufferID().GetUnsafeValue(); + initialize_result->capabilities = driver_->GetCapabilities(); + } + gpu_state_->control_task_runner()->PostTask( + FROM_HERE, base::Bind(callback, base::Passed(&initialize_result))); +} + +bool CommandBufferImpl::SetGetBufferOnGpuThread(int32_t buffer) { + DCHECK(driver_->IsScheduled()); + driver_->SetGetBuffer(buffer); + return true; +} + +bool CommandBufferImpl::FlushOnGpuThread(int32_t put_offset, + uint32_t order_num) { + DCHECK(driver_->IsScheduled()); + driver_->sync_point_order_data()->BeginProcessingOrderNumber(order_num); + driver_->Flush(put_offset); + + // Return false if the Flush is not finished, so the CommandBufferTaskRunner + // will not remove this task from the task queue. + const bool complete = !driver_->HasUnprocessedCommands(); + if (!complete) + driver_->sync_point_order_data()->PauseProcessingOrderNumber(order_num); + else + driver_->sync_point_order_data()->FinishProcessingOrderNumber(order_num); + return complete; +} + +bool CommandBufferImpl::MakeProgressOnGpuThread( + int32_t last_get_offset, + const base::Callback<void(const gpu::CommandBuffer::State&)>& callback) { + DCHECK(driver_->IsScheduled()); + gpu_state_->control_task_runner()->PostTask( + FROM_HERE, base::Bind(callback, driver_->GetLastState())); + return true; +} + +bool CommandBufferImpl::RegisterTransferBufferOnGpuThread( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + DCHECK(driver_->IsScheduled()); + driver_->RegisterTransferBuffer(id, std::move(transfer_buffer), size); + return true; +} + +bool CommandBufferImpl::DestroyTransferBufferOnGpuThread(int32_t id) { + DCHECK(driver_->IsScheduled()); + driver_->DestroyTransferBuffer(id); + return true; +} + +bool CommandBufferImpl::CreateImageOnGpuThread(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format) { + DCHECK(driver_->IsScheduled()); + driver_->CreateImage(id, std::move(memory_handle), type, std::move(size), + format, internal_format); + return true; +} + +bool CommandBufferImpl::DestroyImageOnGpuThread(int32_t id) { + DCHECK(driver_->IsScheduled()); + driver_->DestroyImage(id); + return true; +} + +void CommandBufferImpl::OnConnectionError() { + // OnConnectionError() is called on the control thread |control_task_runner|. + + // Before deleting, we need to delete |binding_| because it is bound to the + // current thread (|control_task_runner|). + binding_.reset(); + + // Objects we own (such as CommandBufferDriver) need to be destroyed on the + // thread we were created on. It's entirely possible we haven't or are in the + // process of creating |driver_|. + if (driver_) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferImpl::DeleteOnGpuThread, + base::Unretained(this))); + } else { + gpu_state_->command_buffer_task_runner()->task_runner()->PostTask( + FROM_HERE, base::Bind(&CommandBufferImpl::DeleteOnGpuThread2, + base::Unretained(this))); + } +} + +bool CommandBufferImpl::DeleteOnGpuThread() { + delete this; + return true; +} + +void CommandBufferImpl::DeleteOnGpuThread2() { + delete this; +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/command_buffer_impl.h b/chromium/components/mus/gles2/command_buffer_impl.h new file mode 100644 index 00000000000..a37f2d1ec43 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_impl.h @@ -0,0 +1,126 @@ +// 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 COMPONENTS_MUS_GLES2_COMMAND_BUFFER_IMPL_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_IMPL_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/single_thread_task_runner.h" +#include "components/mus/gles2/command_buffer_driver.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { + +class CommandBufferDriver; +class GpuState; + +// 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 mojom::CommandBuffer, + public CommandBufferDriver::Client { + public: + CommandBufferImpl(mojo::InterfaceRequest<CommandBuffer> request, + scoped_refptr<GpuState> gpu_state); + + private: + class CommandBufferDriverClientImpl; + ~CommandBufferImpl() override; + + // CommandBufferDriver::Client. All called on the GPU thread. + void DidLoseContext(uint32_t reason) override; + void UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) override; + void OnGpuCompletedSwapBuffers(gfx::SwapResult result) override; + + // mojom::CommandBuffer: + void Initialize( + mojom::CommandBufferClientPtr client, + mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs, + const mojom::CommandBuffer::InitializeCallback& callback) override; + void SetGetBuffer(int32_t buffer) override; + void Flush(int32_t put_offset) override; + void MakeProgress( + int32_t last_get_offset, + const mojom::CommandBuffer::MakeProgressCallback& callback) override; + void RegisterTransferBuffer(int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) override; + void DestroyTransferBuffer(int32_t id) override; + void CreateImage(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format) override; + void DestroyImage(int32_t id) override; + void CreateStreamTexture( + uint32_t client_texture_id, + const mojom::CommandBuffer::CreateStreamTextureCallback& callback + ) override; + void TakeFrontBuffer(const gpu::Mailbox& mailbox) override; + void ReturnFrontBuffer(const gpu::Mailbox& mailbox, bool is_lost) override; + void SignalQuery(uint32_t query, uint32_t signal_id) override; + void SignalSyncToken(const gpu::SyncToken& sync_token, + uint32_t signal_id) override; + void WaitForGetOffsetInRange( + int32_t start, int32_t end, + const mojom::CommandBuffer::WaitForGetOffsetInRangeCallback& callback + ) override; + void WaitForTokenInRange( + int32_t start, int32_t end, + const mojom::CommandBuffer::WaitForGetOffsetInRangeCallback& callback + ) override; + + // All helper functions are called in the GPU therad. + void InitializeOnGpuThread( + mojom::CommandBufferClientPtr client, + mojo::ScopedSharedBufferHandle shared_state, + mojo::Array<int32_t> attribs, + const base::Callback< + void(mojom::CommandBufferInitializeResultPtr)>& callback); + bool SetGetBufferOnGpuThread(int32_t buffer); + bool FlushOnGpuThread(int32_t put_offset, uint32_t order_num); + bool MakeProgressOnGpuThread( + int32_t last_get_offset, + const base::Callback<void(const gpu::CommandBuffer::State&)>& callback); + bool RegisterTransferBufferOnGpuThread( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size); + bool DestroyTransferBufferOnGpuThread(int32_t id); + bool CreateImageOnGpuThread(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format); + bool DestroyImageOnGpuThread(int32_t id); + + void BindToRequest(mojo::InterfaceRequest<CommandBuffer> request); + + void OnConnectionError(); + bool DeleteOnGpuThread(); + void DeleteOnGpuThread2(); + + scoped_refptr<GpuState> gpu_state_; + std::unique_ptr<CommandBufferDriver> driver_; + std::unique_ptr<mojo::Binding<CommandBuffer>> binding_; + mojom::CommandBufferClientPtr client_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferImpl); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_COMMAND_BUFFER_IMPL_H_ diff --git a/chromium/components/mus/gles2/command_buffer_local.cc b/chromium/components/mus/gles2/command_buffer_local.cc new file mode 100644 index 00000000000..434fb551525 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_local.cc @@ -0,0 +1,576 @@ +// 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 "components/mus/gles2/command_buffer_local.h" + +#include "base/atomic_sequence_num.h" +#include "base/bind.h" +#include "base/memory/shared_memory.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/common/mojo_buffer_backing.h" +#include "components/mus/common/mojo_gpu_memory_buffer.h" +#include "components/mus/gles2/command_buffer_driver.h" +#include "components/mus/gles2/command_buffer_local_client.h" +#include "components/mus/gles2/gpu_memory_tracker.h" +#include "components/mus/gles2/gpu_state.h" +#include "gpu/command_buffer/client/gpu_control_client.h" +#include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "gpu/command_buffer/service/command_buffer_service.h" +#include "gpu/command_buffer/service/context_group.h" +#include "gpu/command_buffer/service/image_manager.h" +#include "gpu/command_buffer/service/memory_tracking.h" +#include "gpu/command_buffer/service/shader_translator_cache.h" +#include "gpu/command_buffer/service/transfer_buffer_manager.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "ui/gfx/buffer_format_util.h" +#include "ui/gfx/vsync_provider.h" +#include "ui/gl/gl_context.h" +#include "ui/gl/gl_image_shared_memory.h" +#include "ui/gl/gl_surface.h" + +namespace mus { + +namespace { + +uint64_t g_next_command_buffer_id = 0; + +bool CreateAndMapSharedBuffer(size_t size, + mojo::ScopedSharedBufferMapping* mapping, + mojo::ScopedSharedBufferHandle* handle) { + *handle = mojo::SharedBufferHandle::Create(size); + if (!handle->is_valid()) + return false; + + *mapping = (*handle)->Map(size); + if (!*mapping) + return false; + + return true; +} + +void PostTask(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const base::Closure& callback) { + task_runner->PostTask(FROM_HERE, callback); +} +} + +const unsigned int GL_READ_WRITE_CHROMIUM = 0x78F2; + +CommandBufferLocal::CommandBufferLocal(CommandBufferLocalClient* client, + gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& gpu_state) + : widget_(widget), + gpu_state_(gpu_state), + client_(client), + client_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), + gpu_control_client_(nullptr), + next_transfer_buffer_id_(0), + next_image_id_(0), + next_fence_sync_release_(1), + flushed_fence_sync_release_(0), + lost_context_(false), + sync_point_client_waiter_( + gpu_state->sync_point_manager()->CreateSyncPointClientWaiter()), + weak_factory_(this) { + weak_ptr_ = weak_factory_.GetWeakPtr(); +} + +void CommandBufferLocal::Destroy() { + DCHECK(CalledOnValidThread()); + // After this |Destroy()| call, this object will not be used by client anymore + // and it will be deleted on the GPU thread. So we have to detach it from the + // client thread first. + DetachFromThread(); + + weak_factory_.InvalidateWeakPtrs(); + // CommandBufferLocal is initialized on the GPU thread with + // InitializeOnGpuThread(), so we need delete memebers on the GPU thread + // too. Additionally we need to make sure we are deleted before returning, + // otherwise we may attempt to use the AcceleratedWidget which has since been + // destroyed. + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferLocal::DeleteOnGpuThread, + base::Unretained(this), &event)); + event.Wait(); +} + +bool CommandBufferLocal::Initialize() { + DCHECK(CalledOnValidThread()); + base::ThreadRestrictions::ScopedAllowWait allow_wait; + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + bool result = false; + gpu_state_->command_buffer_task_runner()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&CommandBufferLocal::InitializeOnGpuThread, + base::Unretained(this), base::Unretained(&event), + base::Unretained(&result))); + event.Wait(); + return result; +} + +gpu::CommandBuffer::State CommandBufferLocal::GetLastState() { + DCHECK(CalledOnValidThread()); + return last_state_; +} + +int32_t CommandBufferLocal::GetLastToken() { + DCHECK(CalledOnValidThread()); + TryUpdateState(); + return last_state_.token; +} + +void CommandBufferLocal::Flush(int32_t put_offset) { + DCHECK(CalledOnValidThread()); + if (last_put_offset_ == put_offset) + return; + + last_put_offset_ = put_offset; + gpu::SyncPointManager* sync_point_manager = gpu_state_->sync_point_manager(); + const uint32_t order_num = + driver_->sync_point_order_data()->GenerateUnprocessedOrderNumber( + sync_point_manager); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferLocal::FlushOnGpuThread, + base::Unretained(this), put_offset, order_num)); + flushed_fence_sync_release_ = next_fence_sync_release_ - 1; +} + +void CommandBufferLocal::OrderingBarrier(int32_t put_offset) { + DCHECK(CalledOnValidThread()); + // TODO(penghuang): Implement this more efficiently. + Flush(put_offset); +} + +void CommandBufferLocal::WaitForTokenInRange(int32_t start, int32_t end) { + DCHECK(CalledOnValidThread()); + TryUpdateState(); + while (!InRange(start, end, last_state_.token) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + } +} + +void CommandBufferLocal::WaitForGetOffsetInRange(int32_t start, int32_t end) { + DCHECK(CalledOnValidThread()); + TryUpdateState(); + while (!InRange(start, end, last_state_.get_offset) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + } +} + +void CommandBufferLocal::SetGetBuffer(int32_t buffer) { + DCHECK(CalledOnValidThread()); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferLocal::SetGetBufferOnGpuThread, + base::Unretained(this), buffer)); + last_put_offset_ = -1; +} + +scoped_refptr<gpu::Buffer> CommandBufferLocal::CreateTransferBuffer( + size_t size, + int32_t* id) { + DCHECK(CalledOnValidThread()); + if (size >= std::numeric_limits<uint32_t>::max()) + return nullptr; + + mojo::ScopedSharedBufferMapping mapping; + mojo::ScopedSharedBufferHandle handle; + if (!CreateAndMapSharedBuffer(size, &mapping, &handle)) { + if (last_state_.error == gpu::error::kNoError) + last_state_.error = gpu::error::kLostContext; + return nullptr; + } + + *id = ++next_transfer_buffer_id_; + + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferLocal::RegisterTransferBufferOnGpuThread, + base::Unretained(this), *id, base::Passed(&handle), + static_cast<uint32_t>(size))); + std::unique_ptr<gpu::BufferBacking> backing( + new mus::MojoBufferBacking(std::move(mapping), size)); + scoped_refptr<gpu::Buffer> buffer(new gpu::Buffer(std::move(backing))); + return buffer; +} + +void CommandBufferLocal::DestroyTransferBuffer(int32_t id) { + DCHECK(CalledOnValidThread()); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferLocal::DestroyTransferBufferOnGpuThread, + base::Unretained(this), id)); +} + +void CommandBufferLocal::SetGpuControlClient(gpu::GpuControlClient* client) { + gpu_control_client_ = client; +} + +gpu::Capabilities CommandBufferLocal::GetCapabilities() { + DCHECK(CalledOnValidThread()); + return capabilities_; +} + +int32_t CommandBufferLocal::CreateImage(ClientBuffer buffer, + size_t width, + size_t height, + unsigned internal_format) { + DCHECK(CalledOnValidThread()); + int32_t new_id = ++next_image_id_; + gfx::Size size(static_cast<int32_t>(width), static_cast<int32_t>(height)); + + mus::MojoGpuMemoryBufferImpl* gpu_memory_buffer = + mus::MojoGpuMemoryBufferImpl::FromClientBuffer(buffer); + + bool requires_sync_point = false; + + if (gpu_memory_buffer->GetBufferType() == gfx::SHARED_MEMORY_BUFFER) { + gfx::GpuMemoryBufferHandle handle = gpu_memory_buffer->GetHandle(); + // TODO(rjkroege): Verify that this is required and update appropriately. + base::SharedMemoryHandle dupd_handle = + base::SharedMemory::DuplicateHandle(handle.handle); +#if defined(OS_WIN) + HANDLE platform_file = dupd_handle.GetHandle(); +#else + int platform_file = dupd_handle.fd; +#endif + + mojo::ScopedHandle scoped_handle = mojo::WrapPlatformFile(platform_file); + const int32_t format = static_cast<int32_t>(gpu_memory_buffer->GetFormat()); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferLocal::CreateImageOnGpuThread, + base::Unretained(this), new_id, base::Passed(&scoped_handle), + handle.type, base::Passed(&size), format, internal_format)); +#if defined(USE_OZONE) + } else if (gpu_memory_buffer->GetBufferType() == gfx::OZONE_NATIVE_PIXMAP) { + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferLocal::CreateImageNativeOzoneOnGpuThread, + base::Unretained(this), new_id, + gpu_memory_buffer->GetBufferType(), + gpu_memory_buffer->GetSize(), gpu_memory_buffer->GetFormat(), + internal_format, + base::RetainedRef(gpu_memory_buffer->GetNativePixmap()))); +#endif + } else { + NOTIMPLEMENTED(); + return -1; + } + + if (requires_sync_point) { + NOTIMPLEMENTED() << "Require sync points"; + // TODO(jam): need to support this if we support types other than + // SHARED_MEMORY_BUFFER. + // gpu_memory_buffer_manager->SetDestructionSyncPoint(gpu_memory_buffer, + // InsertSyncPoint()); + } + + return new_id; +} + +void CommandBufferLocal::DestroyImage(int32_t id) { + DCHECK(CalledOnValidThread()); + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferLocal::DestroyImageOnGpuThread, + base::Unretained(this), id)); +} + +int32_t CommandBufferLocal::CreateGpuMemoryBufferImage(size_t width, + size_t height, + unsigned internal_format, + unsigned usage) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(usage, static_cast<unsigned>(GL_READ_WRITE_CHROMIUM)); + std::unique_ptr<gfx::GpuMemoryBuffer> buffer(MojoGpuMemoryBufferImpl::Create( + gfx::Size(static_cast<int>(width), static_cast<int>(height)), + gpu::DefaultBufferFormatForImageFormat(internal_format), + gfx::BufferUsage::SCANOUT)); + if (!buffer) + return -1; + return CreateImage(buffer->AsClientBuffer(), width, height, internal_format); +} + +int32_t CommandBufferLocal::GetImageGpuMemoryBufferId(unsigned image_id) { + // TODO(erikchen): Once this class supports IOSurface GpuMemoryBuffer backed + // images, it will also need to keep a local cache from image id to + // GpuMemoryBuffer id. + NOTIMPLEMENTED(); + return -1; +} + +void CommandBufferLocal::SignalQuery(uint32_t query_id, + const base::Closure& callback) { + DCHECK(CalledOnValidThread()); + + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), base::Bind(&CommandBufferLocal::SignalQueryOnGpuThread, + base::Unretained(this), query_id, callback)); +} + +void CommandBufferLocal::SetLock(base::Lock* lock) { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); +} + +void CommandBufferLocal::EnsureWorkVisible() { + // This is only relevant for out-of-process command buffers. +} + +gpu::CommandBufferNamespace CommandBufferLocal::GetNamespaceID() const { + DCHECK(CalledOnValidThread()); + return gpu::CommandBufferNamespace::MOJO_LOCAL; +} + +gpu::CommandBufferId CommandBufferLocal::GetCommandBufferID() const { + DCHECK(CalledOnValidThread()); + return driver_->GetCommandBufferID(); +} + +int32_t CommandBufferLocal::GetExtraCommandBufferData() const { + DCHECK(CalledOnValidThread()); + return 0; +} + +uint64_t CommandBufferLocal::GenerateFenceSyncRelease() { + DCHECK(CalledOnValidThread()); + return next_fence_sync_release_++; +} + +bool CommandBufferLocal::IsFenceSyncRelease(uint64_t release) { + DCHECK(CalledOnValidThread()); + return release != 0 && release < next_fence_sync_release_; +} + +bool CommandBufferLocal::IsFenceSyncFlushed(uint64_t release) { + DCHECK(CalledOnValidThread()); + return release != 0 && release <= flushed_fence_sync_release_; +} + +bool CommandBufferLocal::IsFenceSyncFlushReceived(uint64_t release) { + DCHECK(CalledOnValidThread()); + return IsFenceSyncFlushed(release); +} + +void CommandBufferLocal::SignalSyncToken(const gpu::SyncToken& sync_token, + const base::Closure& callback) { + DCHECK(CalledOnValidThread()); + scoped_refptr<gpu::SyncPointClientState> release_state = + gpu_state_->sync_point_manager()->GetSyncPointClientState( + sync_token.namespace_id(), sync_token.command_buffer_id()); + if (!release_state || + release_state->IsFenceSyncReleased(sync_token.release_count())) { + callback.Run(); + return; + } + + sync_point_client_waiter_->WaitOutOfOrderNonThreadSafe( + release_state.get(), sync_token.release_count(), + client_thread_task_runner_, callback); +} + +bool CommandBufferLocal::CanWaitUnverifiedSyncToken( + const gpu::SyncToken* sync_token) { + DCHECK(CalledOnValidThread()); + // Right now, MOJO_LOCAL is only used by trusted code, so it is safe to wait + // on a sync token in MOJO_LOCAL command buffer. + return sync_token->namespace_id() == gpu::CommandBufferNamespace::MOJO_LOCAL; +} + +void CommandBufferLocal::DidLoseContext(uint32_t reason) { + if (client_) { + driver_->set_client(nullptr); + client_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CommandBufferLocal::DidLoseContextOnClientThread, + weak_ptr_, reason)); + } +} + +void CommandBufferLocal::UpdateVSyncParameters( + const base::TimeTicks& timebase, + const base::TimeDelta& interval) { + if (client_) { + client_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CommandBufferLocal::UpdateVSyncParametersOnClientThread, + weak_ptr_, timebase, interval)); + } +} + +void CommandBufferLocal::OnGpuCompletedSwapBuffers(gfx::SwapResult result) { + if (client_) { + client_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&CommandBufferLocal::OnGpuCompletedSwapBuffersOnClientThread, + weak_ptr_, result)); + } +} + +CommandBufferLocal::~CommandBufferLocal() {} + +void CommandBufferLocal::TryUpdateState() { + if (last_state_.error == gpu::error::kNoError) + shared_state()->Read(&last_state_); +} + +void CommandBufferLocal::MakeProgressAndUpdateState() { + base::ThreadRestrictions::ScopedAllowWait allow_wait; + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + gpu::CommandBuffer::State state; + gpu_state_->command_buffer_task_runner()->PostTask( + driver_.get(), + base::Bind(&CommandBufferLocal::MakeProgressOnGpuThread, + base::Unretained(this), base::Unretained(&event), + base::Unretained(&state))); + event.Wait(); + if (state.generation - last_state_.generation < 0x80000000U) + last_state_ = state; +} + +void CommandBufferLocal::InitializeOnGpuThread(base::WaitableEvent* event, + bool* result) { + driver_.reset(new CommandBufferDriver( + gpu::CommandBufferNamespace::MOJO_LOCAL, + gpu::CommandBufferId::FromUnsafeValue(++g_next_command_buffer_id), + widget_, gpu_state_)); + driver_->set_client(this); + const size_t kSharedStateSize = sizeof(gpu::CommandBufferSharedState); + mojo::ScopedSharedBufferMapping mapping; + mojo::ScopedSharedBufferHandle handle; + *result = CreateAndMapSharedBuffer(kSharedStateSize, &shared_state_, &handle); + + if (!*result) { + event->Signal(); + return; + } + + shared_state()->Initialize(); + + *result = + driver_->Initialize(std::move(handle), mojo::Array<int32_t>::New(0)); + if (*result) + capabilities_ = driver_->GetCapabilities(); + event->Signal(); +} + +bool CommandBufferLocal::FlushOnGpuThread(int32_t put_offset, + uint32_t order_num) { + DCHECK(driver_->IsScheduled()); + driver_->sync_point_order_data()->BeginProcessingOrderNumber(order_num); + driver_->Flush(put_offset); + + // Return false if the Flush is not finished, so the CommandBufferTaskRunner + // will not remove this task from the task queue. + const bool complete = !driver_->HasUnprocessedCommands(); + if (complete) + driver_->sync_point_order_data()->FinishProcessingOrderNumber(order_num); + return complete; +} + +bool CommandBufferLocal::SetGetBufferOnGpuThread(int32_t buffer) { + DCHECK(driver_->IsScheduled()); + driver_->SetGetBuffer(buffer); + return true; +} + +bool CommandBufferLocal::RegisterTransferBufferOnGpuThread( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size) { + DCHECK(driver_->IsScheduled()); + driver_->RegisterTransferBuffer(id, std::move(transfer_buffer), size); + return true; +} + +bool CommandBufferLocal::DestroyTransferBufferOnGpuThread(int32_t id) { + DCHECK(driver_->IsScheduled()); + driver_->DestroyTransferBuffer(id); + return true; +} + +bool CommandBufferLocal::CreateImageOnGpuThread( + int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format) { + DCHECK(driver_->IsScheduled()); + driver_->CreateImage(id, std::move(memory_handle), type, std::move(size), + format, internal_format); + return true; +} + +bool CommandBufferLocal::CreateImageNativeOzoneOnGpuThread( + int32_t id, + int32_t type, + gfx::Size size, + gfx::BufferFormat format, + uint32_t internal_format, + ui::NativePixmap* pixmap) { + DCHECK(driver_->IsScheduled()); + driver_->CreateImageNativeOzone(id, type, size, format, internal_format, + pixmap); + return true; +} + +bool CommandBufferLocal::DestroyImageOnGpuThread(int32_t id) { + DCHECK(driver_->IsScheduled()); + driver_->DestroyImage(id); + return true; +} + +bool CommandBufferLocal::MakeProgressOnGpuThread( + base::WaitableEvent* event, + gpu::CommandBuffer::State* state) { + DCHECK(driver_->IsScheduled()); + *state = driver_->GetLastState(); + event->Signal(); + return true; +} + +bool CommandBufferLocal::DeleteOnGpuThread(base::WaitableEvent* event) { + delete this; + event->Signal(); + return true; +} + +bool CommandBufferLocal::SignalQueryOnGpuThread(uint32_t query_id, + const base::Closure& callback) { + // |callback| should run on the client thread. + driver_->SignalQuery( + query_id, base::Bind(&PostTask, client_thread_task_runner_, callback)); + return true; +} + +void CommandBufferLocal::DidLoseContextOnClientThread(uint32_t reason) { + DCHECK(gpu_control_client_); + if (!lost_context_) + gpu_control_client_->OnGpuControlLostContext(); + lost_context_ = true; +} + +void CommandBufferLocal::UpdateVSyncParametersOnClientThread( + const base::TimeTicks& timebase, + const base::TimeDelta& interval) { + if (client_) + client_->UpdateVSyncParameters(timebase, interval); +} + +void CommandBufferLocal::OnGpuCompletedSwapBuffersOnClientThread( + gfx::SwapResult result) { + if (client_) + client_->GpuCompletedSwapBuffers(result); +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/command_buffer_local.h b/chromium/components/mus/gles2/command_buffer_local.h new file mode 100644 index 00000000000..4d893322737 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_local.h @@ -0,0 +1,185 @@ +// 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 COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <map> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "components/mus/gles2/command_buffer_driver.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "gpu/command_buffer/client/gpu_control.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/command_buffer_id.h" +#include "gpu/command_buffer/common/command_buffer_shared.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/native_widget_types.h" + +namespace { +class WaitableEvent; +} + +namespace gl { +class GLContext; +class GLSurface; +} + +namespace gpu { +class GpuControlClient; +class SyncPointClient; +} + +namespace mus { + +class CommandBufferDriver; +class CommandBufferLocalClient; +class GpuState; + +// This class provides a wrapper around a CommandBufferDriver and a GpuControl +// implementation to allow cc::Display to generate GL directly on the main +// thread. +class CommandBufferLocal : public gpu::CommandBuffer, + public gpu::GpuControl, + public CommandBufferDriver::Client, + base::NonThreadSafe { + public: + CommandBufferLocal(CommandBufferLocalClient* client, + gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& gpu_state); + + bool Initialize(); + // Destroy the CommandBufferLocal. The client should not use this class + // after calling it. + void Destroy(); + + // gpu::CommandBuffer implementation: + gpu::CommandBuffer::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 buffer) override; + scoped_refptr<gpu::Buffer> CreateTransferBuffer(size_t size, + int32_t* id) override; + void DestroyTransferBuffer(int32_t id) override; + + // gpu::GpuControl implementation: + void SetGpuControlClient(gpu::GpuControlClient*) override; + 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 internal_format, + unsigned usage) override; + int32_t GetImageGpuMemoryBufferId(unsigned image_id) override; + void SignalQuery(uint32_t query_id, const base::Closure& callback) override; + void SetLock(base::Lock*) override; + void EnsureWorkVisible() override; + gpu::CommandBufferNamespace GetNamespaceID() const override; + gpu::CommandBufferId GetCommandBufferID() const override; + int32_t GetExtraCommandBufferData() const override; + uint64_t GenerateFenceSyncRelease() override; + bool IsFenceSyncRelease(uint64_t release) override; + bool IsFenceSyncFlushed(uint64_t release) override; + bool IsFenceSyncFlushReceived(uint64_t release) override; + void SignalSyncToken(const gpu::SyncToken& sync_token, + const base::Closure& callback) override; + bool CanWaitUnverifiedSyncToken(const gpu::SyncToken* sync_token) override; + + private: + // CommandBufferDriver::Client implementation. All called on GPU thread. + void DidLoseContext(uint32_t reason) override; + void UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) override; + void OnGpuCompletedSwapBuffers(gfx::SwapResult result) override; + + ~CommandBufferLocal() override; + + gpu::CommandBufferSharedState* shared_state() const { + return reinterpret_cast<gpu::CommandBufferSharedState*>( + shared_state_.get()); + } + void TryUpdateState(); + void MakeProgressAndUpdateState(); + + // Helper functions are called in the GPU thread. + void InitializeOnGpuThread(base::WaitableEvent* event, bool* result); + bool FlushOnGpuThread(int32_t put_offset, uint32_t order_num); + bool SetGetBufferOnGpuThread(int32_t buffer); + bool RegisterTransferBufferOnGpuThread( + int32_t id, + mojo::ScopedSharedBufferHandle transfer_buffer, + uint32_t size); + bool DestroyTransferBufferOnGpuThread(int32_t id); + bool CreateImageOnGpuThread(int32_t id, + mojo::ScopedHandle memory_handle, + int32_t type, + const gfx::Size& size, + int32_t format, + int32_t internal_format); + bool CreateImageNativeOzoneOnGpuThread(int32_t id, + int32_t type, + gfx::Size size, + gfx::BufferFormat format, + uint32_t internal_format, + ui::NativePixmap* pixmap); + bool DestroyImageOnGpuThread(int32_t id); + bool MakeProgressOnGpuThread(base::WaitableEvent* event, + gpu::CommandBuffer::State* state); + bool DeleteOnGpuThread(base::WaitableEvent* event); + bool SignalQueryOnGpuThread(uint32_t query_id, const base::Closure& callback); + + // Helper functions are called in the client thread. + void DidLoseContextOnClientThread(uint32_t reason); + void UpdateVSyncParametersOnClientThread(const base::TimeTicks& timebase, + const base::TimeDelta& interval); + void OnGpuCompletedSwapBuffersOnClientThread(gfx::SwapResult result); + + gfx::AcceleratedWidget widget_; + scoped_refptr<GpuState> gpu_state_; + std::unique_ptr<CommandBufferDriver> driver_; + CommandBufferLocalClient* client_; + scoped_refptr<base::SingleThreadTaskRunner> client_thread_task_runner_; + + // Members accessed on the client thread: + gpu::GpuControlClient* gpu_control_client_; + gpu::CommandBuffer::State last_state_; + mojo::ScopedSharedBufferMapping shared_state_; + int32_t last_put_offset_; + gpu::Capabilities capabilities_; + int32_t next_transfer_buffer_id_; + int32_t next_image_id_; + uint64_t next_fence_sync_release_; + uint64_t flushed_fence_sync_release_; + bool lost_context_; + + // This sync point client is only for out of order Wait on client thread. + std::unique_ptr<gpu::SyncPointClient> sync_point_client_waiter_; + + base::WeakPtr<CommandBufferLocal> weak_ptr_; + + // This weak factory will be invalidated in the client thread, so all weak + // pointers have to be dereferenced in the client thread too. + base::WeakPtrFactory<CommandBufferLocal> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferLocal); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_H_ diff --git a/chromium/components/mus/gles2/command_buffer_local_client.h b/chromium/components/mus/gles2/command_buffer_local_client.h new file mode 100644 index 00000000000..c26cbdf4941 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_local_client.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 COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_CLIENT_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_CLIENT_H_ + +#include <stdint.h> + +#include "ui/gfx/swap_result.h" + +namespace base { +class TimeDelta; +class TimeTicks; +} + +namespace mus { + +class CommandBufferLocalClient { + public: + virtual void UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) = 0; + virtual void GpuCompletedSwapBuffers(gfx::SwapResult result) = 0; + + protected: + virtual ~CommandBufferLocalClient() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_COMMAND_BUFFER_LOCAL_CLIENT_H_ diff --git a/chromium/components/mus/gles2/command_buffer_task_runner.cc b/chromium/components/mus/gles2/command_buffer_task_runner.cc new file mode 100644 index 00000000000..df1dc548b6a --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_task_runner.cc @@ -0,0 +1,77 @@ +// 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 "components/mus/gles2/command_buffer_task_runner.h" + +#include "base/bind.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/mus/gles2/command_buffer_driver.h" + +namespace mus { + +CommandBufferTaskRunner::CommandBufferTaskRunner() + : task_runner_(base::ThreadTaskRunnerHandle::Get()), + need_post_task_(true) { +} + +bool CommandBufferTaskRunner::PostTask( + const CommandBufferDriver* driver, + const TaskCallback& task) { + base::AutoLock locker(lock_); + driver_map_[driver].push_back(task); + ScheduleTaskIfNecessaryLocked(); + return true; +} + +CommandBufferTaskRunner::~CommandBufferTaskRunner() {} + +bool CommandBufferTaskRunner::RunOneTaskInternalLocked() { + DCHECK(thread_checker_.CalledOnValidThread()); + lock_.AssertAcquired(); + + for (auto it = driver_map_.begin(); it != driver_map_.end(); ++it) { + if (!it->first->IsScheduled()) + continue; + TaskQueue& task_queue = it->second; + DCHECK(!task_queue.empty()); + const TaskCallback& callback = task_queue.front(); + bool complete = false; + { + base::AutoUnlock unlocker(lock_); + complete = callback.Run(); + } + if (complete) { + // Only remove the task if it is complete. + task_queue.pop_front(); + if (task_queue.empty()) + driver_map_.erase(it); + } + return true; + } + return false; +} + +void CommandBufferTaskRunner::ScheduleTaskIfNecessaryLocked() { + lock_.AssertAcquired(); + if (!need_post_task_) + return; + task_runner()->PostTask( + FROM_HERE, + base::Bind(&CommandBufferTaskRunner::RunCommandBufferTask, this)); + need_post_task_ = false; +} + +void CommandBufferTaskRunner::RunCommandBufferTask() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::AutoLock locker(lock_); + + // Try executing all tasks in |driver_map_| until |RunOneTaskInternalLock()| + // returns false (Returning false means |driver_map_| is empty or all tasks + // in it aren't executable currently). + while (RunOneTaskInternalLocked()) + ; + need_post_task_ = true; +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/command_buffer_task_runner.h b/chromium/components/mus/gles2/command_buffer_task_runner.h new file mode 100644 index 00000000000..8ee317f5800 --- /dev/null +++ b/chromium/components/mus/gles2/command_buffer_task_runner.h @@ -0,0 +1,78 @@ +// 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 COMPONENTS_MUS_GLES2_COMMAND_BUFFER_TASK_RUNNER_H_ +#define COMPONENTS_MUS_GLES2_COMMAND_BUFFER_TASK_RUNNER_H_ + +#include <deque> +#include <map> +#include <memory> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" + +namespace mus { + +class CommandBufferDriver; + +// This class maintains tasks submitted by |CommandBufferImpl|. Those tasks will +// be executed on the main thread. But if the main thread is blocked in +// |CommandBufferLocal::OnWaitFenceSync()| by waiting a sync point, the +// |CommandBufferTaskRunner::RunOneTask()| could be used for executing a task +// from other command buffers which may retire the sync point. +class CommandBufferTaskRunner + : public base::RefCountedThreadSafe<CommandBufferTaskRunner> { + public: + CommandBufferTaskRunner(); + + // TaskCallback returns true if the task is completed and should be removed + // from the task queue, otherwise returns false. + typedef base::Callback<bool(void)> TaskCallback; + bool PostTask(const CommandBufferDriver* driver, + const TaskCallback& task); + + scoped_refptr<base::SingleThreadTaskRunner> task_runner() const { + return task_runner_; + } + + private: + friend class base::RefCountedThreadSafe<CommandBufferTaskRunner>; + + ~CommandBufferTaskRunner(); + + // Run one command buffer task from a scheduled command buffer. + // When there isn't any command buffer task, and if the |block| is false, + // this function will return false immediately, otherwise, this function + // will be blocked until a task is available for executing. + bool RunOneTaskInternalLocked(); + + // Post a task to the main thread to execute tasks in |driver_map_|, if it is + // necessary. + void ScheduleTaskIfNecessaryLocked(); + + // The callback function for executing tasks in |driver_map_|. + void RunCommandBufferTask(); + + base::ThreadChecker thread_checker_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + typedef std::deque<TaskCallback> TaskQueue; + typedef std::map<const CommandBufferDriver*, TaskQueue> DriverMap; + DriverMap driver_map_; + bool need_post_task_; + + // The access lock for |driver_map_| and |need_post_task_|. + base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(CommandBufferTaskRunner); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_COMMAND_BUFFER_TASK_RUNNER_H_ diff --git a/chromium/components/mus/gles2/gl_surface_adapter.cc b/chromium/components/mus/gles2/gl_surface_adapter.cc new file mode 100644 index 00000000000..df1761b5854 --- /dev/null +++ b/chromium/components/mus/gles2/gl_surface_adapter.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gles2/gl_surface_adapter.h" + +#include "base/bind.h" + +namespace mus { + +GLSurfaceAdapterMus::GLSurfaceAdapterMus(scoped_refptr<gl::GLSurface> surface) + : gl::GLSurfaceAdapter(surface.get()), + surface_(surface), + weak_ptr_factory_(this) {} + +GLSurfaceAdapterMus::~GLSurfaceAdapterMus() {} + +void GLSurfaceAdapterMus::SwapBuffersAsync( + const GLSurface::SwapCompletionCallback& callback) { + gl::GLSurfaceAdapter::SwapBuffersAsync( + base::Bind(&GLSurfaceAdapterMus::WrappedCallbackForSwapBuffersAsync, + weak_ptr_factory_.GetWeakPtr(), callback)); +} + +void GLSurfaceAdapterMus::PostSubBufferAsync( + int x, + int y, + int width, + int height, + const GLSurface::SwapCompletionCallback& callback) { + gl::GLSurfaceAdapter::PostSubBufferAsync( + x, y, width, height, + base::Bind(&GLSurfaceAdapterMus::WrappedCallbackForSwapBuffersAsync, + weak_ptr_factory_.GetWeakPtr(), callback)); +} + +void GLSurfaceAdapterMus::CommitOverlayPlanesAsync( + const GLSurface::SwapCompletionCallback& callback) { + gl::GLSurfaceAdapter::CommitOverlayPlanesAsync( + base::Bind(&GLSurfaceAdapterMus::WrappedCallbackForSwapBuffersAsync, + weak_ptr_factory_.GetWeakPtr(), callback)); +} + +void GLSurfaceAdapterMus::WrappedCallbackForSwapBuffersAsync( + const gl::GLSurface::SwapCompletionCallback& original_callback, + gfx::SwapResult result) { + if (!adapter_callback_.is_null()) + adapter_callback_.Run(result); + original_callback.Run(result); +} + +void GLSurfaceAdapterMus::SetGpuCompletedSwapBuffersCallback( + gl::GLSurface::SwapCompletionCallback callback) { + adapter_callback_ = std::move(callback); +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/gl_surface_adapter.h b/chromium/components/mus/gles2/gl_surface_adapter.h new file mode 100644 index 00000000000..98da7530915 --- /dev/null +++ b/chromium/components/mus/gles2/gl_surface_adapter.h @@ -0,0 +1,71 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GLES2_GL_SURFACE_ADAPTER_H_ +#define COMPONENTS_MUS_GLES2_GL_SURFACE_ADAPTER_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "ui/gfx/swap_result.h" +#include "ui/gl/gl_surface.h" + +namespace mus { + +// Wraps a GLSurface such that there is a real GLSurface instance acting as +// delegate. Implements the GLSurface interface. The |gl::GLSurfaceAdapter| +// superclass provides a default implementation that forwards all GLSurface +// methods to the provided delegate. |GLSurfaceAdapterMus| overrides only the +// necessary methods to augment the completion callback argument to +// |SwapBuffersAsync|, |PostSubBufferAsync| and |CommitOverlayPlanesAsync| with +// a callback provided with the |SetGpuCompletedSwapBuffersCallback| method. +class GLSurfaceAdapterMus : public gl::GLSurfaceAdapter { + public: + // Creates a new |GLSurfaceAdapterMus| that delegates to |surface|. + explicit GLSurfaceAdapterMus(scoped_refptr<gl::GLSurface> surface); + + // gl::GLSurfaceAdapter. + void SwapBuffersAsync( + const gl::GLSurface::SwapCompletionCallback& callback) override; + void PostSubBufferAsync( + int x, + int y, + int width, + int height, + const gl::GLSurface::SwapCompletionCallback& callback) override; + void CommitOverlayPlanesAsync( + const gl::GLSurface::SwapCompletionCallback& callback) override; + + // Set an additional callback to run on SwapBuffers completion. + void SetGpuCompletedSwapBuffersCallback( + gl::GLSurface::SwapCompletionCallback callback); + + private: + ~GLSurfaceAdapterMus() override; + + // We wrap the callback given to the |gl::GLSurfaceAdapter| overrides above + // with an invocation of this function. It invokes |adapter_callback_| and the + // original |original_callback| provided by the original invoker of + // |SwapBuffersAsync| etc. + void WrappedCallbackForSwapBuffersAsync( + const gl::GLSurface::SwapCompletionCallback& original_callback, + gfx::SwapResult result); + + gl::GLSurface::SwapCompletionCallback adapter_callback_; + + // gl::GLSurfaceAdapter doesn't own the delegate surface so keep a reference + // here. + scoped_refptr<gl::GLSurface> surface_; + + base::WeakPtrFactory<GLSurfaceAdapterMus> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(GLSurfaceAdapterMus); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_GL_SURFACE_ADAPTER_H_ diff --git a/chromium/components/mus/gles2/gpu_impl.cc b/chromium/components/mus/gles2/gpu_impl.cc new file mode 100644 index 00000000000..7ae0b6d0c25 --- /dev/null +++ b/chromium/components/mus/gles2/gpu_impl.cc @@ -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. + +#include "components/mus/gles2/gpu_impl.h" + +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/gles2/command_buffer_impl.h" + +namespace mus { + +GpuImpl::GpuImpl(mojo::InterfaceRequest<Gpu> request, + const scoped_refptr<GpuState>& state) + : binding_(this, std::move(request)), state_(state) {} + +GpuImpl::~GpuImpl() {} + +void GpuImpl::CreateOffscreenGLES2Context( + mojo::InterfaceRequest<mojom::CommandBuffer> request) { + new CommandBufferImpl(std::move(request), state_); +} + +void GpuImpl::GetGpuInfo(const GetGpuInfoCallback& callback) { + callback.Run(mojom::GpuInfo::From<gpu::GPUInfo>(state_->gpu_info())); +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/gpu_impl.h b/chromium/components/mus/gles2/gpu_impl.h new file mode 100644 index 00000000000..73fb66ed27a --- /dev/null +++ b/chromium/components/mus/gles2/gpu_impl.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 COMPONENTS_MUS_GLES2_GPU_IMPL_H_ +#define COMPONENTS_MUS_GLES2_GPU_IMPL_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread.h" +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "components/mus/public/interfaces/gpu.mojom.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" + +namespace mus { + +class GpuImpl : public mojom::Gpu { + public: + GpuImpl(mojo::InterfaceRequest<mojom::Gpu> request, + const scoped_refptr<GpuState>& state); + ~GpuImpl() override; + + private: + void CreateOffscreenGLES2Context(mojo::InterfaceRequest<mojom::CommandBuffer> + command_buffer_request) override; + void GetGpuInfo(const GetGpuInfoCallback& callback) override; + + mojo::StrongBinding<Gpu> binding_; + scoped_refptr<GpuState> state_; + + DISALLOW_COPY_AND_ASSIGN(GpuImpl); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_GPU_IMPL_H_ diff --git a/chromium/components/mus/gles2/gpu_memory_tracker.cc b/chromium/components/mus/gles2/gpu_memory_tracker.cc new file mode 100644 index 00000000000..0d9f12b989e --- /dev/null +++ b/chromium/components/mus/gles2/gpu_memory_tracker.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 "components/mus/gles2/gpu_memory_tracker.h" + +namespace mus { + +GpuMemoryTracker::GpuMemoryTracker() {} + +void GpuMemoryTracker::TrackMemoryAllocatedChange( + size_t old_size, + size_t new_size) {} + +bool GpuMemoryTracker::EnsureGPUMemoryAvailable(size_t size_needed) { + return true; +} + +uint64_t GpuMemoryTracker::ClientTracingId() const { + return 0u; +} + +int GpuMemoryTracker::ClientId() const { + return 0; +} + +uint64_t GpuMemoryTracker::ShareGroupTracingGUID() const { + // TODO(rjkroege): This should return a GUID which identifies the share group + // we are tracking memory for. This GUID should correspond to the GUID of the + // first command buffer in the share group. + return 0; +} + +GpuMemoryTracker::~GpuMemoryTracker() {} + +} // namespace mus diff --git a/chromium/components/mus/gles2/gpu_memory_tracker.h b/chromium/components/mus/gles2/gpu_memory_tracker.h new file mode 100644 index 00000000000..925ca978d19 --- /dev/null +++ b/chromium/components/mus/gles2/gpu_memory_tracker.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 COMPONENTS_MUS_GLES2_GPU_MEMORY_TRACKER_H_ +#define COMPONENTS_MUS_GLES2_GPU_MEMORY_TRACKER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "gpu/command_buffer/service/memory_tracking.h" + +namespace mus { + +// TODO(fsamuel, rjkroege): This is a stub implementation that needs to be +// completed for proper memory tracking. +class GpuMemoryTracker : public gpu::gles2::MemoryTracker { + public: + GpuMemoryTracker(); + + // gpu::MemoryTracker implementation: + void TrackMemoryAllocatedChange( + size_t old_size, + size_t new_size) override; + bool EnsureGPUMemoryAvailable(size_t size_needed) override; + uint64_t ClientTracingId() const override; + int ClientId() const override; + uint64_t ShareGroupTracingGUID() const override; + + private: + ~GpuMemoryTracker() override; + + DISALLOW_COPY_AND_ASSIGN(GpuMemoryTracker); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_GPU_MEMORY_TRACKER_H_ diff --git a/chromium/components/mus/gles2/gpu_state.cc b/chromium/components/mus/gles2/gpu_state.cc new file mode 100644 index 00000000000..ce00ae30af9 --- /dev/null +++ b/chromium/components/mus/gles2/gpu_state.cc @@ -0,0 +1,82 @@ +// 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 "components/mus/gles2/gpu_state.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "gpu/config/gpu_info_collector.h" +#include "ui/gl/gl_implementation.h" +#include "ui/gl/init/gl_factory.h" + +#if defined(USE_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#endif + +namespace mus { + +GpuState::GpuState() + : gpu_thread_("gpu_thread"), + control_thread_("gpu_command_buffer_control"), + gpu_driver_bug_workarounds_(base::CommandLine::ForCurrentProcess()), + hardware_rendering_available_(false) { + base::ThreadRestrictions::ScopedAllowWait allow_wait; + gpu_thread_.Start(); + control_thread_.Start(); + control_thread_task_runner_ = control_thread_.task_runner(); + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + gpu_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&GpuState::InitializeOnGpuThread, + base::Unretained(this), &event)); + event.Wait(); +} + +GpuState::~GpuState() {} + +void GpuState::StopThreads() { + control_thread_.Stop(); + gpu_thread_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&GpuState::DestroyGpuSpecificStateOnGpuThread, this)); + gpu_thread_.Stop(); +} + +void GpuState::InitializeOnGpuThread(base::WaitableEvent* event) { +#if defined(USE_OZONE) + ui::OzonePlatform::InitializeForGPU(); +#endif + hardware_rendering_available_ = gl::init::InitializeGLOneOff(); + command_buffer_task_runner_ = new CommandBufferTaskRunner; + driver_manager_.reset(new CommandBufferDriverManager); + sync_point_manager_.reset(new gpu::SyncPointManager(true)); + share_group_ = new gl::GLShareGroup; + mailbox_manager_ = new gpu::gles2::MailboxManagerImpl; + + // TODO(penghuang): investigate why gpu::CollectBasicGraphicsInfo() failed on + // windows remote desktop. + const gl::GLImplementation impl = gl::GetGLImplementation(); + if (impl != gl::kGLImplementationNone && + impl != gl::kGLImplementationOSMesaGL && + impl != gl::kGLImplementationMockGL) { + gpu::CollectInfoResult result = gpu::CollectBasicGraphicsInfo(&gpu_info_); + LOG_IF(ERROR, result != gpu::kCollectInfoSuccess) + << "Collect basic graphics info failed!"; + } + if (impl != gl::kGLImplementationNone) { + gpu::CollectInfoResult result = gpu::CollectContextGraphicsInfo(&gpu_info_); + LOG_IF(ERROR, result != gpu::kCollectInfoSuccess) + << "Collect context graphics info failed!"; + } + event->Signal(); + +} + +void GpuState::DestroyGpuSpecificStateOnGpuThread() { + driver_manager_.reset(); +} + +} // namespace mus diff --git a/chromium/components/mus/gles2/gpu_state.h b/chromium/components/mus/gles2/gpu_state.h new file mode 100644 index 00000000000..f30d485bfc7 --- /dev/null +++ b/chromium/components/mus/gles2/gpu_state.h @@ -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. + +#ifndef COMPONENTS_MUS_GLES2_GPU_STATE_H_ +#define COMPONENTS_MUS_GLES2_GPU_STATE_H_ + +#include <memory> + +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread.h" +#include "components/mus/gles2/command_buffer_driver_manager.h" +#include "components/mus/gles2/command_buffer_task_runner.h" +#include "gpu/command_buffer/service/gpu_preferences.h" +#include "gpu/command_buffer/service/mailbox_manager_impl.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "gpu/config/gpu_driver_bug_workarounds.h" +#include "gpu/config/gpu_info.h" +#include "ui/gl/gl_share_group.h" + +namespace { +class WaitableEvent; +} + +namespace mus { + +// 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::RefCountedThreadSafe<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_; + } + + void StopThreads(); + + const gpu::GpuPreferences& gpu_preferences() const { + return gpu_preferences_; + } + + const gpu::GpuDriverBugWorkarounds& gpu_driver_bug_workarounds() const { + return gpu_driver_bug_workarounds_; + } + + CommandBufferTaskRunner* command_buffer_task_runner() const { + return command_buffer_task_runner_.get(); + } + + CommandBufferDriverManager* driver_manager() const { + return driver_manager_.get(); + } + + // These objects are intended to be used on the "driver" thread (i.e., the + // thread on which GpuImpl instances are created). + gl::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(); + } + + const gpu::GPUInfo& gpu_info() const { + return gpu_info_; + } + + bool HardwareRenderingAvailable() const { + return hardware_rendering_available_; + } + + private: + friend class base::RefCountedThreadSafe<GpuState>; + ~GpuState(); + + void InitializeOnGpuThread(base::WaitableEvent* event); + + void DestroyGpuSpecificStateOnGpuThread(); + + // |gpu_thread_| is for executing OS GL calls. + base::Thread gpu_thread_; + // |control_thread_| is for mojo incoming calls of CommandBufferImpl. + base::Thread control_thread_; + + // Same as control_thread_->task_runner(). The TaskRunner is cached as it may + // be needed during shutdown. + scoped_refptr<base::SingleThreadTaskRunner> control_thread_task_runner_; + + gpu::GpuPreferences gpu_preferences_; + const gpu::GpuDriverBugWorkarounds gpu_driver_bug_workarounds_; + scoped_refptr<CommandBufferTaskRunner> command_buffer_task_runner_; + std::unique_ptr<CommandBufferDriverManager> driver_manager_; + std::unique_ptr<gpu::SyncPointManager> sync_point_manager_; + scoped_refptr<gl::GLShareGroup> share_group_; + scoped_refptr<gpu::gles2::MailboxManager> mailbox_manager_; + gpu::GPUInfo gpu_info_; + bool hardware_rendering_available_; +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_GPU_STATE_H_ diff --git a/chromium/components/mus/gles2/ozone_gpu_memory_buffer.cc b/chromium/components/mus/gles2/ozone_gpu_memory_buffer.cc new file mode 100644 index 00000000000..c53b51f5ade --- /dev/null +++ b/chromium/components/mus/gles2/ozone_gpu_memory_buffer.cc @@ -0,0 +1,115 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gles2/ozone_gpu_memory_buffer.h" + +#include "ui/gfx/buffer_format_util.h" +#include "ui/ozone/public/client_native_pixmap.h" +#include "ui/ozone/public/client_native_pixmap_factory.h" +#include "ui/ozone/public/native_pixmap.h" +#include "ui/ozone/public/ozone_platform.h" +#include "ui/ozone/public/surface_factory_ozone.h" + +namespace mus { + +OzoneGpuMemoryBuffer::OzoneGpuMemoryBuffer( + gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format, + std::unique_ptr<ui::ClientNativePixmap> client_pixmap, + scoped_refptr<ui::NativePixmap> native_pixmap) + : GpuMemoryBufferImpl(id, size, format), + client_pixmap_(std::move(client_pixmap)), + native_pixmap_(native_pixmap) {} + +OzoneGpuMemoryBuffer::~OzoneGpuMemoryBuffer() { + DCHECK(!mapped_); +} + +// static +OzoneGpuMemoryBuffer* OzoneGpuMemoryBuffer::FromClientBuffer( + ClientBuffer buffer) { + return reinterpret_cast<OzoneGpuMemoryBuffer*>(buffer); +} + +// static +std::unique_ptr<gfx::GpuMemoryBuffer> +OzoneGpuMemoryBuffer::CreateOzoneGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gfx::AcceleratedWidget widget) { + scoped_refptr<ui::NativePixmap> pixmap = + ui::OzonePlatform::GetInstance() + ->GetSurfaceFactoryOzone() + ->CreateNativePixmap(widget, size, format, usage); + + DCHECK(pixmap) << "need pixmap to exist!"; + + if (!pixmap.get()) { + DLOG(ERROR) << "Failed to create pixmap " << size.width() << "x" + << size.height() << " format " << static_cast<int>(format) + << ", usage " << static_cast<int>(usage); + return nullptr; + } + + // We construct a ui::NativePixmapHandle + gfx::NativePixmapHandle native_pixmap_handle = pixmap->ExportHandle(); + DCHECK(ui::ClientNativePixmapFactory::GetInstance()) + << "need me a ClientNativePixmapFactory"; + std::unique_ptr<ui::ClientNativePixmap> client_native_pixmap = + ui::ClientNativePixmapFactory::GetInstance()->ImportFromHandle( + native_pixmap_handle, size, usage); + + std::unique_ptr<OzoneGpuMemoryBuffer> nb( + new OzoneGpuMemoryBuffer(gfx::GpuMemoryBufferId(0), size, format, + std::move(client_native_pixmap), pixmap)); + return std::move(nb); +} + +bool OzoneGpuMemoryBuffer::Map() { + DCHECK(!mapped_); + if (!client_pixmap_->Map()) + return false; + mapped_ = true; + return mapped_; +} + +void* OzoneGpuMemoryBuffer::memory(size_t plane) { + DCHECK(mapped_); + DCHECK_LT(plane, gfx::NumberOfPlanesForBufferFormat(format_)); + return client_pixmap_->Map(); +} + +void OzoneGpuMemoryBuffer::Unmap() { + DCHECK(mapped_); + client_pixmap_->Unmap(); + mapped_ = false; +} + +int OzoneGpuMemoryBuffer::stride(size_t plane) const { + DCHECK_LT(plane, gfx::NumberOfPlanesForBufferFormat(format_)); + int stride; + client_pixmap_->GetStride(&stride); + return stride; +} + +gfx::GpuMemoryBufferHandle OzoneGpuMemoryBuffer::GetHandle() const { + gfx::GpuMemoryBufferHandle handle; + handle.type = gfx::OZONE_NATIVE_PIXMAP; + handle.id = id_; + return handle; +} + +gfx::GpuMemoryBufferType OzoneGpuMemoryBuffer::GetBufferType() const { + return gfx::OZONE_NATIVE_PIXMAP; +} + +#if defined(USE_OZONE) +scoped_refptr<ui::NativePixmap> OzoneGpuMemoryBuffer::GetNativePixmap() { + return native_pixmap_; +} +#endif + +} // namespace mus diff --git a/chromium/components/mus/gles2/ozone_gpu_memory_buffer.h b/chromium/components/mus/gles2/ozone_gpu_memory_buffer.h new file mode 100644 index 00000000000..c5b2ea8a24b --- /dev/null +++ b/chromium/components/mus/gles2/ozone_gpu_memory_buffer.h @@ -0,0 +1,71 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GLES2_OZONE_GPU_MEMORY_BUFFER_ +#define COMPONENTS_MUS_GLES2_OZONE_GPU_MEMORY_BUFFER_ + +#include <memory> + +#include "components/mus/common/gpu_memory_buffer_impl.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/native_widget_types.h" + +namespace ui { +class ClientNativePixmap; +class NativePixmap; +} // namespace ui + +namespace mus { + +// A not-mojo GpuMemoryBuffer implementation solely for use internally to mus +// for scanout buffers. Note that OzoneGpuMemoryBuffer is for use on the client +// (aka CC thread). +class OzoneGpuMemoryBuffer : public mus::GpuMemoryBufferImpl { + public: + ~OzoneGpuMemoryBuffer() override; + + // gfx::GpuMemoryBuffer implementation + bool Map() override; + void Unmap() override; + void* memory(size_t plane) override; + int stride(size_t plane) const override; + gfx::GpuMemoryBufferHandle GetHandle() const override; + + // Returns the type of this GpuMemoryBufferImpl. + gfx::GpuMemoryBufferType GetBufferType() const override; + scoped_refptr<ui::NativePixmap> GetNativePixmap() override; + + // Create a NativeBuffer. The implementation (mus-specific) will call directly + // into ozone to allocate the buffer. See the version in the .cc file. + static std::unique_ptr<gfx::GpuMemoryBuffer> CreateOzoneGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gfx::AcceleratedWidget widget); + + // Cribbed from content/common/gpu/client/gpu_memory_buffer_impl.h + static OzoneGpuMemoryBuffer* FromClientBuffer(ClientBuffer buffer); + + private: + // TODO(rjkroege): It is conceivable that we do not need an |id| here. It + // would seem to be a legacy of content leaking into gfx. In a mojo context, + // perhaps it should be a mojo handle instead. + OzoneGpuMemoryBuffer(gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format, + std::unique_ptr<ui::ClientNativePixmap> client_pixmap, + scoped_refptr<ui::NativePixmap> native_pixmap); + + // The real backing buffer. + // From content/common/gpu/client/gpu_memory_buffer_impl_ozone_native_pixmap.h + std::unique_ptr<ui::ClientNativePixmap> client_pixmap_; + scoped_refptr<ui::NativePixmap> native_pixmap_; + + DISALLOW_COPY_AND_ASSIGN(OzoneGpuMemoryBuffer); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GLES2_OZONE_GPU_MEMORY_BUFFER_ diff --git a/chromium/components/mus/gles2/raster_thread_helper.cc b/chromium/components/mus/gles2/raster_thread_helper.cc new file mode 100644 index 00000000000..93e37405e78 --- /dev/null +++ b/chromium/components/mus/gles2/raster_thread_helper.cc @@ -0,0 +1,27 @@ +// 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 "components/mus/gles2/raster_thread_helper.h" + +#include "base/logging.h" +#include "base/threading/simple_thread.h" +#include "cc/raster/single_thread_task_graph_runner.h" + +namespace gles2 { + +RasterThreadHelper::RasterThreadHelper() + : task_graph_runner_(new cc::SingleThreadTaskGraphRunner) { + task_graph_runner_->Start("CompositorTileWorker1", + base::SimpleThread::Options()); +} + +RasterThreadHelper::~RasterThreadHelper() { + task_graph_runner_->Shutdown(); +} + +cc::TaskGraphRunner* RasterThreadHelper::task_graph_runner() { + return task_graph_runner_.get(); +} + +} // namespace gles2 diff --git a/chromium/components/mus/gles2/raster_thread_helper.h b/chromium/components/mus/gles2/raster_thread_helper.h new file mode 100644 index 00000000000..a43dfab0810 --- /dev/null +++ b/chromium/components/mus/gles2/raster_thread_helper.h @@ -0,0 +1,34 @@ +// 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 COMPONENTS_MUS_GLES2_RASTER_THREAD_HELPER_H_ +#define COMPONENTS_MUS_GLES2_RASTER_THREAD_HELPER_H_ + +#include <memory> + +#include "base/macros.h" + +namespace cc { +class TaskGraphRunner; +class SingleThreadTaskGraphRunner; +} + +namespace gles2 { + +class RasterThreadHelper { + public: + RasterThreadHelper(); + ~RasterThreadHelper(); + + cc::TaskGraphRunner* task_graph_runner(); + + private: + std::unique_ptr<cc::SingleThreadTaskGraphRunner> task_graph_runner_; + + DISALLOW_COPY_AND_ASSIGN(RasterThreadHelper); +}; + +} // namespace gles2 + +#endif // COMPONENTS_MUS_GLES2_RASTER_THREAD_HELPER_H_ diff --git a/chromium/components/mus/gpu/DEPS b/chromium/components/mus/gpu/DEPS new file mode 100644 index 00000000000..004a2b9f68f --- /dev/null +++ b/chromium/components/mus/gpu/DEPS @@ -0,0 +1,21 @@ +include_rules = [ + "+gpu/command_buffer", + "+gpu/config", + "+gpu/ipc/common", + "+gpu/ipc/client/gpu_channel_host.h", + "+gpu/ipc/service", + "+media/gpu/ipc/service", +] + +specific_include_rules = { + "gpu_service_mus\.h": [ + # A GpuChannelHost is used by mus locally. + "+gpu/ipc/client/gpu_channel_host.h", + ], + + "mus_gpu_memory_buffer_manager\.cc": [ + # The GpuMemoryBufferManager implementation used by mus locally. + "+gpu/ipc/client/gpu_memory_buffer_impl.h", + "+gpu/ipc/client/gpu_memory_buffer_impl_shared_memory.h", + ] +} diff --git a/chromium/components/mus/gpu/OWNERS b/chromium/components/mus/gpu/OWNERS new file mode 100644 index 00000000000..1f1af6bcc54 --- /dev/null +++ b/chromium/components/mus/gpu/OWNERS @@ -0,0 +1,2 @@ +fsamuel@chromium.org +rjkroege@chromium.org diff --git a/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h new file mode 100644 index 00000000000..ee377aadc49 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h @@ -0,0 +1,27 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_DELEGATE_H_ +#define COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_DELEGATE_H_ + +#include "cc/surfaces/surface_id.h" + +namespace mus { +namespace gpu { + +class CompositorFrameSinkImpl; + +// A CompositorFrameSinkDelegate decouples CompositorFrameSinks from their +// factories enabling them to be unit tested. +class CompositorFrameSinkDelegate { + public: + virtual void CompositorFrameSinkConnectionLost(int sink_id) = 0; + + virtual cc::SurfaceId GenerateSurfaceId() = 0; +}; + +} // namespace gpu +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_DELEGATE_H_ diff --git a/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.cc b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.cc new file mode 100644 index 00000000000..5c52940a984 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.cc @@ -0,0 +1,46 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.h" + +#include "base/memory/ptr_util.h" +#include "cc/surfaces/surface_id.h" +#include "components/mus/gpu/display_compositor/compositor_frame_sink_impl.h" + +namespace mus { +namespace gpu { + +CompositorFrameSinkFactoryImpl::CompositorFrameSinkFactoryImpl( + uint32_t client_id, + mojo::InterfaceRequest<mojom::CompositorFrameSinkFactory> request, + const scoped_refptr<SurfacesState>& surfaces_state) + : client_id_(client_id), + surfaces_state_(surfaces_state), + allocator_(client_id), + binding_(this, std::move(request)) {} + +CompositorFrameSinkFactoryImpl::~CompositorFrameSinkFactoryImpl() {} + +void CompositorFrameSinkFactoryImpl::CompositorFrameSinkConnectionLost( + int local_id) { + sinks_.erase(local_id); +} + +cc::SurfaceId CompositorFrameSinkFactoryImpl::GenerateSurfaceId() { + return allocator_.GenerateId(); +} + +void CompositorFrameSinkFactoryImpl::CreateCompositorFrameSink( + uint32_t local_id, + uint64_t nonce, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client) { + // TODO(fsamuel): Use nonce once patch lands: + // https://codereview.chromium.org/1996783002/ + sinks_[local_id] = base::WrapUnique(new CompositorFrameSinkImpl( + this, local_id, surfaces_state_, std::move(sink), std::move(client))); +} + +} // namespace gpu +} // namespace mus diff --git a/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.h b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.h new file mode 100644 index 00000000000..50aa0a5a0d3 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_factory_impl.h @@ -0,0 +1,55 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_FACTORY_IMPL_H_ +#define COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_FACTORY_IMPL_H_ + +#include "cc/surfaces/surface_id_allocator.h" +#include "components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h" +#include "components/mus/public/interfaces/gpu/display_compositor.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mus { +namespace gpu { + +class CompositorFrameSinkImpl; + +class CompositorFrameSinkFactoryImpl : public mojom::CompositorFrameSinkFactory, + public CompositorFrameSinkDelegate { + public: + CompositorFrameSinkFactoryImpl( + uint32_t client_id, + mojo::InterfaceRequest<mojom::CompositorFrameSinkFactory> request, + const scoped_refptr<SurfacesState>& surfaces_state); + ~CompositorFrameSinkFactoryImpl() override; + + uint32_t client_id() const { return client_id_; } + + void CompositorFrameSinkConnectionLost(int local_id) override; + cc::SurfaceId GenerateSurfaceId() override; + + // mojom::CompositorFrameSinkFactory implementation. + void CreateCompositorFrameSink( + uint32_t local_id, + uint64_t nonce, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client) override; + + private: + const uint32_t client_id_; + scoped_refptr<SurfacesState> surfaces_state_; + cc::SurfaceIdAllocator allocator_; + using CompositorFrameSinkMap = + std::map<uint32_t, std::unique_ptr<CompositorFrameSinkImpl>>; + CompositorFrameSinkMap sinks_; + mojo::StrongBinding<mojom::CompositorFrameSinkFactory> binding_; + + DISALLOW_COPY_AND_ASSIGN(CompositorFrameSinkFactoryImpl); +}; + +} // namespace gpu +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_FACTORY_IMPL_H_ diff --git a/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.cc b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.cc new file mode 100644 index 00000000000..80bb15197df --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.cc @@ -0,0 +1,112 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/display_compositor/compositor_frame_sink_impl.h" + +#include "cc/ipc/compositor_frame.mojom.h" +#include "cc/surfaces/surface_factory.h" +#include "components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h" + +namespace mus { +namespace gpu { + +namespace { + +void CallCallback( + const mojom::CompositorFrameSink::SubmitCompositorFrameCallback& callback, + cc::SurfaceDrawStatus draw_status) { + callback.Run(static_cast<mojom::CompositorFrameDrawStatus>(draw_status)); +} +} + +CompositorFrameSinkImpl::CompositorFrameSinkImpl( + CompositorFrameSinkDelegate* delegate, + int sink_id, + const scoped_refptr<SurfacesState>& surfaces_state, + mojo::InterfaceRequest<mojom::CompositorFrameSink> request, + mojom::CompositorFrameSinkClientPtr client) + : delegate_(delegate), + surfaces_state_(surfaces_state), + sink_id_(sink_id), + begin_frame_source_(nullptr), + needs_begin_frame_(false), + factory_(surfaces_state->manager(), this), + client_(std::move(client)), + binding_(this, std::move(request)) { + DCHECK(delegate_); + binding_.set_connection_error_handler(base::Bind( + &CompositorFrameSinkImpl::OnConnectionLost, base::Unretained(this))); +} + +CompositorFrameSinkImpl::~CompositorFrameSinkImpl() {} + +void CompositorFrameSinkImpl::SetNeedsBeginFrame(bool needs_begin_frame) { + if (needs_begin_frame_ == needs_begin_frame) + return; + + needs_begin_frame_ = needs_begin_frame; + if (begin_frame_source_) { + if (needs_begin_frame_) + begin_frame_source_->AddObserver(this); + else + begin_frame_source_->RemoveObserver(this); + } +} + +void CompositorFrameSinkImpl::SubmitCompositorFrame( + cc::CompositorFrame compositor_frame, + const SubmitCompositorFrameCallback& callback) { + gfx::Size frame_size = + compositor_frame.delegated_frame_data->render_pass_list.back() + ->output_rect.size(); + if (frame_size.IsEmpty() || frame_size != last_submitted_frame_size_) { + if (!surface_id_.is_null()) + factory_.Destroy(surface_id_); + // TODO(fsamuel): The allocator should live on CompositorFrameSinkFactory. + surface_id_ = delegate_->GenerateSurfaceId(); + factory_.Create(surface_id_); + last_submitted_frame_size_ = frame_size; + } + factory_.SubmitCompositorFrame(surface_id_, std::move(compositor_frame), + base::Bind(&CallCallback, callback)); +} + +void CompositorFrameSinkImpl::ReturnResources( + const cc::ReturnedResourceArray& resources) { + if (!client_) + return; + + client_->ReturnResources(mojo::Array<cc::ReturnedResource>::From(resources)); +} + +void CompositorFrameSinkImpl::WillDrawSurface(const cc::SurfaceId& surface_id, + const gfx::Rect& damage_rect) { + NOTIMPLEMENTED(); +} + +void CompositorFrameSinkImpl::SetBeginFrameSource( + cc::BeginFrameSource* begin_frame_source) { + begin_frame_source_ = begin_frame_source; +} + +void CompositorFrameSinkImpl::OnBeginFrame(const cc::BeginFrameArgs& args) { + // TODO(fsamuel): Implement this. +} + +const cc::BeginFrameArgs& CompositorFrameSinkImpl::LastUsedBeginFrameArgs() + const { + // TODO(fsamuel): Implement this. + return last_used_begin_frame_args_; +} + +void CompositorFrameSinkImpl::OnBeginFrameSourcePausedChanged(bool paused) { + // TODO(fsamuel): Implement this. +} + +void CompositorFrameSinkImpl::OnConnectionLost() { + delegate_->CompositorFrameSinkConnectionLost(sink_id_); +} + +} // namespace gpu +} // namespace mus diff --git a/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.h b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.h new file mode 100644 index 00000000000..e402a7ce2ce --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/compositor_frame_sink_impl.h @@ -0,0 +1,74 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_IMPL_H_ +#define COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "cc/scheduler/begin_frame_source.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_factory_client.h" +#include "cc/surfaces/surface_id.h" +#include "components/mus/public/interfaces/gpu/display_compositor.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mus { +namespace gpu { + +class CompositorFrameSinkDelegate; + +// A client presents visuals to screen by submitting CompositorFrames to a +// CompositorFrameSink. +class CompositorFrameSinkImpl : public cc::SurfaceFactoryClient, + public mojom::CompositorFrameSink, + public cc::BeginFrameObserver { + public: + CompositorFrameSinkImpl( + CompositorFrameSinkDelegate* delegate, + int sink_id, + const scoped_refptr<SurfacesState>& surfaces_state, + mojo::InterfaceRequest<mojom::CompositorFrameSink> request, + mojom::CompositorFrameSinkClientPtr client); + ~CompositorFrameSinkImpl() override; + + // mojom::CompositorFrameSink implementation. + void SetNeedsBeginFrame(bool needs_begin_frame) override; + void SubmitCompositorFrame( + cc::CompositorFrame compositor_frame, + const SubmitCompositorFrameCallback& callback) override; + + private: + // SurfaceFactoryClient implementation. + void ReturnResources(const cc::ReturnedResourceArray& resources) override; + void WillDrawSurface(const cc::SurfaceId& surface_id, + const gfx::Rect& damage_rect) override; + void SetBeginFrameSource(cc::BeginFrameSource* begin_frame_source) override; + + // BeginFrameObserver implementation. + void OnBeginFrame(const cc::BeginFrameArgs& args) override; + const cc::BeginFrameArgs& LastUsedBeginFrameArgs() const override; + void OnBeginFrameSourcePausedChanged(bool paused) override; + + void OnConnectionLost(); + + CompositorFrameSinkDelegate* const delegate_; + scoped_refptr<SurfacesState> surfaces_state_; + const int sink_id_; + cc::BeginFrameSource* begin_frame_source_; + bool needs_begin_frame_; + cc::BeginFrameArgs last_used_begin_frame_args_; + cc::SurfaceFactory factory_; + cc::SurfaceId surface_id_; + gfx::Size last_submitted_frame_size_; + mojom::CompositorFrameSinkClientPtr client_; + mojo::Binding<mojom::CompositorFrameSink> binding_; + + DISALLOW_COPY_AND_ASSIGN(CompositorFrameSinkImpl); +}; + +} // namespace gpu +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_COMPOSITOR_FRAME_SINK_IMPL_H_ diff --git a/chromium/components/mus/gpu/display_compositor/display_compositor_impl.cc b/chromium/components/mus/gpu/display_compositor/display_compositor_impl.cc new file mode 100644 index 00000000000..cf9a24df684 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/display_compositor_impl.cc @@ -0,0 +1,28 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/display_compositor/display_compositor_impl.h" + +#include "components/mus/gpu/display_compositor/display_impl.h" + +namespace mus { +namespace gpu { + +DisplayCompositorImpl::DisplayCompositorImpl( + mojo::InterfaceRequest<mojom::DisplayCompositor> request) + : binding_(this, std::move(request)) {} + +DisplayCompositorImpl::~DisplayCompositorImpl() {} + +void DisplayCompositorImpl::CreateDisplay( + int accelerated_widget, + mojo::InterfaceRequest<mojom::Display> display, + mojom::DisplayHostPtr host, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client) { + NOTIMPLEMENTED(); +} + +} // namespace gpu +} // namespace mus diff --git a/chromium/components/mus/gpu/display_compositor/display_compositor_impl.h b/chromium/components/mus/gpu/display_compositor/display_compositor_impl.h new file mode 100644 index 00000000000..237cc108b7c --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/display_compositor_impl.h @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_COMPOSITOR_IMPL_H_ +#define COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_COMPOSITOR_IMPL_H_ + +#include "components/mus/public/interfaces/gpu/display_compositor.mojom.h" +#include "components/mus/public/interfaces/gpu/display_compositor_host.mojom.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mus { +namespace gpu { + +class DisplayCompositorImpl : public mojom::DisplayCompositor { + public: + explicit DisplayCompositorImpl( + mojo::InterfaceRequest<mojom::DisplayCompositor> request); + ~DisplayCompositorImpl() override; + + // mojom::DisplayCompositor implementation. + void CreateDisplay(int accelerated_widget, + mojo::InterfaceRequest<mojom::Display> display, + mojom::DisplayHostPtr host, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client) override; + + private: + mojo::StrongBinding<mojom::DisplayCompositor> binding_; + + DISALLOW_COPY_AND_ASSIGN(DisplayCompositorImpl); +}; + +} // namespace gpu +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_COMPOSITOR_IMPL_H_ diff --git a/chromium/components/mus/gpu/display_compositor/display_impl.cc b/chromium/components/mus/gpu/display_compositor/display_impl.cc new file mode 100644 index 00000000000..274c67acbb1 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/display_impl.cc @@ -0,0 +1,43 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/display_compositor/display_impl.h" + +#include "components/mus/gpu/display_compositor/compositor_frame_sink_impl.h" + +namespace mus { +namespace gpu { + +DisplayImpl::DisplayImpl( + int accelerated_widget, + mojo::InterfaceRequest<mojom::Display> display, + mojom::DisplayHostPtr host, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client, + const scoped_refptr<SurfacesState>& surfaces_state) + : binding_(this, std::move(display)) { + const uint32_t client_id = 1; + sink_.reset(new CompositorFrameSinkImpl(this, client_id, surfaces_state, + std::move(sink), std::move(client))); +} + +DisplayImpl::~DisplayImpl() {} + +void DisplayImpl::CreateClient( + uint32_t client_id, + mojo::InterfaceRequest<mojom::DisplayClient> client) { + NOTIMPLEMENTED(); +} + +void DisplayImpl::CompositorFrameSinkConnectionLost(int sink_id) { + NOTIMPLEMENTED(); +} + +cc::SurfaceId DisplayImpl::GenerateSurfaceId() { + NOTIMPLEMENTED(); + return cc::SurfaceId(); +} + +} // namespace gpu +} // namespace mus diff --git a/chromium/components/mus/gpu/display_compositor/display_impl.h b/chromium/components/mus/gpu/display_compositor/display_impl.h new file mode 100644 index 00000000000..c667bcbde03 --- /dev/null +++ b/chromium/components/mus/gpu/display_compositor/display_impl.h @@ -0,0 +1,44 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_IMPL_H_ +#define COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_IMPL_H_ + +#include "components/mus/gpu/display_compositor/compositor_frame_sink_delegate.h" +#include "components/mus/public/interfaces/gpu/display_compositor_host.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace gpu { + +class DisplayImpl : public mojom::Display, public CompositorFrameSinkDelegate { + public: + DisplayImpl(int accelerated_widget, + mojo::InterfaceRequest<mojom::Display> display, + mojom::DisplayHostPtr host, + mojo::InterfaceRequest<mojom::CompositorFrameSink> sink, + mojom::CompositorFrameSinkClientPtr client, + const scoped_refptr<SurfacesState>& surfaces_state); + ~DisplayImpl() override; + + // mojom::Display implementation. + void CreateClient( + uint32_t client_id, + mojo::InterfaceRequest<mojom::DisplayClient> client) override; + + private: + // CompositorFrameSinkDelegate implementation: + void CompositorFrameSinkConnectionLost(int sink_id) override; + cc::SurfaceId GenerateSurfaceId() override; + + std::unique_ptr<CompositorFrameSinkImpl> sink_; + mojo::Binding<mojom::Display> binding_; + DISALLOW_COPY_AND_ASSIGN(DisplayImpl); +}; + +} // namespace gpu +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_DISPLAY_COMPOSITOR_DISPLAY_IMPL_H_ diff --git a/chromium/components/mus/gpu/gpu_service_impl.cc b/chromium/components/mus/gpu/gpu_service_impl.cc new file mode 100644 index 00000000000..9ea83820ffe --- /dev/null +++ b/chromium/components/mus/gpu/gpu_service_impl.cc @@ -0,0 +1,63 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/gpu_service_impl.h" + +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/gpu/gpu_service_mus.h" +#include "services/shell/public/cpp/connection.h" + +namespace mus { + +namespace { + +void EstablishGpuChannelDone( + const mojom::GpuService::EstablishGpuChannelCallback& callback, + int32_t client_id, + const IPC::ChannelHandle& channel_handle) { + // TODO(penghuang): Send the real GPUInfo to the client. + callback.Run(client_id, mojom::ChannelHandle::From(channel_handle), + mojom::GpuInfo::From<gpu::GPUInfo>(gpu::GPUInfo())); +} +} + +GpuServiceImpl::GpuServiceImpl( + mojo::InterfaceRequest<mojom::GpuService> request, + shell::Connection* connection) + : binding_(this, std::move(request)) {} + +GpuServiceImpl::~GpuServiceImpl() {} + +void GpuServiceImpl::EstablishGpuChannel( + const mojom::GpuService::EstablishGpuChannelCallback& callback) { + GpuServiceMus* service = GpuServiceMus::GetInstance(); + // TODO(penghuang): crbug.com/617415 figure out how to generate a meaningful + // tracing id. + const uint64_t client_tracing_id = 0; + // TODO(penghuang): windows server may want to control those flags. + // Add a private interface for windows server. + const bool preempts = false; + const bool allow_view_command_buffers = false; + const bool allow_real_time_streams = false; + service->EstablishGpuChannel( + client_tracing_id, preempts, allow_view_command_buffers, + allow_real_time_streams, base::Bind(&EstablishGpuChannelDone, callback)); +} + +void GpuServiceImpl::CreateGpuMemoryBuffer( + mojom::GpuMemoryBufferIdPtr id, + const gfx::Size& size, + mojom::BufferFormat format, + mojom::BufferUsage usage, + uint64_t surface_id, + const mojom::GpuService::CreateGpuMemoryBufferCallback& callback) { + NOTIMPLEMENTED(); +} + +void GpuServiceImpl::DestroyGpuMemoryBuffer(mojom::GpuMemoryBufferIdPtr id, + const gpu::SyncToken& sync_token) { + NOTIMPLEMENTED(); +} + +} // namespace mus diff --git a/chromium/components/mus/gpu/gpu_service_impl.h b/chromium/components/mus/gpu/gpu_service_impl.h new file mode 100644 index 00000000000..9d5f66ec715 --- /dev/null +++ b/chromium/components/mus/gpu/gpu_service_impl.h @@ -0,0 +1,49 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_GPU_SERVICE_IMPL_H_ +#define COMPONENTS_MUS_GPU_GPU_SERVICE_IMPL_H_ + +#include "components/mus/public/interfaces/gpu_memory_buffer.mojom.h" +#include "components/mus/public/interfaces/gpu_service.mojom.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace shell { +class Connection; +} + +namespace mus { + +class GpuServiceImpl : public mojom::GpuService { + public: + GpuServiceImpl(mojo::InterfaceRequest<mojom::GpuService> request, + shell::Connection* connection); + ~GpuServiceImpl() override; + + // mojom::GpuService overrides: + void EstablishGpuChannel( + const mojom::GpuService::EstablishGpuChannelCallback& callback) override; + + void CreateGpuMemoryBuffer( + mojom::GpuMemoryBufferIdPtr id, + const gfx::Size& size, + mojom::BufferFormat format, + mojom::BufferUsage usage, + uint64_t surface_id, + const mojom::GpuService::CreateGpuMemoryBufferCallback& callback) + override; + + void DestroyGpuMemoryBuffer(mojom::GpuMemoryBufferIdPtr id, + const gpu::SyncToken& sync_token) override; + + private: + mojo::StrongBinding<GpuService> binding_; + + DISALLOW_COPY_AND_ASSIGN(GpuServiceImpl); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_GPU_SERVICE_IMPL_H_ diff --git a/chromium/components/mus/gpu/gpu_service_mus.cc b/chromium/components/mus/gpu/gpu_service_mus.cc new file mode 100644 index 00000000000..43ddb21ab00 --- /dev/null +++ b/chromium/components/mus/gpu/gpu_service_mus.cc @@ -0,0 +1,273 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/gpu_service_mus.h" + +#include "base/memory/shared_memory.h" +#include "base/memory/singleton.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "components/mus/gpu/mus_gpu_memory_buffer_manager.h" +#include "gpu/command_buffer/service/gpu_switches.h" +#include "gpu/command_buffer/service/sync_point_manager.h" +#include "gpu/config/gpu_info_collector.h" +#include "gpu/config/gpu_switches.h" +#include "gpu/config/gpu_util.h" +#include "gpu/ipc/common/gpu_memory_buffer_support.h" +#include "gpu/ipc/common/memory_stats.h" +#include "gpu/ipc/service/gpu_memory_buffer_factory.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_sync_message_filter.h" +#include "media/gpu/ipc/service/gpu_jpeg_decode_accelerator.h" +#include "media/gpu/ipc/service/gpu_video_decode_accelerator.h" +#include "media/gpu/ipc/service/gpu_video_encode_accelerator.h" +#include "media/gpu/ipc/service/media_service.h" +#include "ui/gl/gl_implementation.h" +#include "ui/gl/gl_switches.h" +#include "ui/gl/gpu_switching_manager.h" +#include "ui/gl/init/gl_factory.h" +#include "url/gurl.h" + +#if defined(USE_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#endif + +namespace mus { +namespace { + +const int kLocalGpuChannelClientId = 1; +const uint64_t kLocalGpuChannelClientTracingId = 1; + +void EstablishGpuChannelDone( + int client_id, + const IPC::ChannelHandle* channel_handle, + const GpuServiceMus::EstablishGpuChannelCallback& callback) { + callback.Run(channel_handle ? client_id : -1, *channel_handle); +} +} + +GpuServiceMus::GpuServiceMus() + : next_client_id_(kLocalGpuChannelClientId), + main_task_runner_(base::ThreadTaskRunnerHandle::Get()), + shutdown_event_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED), + gpu_thread_("GpuThread"), + io_thread_("GpuIOThread") { + Initialize(); +} + +GpuServiceMus::~GpuServiceMus() { + // Signal this event before destroying the child process. That way all + // background threads can cleanup. + // For example, in the renderer the RenderThread instances will be able to + // notice shutdown before the render process begins waiting for them to exit. + shutdown_event_.Signal(); + io_thread_.Stop(); +} + +void GpuServiceMus::EstablishGpuChannel( + uint64_t client_tracing_id, + bool preempts, + bool allow_view_command_buffers, + bool allow_real_time_streams, + const EstablishGpuChannelCallback& callback) { + DCHECK(CalledOnValidThread()); + + if (!gpu_channel_manager_) { + callback.Run(-1, IPC::ChannelHandle()); + return; + } + + const int client_id = ++next_client_id_; + IPC::ChannelHandle* channel_handle = new IPC::ChannelHandle; + gpu_thread_.task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&GpuServiceMus::EstablishGpuChannelOnGpuThread, + base::Unretained(this), client_id, client_tracing_id, preempts, + allow_view_command_buffers, allow_real_time_streams, + base::Unretained(channel_handle)), + base::Bind(&EstablishGpuChannelDone, client_id, + base::Owned(channel_handle), callback)); +} + +gfx::GpuMemoryBufferHandle GpuServiceMus::CreateGpuMemoryBuffer( + gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + int client_id, + gpu::SurfaceHandle surface_handle) { + DCHECK(CalledOnValidThread()); + return gpu_memory_buffer_factory_->CreateGpuMemoryBuffer( + id, size, format, usage, client_id, surface_handle); +} + +void GpuServiceMus::DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id, + int client_id, + const gpu::SyncToken& sync_token) { + DCHECK(CalledOnValidThread()); + + if (gpu_channel_manager_) + gpu_channel_manager_->DestroyGpuMemoryBuffer(id, client_id, sync_token); +} + +void GpuServiceMus::DidCreateOffscreenContext(const GURL& active_url) { + NOTIMPLEMENTED(); +} + +void GpuServiceMus::DidDestroyChannel(int client_id) { + media_service_->RemoveChannel(client_id); + NOTIMPLEMENTED(); +} + +void GpuServiceMus::DidDestroyOffscreenContext(const GURL& active_url) { + NOTIMPLEMENTED(); +} + +void GpuServiceMus::DidLoseContext(bool offscreen, + gpu::error::ContextLostReason reason, + const GURL& active_url) { + NOTIMPLEMENTED(); +} + +void GpuServiceMus::GpuMemoryUmaStats(const gpu::GPUMemoryUmaStats& params) { + NOTIMPLEMENTED(); +} + +void GpuServiceMus::StoreShaderToDisk(int client_id, + const std::string& key, + const std::string& shader) { + NOTIMPLEMENTED(); +} + +#if defined(OS_WIN) +void GpuServiceMus::SendAcceleratedSurfaceCreatedChildWindow( + gpu::SurfaceHandle parent_window, + gpu::SurfaceHandle child_window) { + NOTIMPLEMENTED(); +} +#endif + +void GpuServiceMus::SetActiveURL(const GURL& url) { + NOTIMPLEMENTED(); +} + +void GpuServiceMus::Initialize() { + DCHECK(CalledOnValidThread()); + base::Thread::Options thread_options(base::MessageLoop::TYPE_DEFAULT, 0); + thread_options.priority = base::ThreadPriority::NORMAL; + CHECK(gpu_thread_.StartWithOptions(thread_options)); + + thread_options = base::Thread::Options(base::MessageLoop::TYPE_IO, 0); + thread_options.priority = base::ThreadPriority::NORMAL; +#if defined(OS_ANDROID) + // TODO(reveman): Remove this in favor of setting it explicitly for each type + // of process. + thread_options.priority = base::ThreadPriority::DISPLAY; +#endif + CHECK(io_thread_.StartWithOptions(thread_options)); + + IPC::ChannelHandle channel_handle; + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + gpu_thread_.task_runner()->PostTask( + FROM_HERE, base::Bind(&GpuServiceMus::InitializeOnGpuThread, + base::Unretained(this), &channel_handle, &event)); + event.Wait(); + + gpu_memory_buffer_manager_local_.reset( + new MusGpuMemoryBufferManager(this, kLocalGpuChannelClientId)); + gpu_channel_local_ = gpu::GpuChannelHost::Create( + this, kLocalGpuChannelClientId, gpu_info_, channel_handle, + &shutdown_event_, gpu_memory_buffer_manager_local_.get()); +} + +void GpuServiceMus::InitializeOnGpuThread(IPC::ChannelHandle* channel_handle, + base::WaitableEvent* event) { + gpu_info_.video_decode_accelerator_capabilities = + media::GpuVideoDecodeAccelerator::GetCapabilities(gpu_preferences_); + gpu_info_.video_encode_accelerator_supported_profiles = + media::GpuVideoEncodeAccelerator::GetSupportedProfiles(gpu_preferences_); + gpu_info_.jpeg_decode_accelerator_supported = + media::GpuJpegDecodeAccelerator::IsSupported(); + +#if defined(USE_OZONE) + ui::OzonePlatform::InitializeForGPU(); +#endif + + if (gpu::GetNativeGpuMemoryBufferType() != gfx::EMPTY_BUFFER) { + gpu_memory_buffer_factory_ = + gpu::GpuMemoryBufferFactory::CreateNativeType(); + } + + if (!gl::init::InitializeGLOneOff()) + VLOG(1) << "gl::init::InitializeGLOneOff failed"; + + DCHECK(!owned_sync_point_manager_); + const bool allow_threaded_wait = false; + owned_sync_point_manager_.reset( + new gpu::SyncPointManager(allow_threaded_wait)); + + // Defer creation of the render thread. This is to prevent it from handling + // IPC messages before the sandbox has been enabled and all other necessary + // initialization has succeeded. + // TODO(penghuang): implement a watchdog. + gpu::GpuWatchdog* watchdog = nullptr; + gpu_channel_manager_.reset(new gpu::GpuChannelManager( + gpu_preferences_, this, watchdog, + base::ThreadTaskRunnerHandle::Get().get(), io_thread_.task_runner().get(), + &shutdown_event_, owned_sync_point_manager_.get(), + gpu_memory_buffer_factory_.get())); + + media_service_.reset(new media::MediaService(gpu_channel_manager_.get())); + + const bool preempts = true; + const bool allow_view_command_buffers = true; + const bool allow_real_time_streams = true; + EstablishGpuChannelOnGpuThread( + kLocalGpuChannelClientId, kLocalGpuChannelClientTracingId, preempts, + allow_view_command_buffers, allow_real_time_streams, channel_handle); + event->Signal(); +} + +void GpuServiceMus::EstablishGpuChannelOnGpuThread( + int client_id, + uint64_t client_tracing_id, + bool preempts, + bool allow_view_command_buffers, + bool allow_real_time_streams, + IPC::ChannelHandle* channel_handle) { + if (gpu_channel_manager_) { + *channel_handle = gpu_channel_manager_->EstablishChannel( + client_id, client_tracing_id, preempts, allow_view_command_buffers, + allow_real_time_streams); + media_service_->AddChannel(client_id); + } +} + +bool GpuServiceMus::IsMainThread() { + return main_task_runner_->BelongsToCurrentThread(); +} + +scoped_refptr<base::SingleThreadTaskRunner> +GpuServiceMus::GetIOThreadTaskRunner() { + return io_thread_.task_runner(); +} + +std::unique_ptr<base::SharedMemory> GpuServiceMus::AllocateSharedMemory( + size_t size) { + std::unique_ptr<base::SharedMemory> shm(new base::SharedMemory()); + if (!shm->CreateAnonymous(size)) + return std::unique_ptr<base::SharedMemory>(); + return shm; +} + +// static +GpuServiceMus* GpuServiceMus::GetInstance() { + return base::Singleton<GpuServiceMus, + base::LeakySingletonTraits<GpuServiceMus>>::get(); +} + +} // namespace mus diff --git a/chromium/components/mus/gpu/gpu_service_mus.h b/chromium/components/mus/gpu/gpu_service_mus.h new file mode 100644 index 00000000000..b6bfd1b69f5 --- /dev/null +++ b/chromium/components/mus/gpu/gpu_service_mus.h @@ -0,0 +1,173 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_GPU_SERVICE_MUS_H_ +#define COMPONENTS_MUS_GPU_GPU_SERVICE_MUS_H_ + +#include "base/callback.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread.h" +#include "build/build_config.h" +#include "gpu/command_buffer/service/gpu_preferences.h" +#include "gpu/config/gpu_info.h" +#include "gpu/ipc/client/gpu_channel_host.h" +#include "gpu/ipc/common/surface_handle.h" +#include "gpu/ipc/service/gpu_channel.h" +#include "gpu/ipc/service/gpu_channel_manager.h" +#include "gpu/ipc/service/gpu_channel_manager_delegate.h" +#include "gpu/ipc/service/gpu_config.h" +#include "gpu/ipc/service/x_util.h" +#include "ui/gfx/native_widget_types.h" + +namespace base { +template <typename T> +struct DefaultSingletonTraits; +} + +namespace IPC { +struct ChannelHandle; +} + +namespace gpu { +class GpuChannelHost; +class GpuMemoryBufferFactory; +class SyncPointManager; +} + +namespace media { +class MediaService; +} + +namespace mus { + +class MusGpuMemoryBufferManager; + +// TODO(fsamuel): GpuServiceMus is intended to be the Gpu thread within Mus. +// Similar to GpuChildThread, it is a GpuChannelManagerDelegate and will have a +// GpuChannelManager. +class GpuServiceMus : public gpu::GpuChannelManagerDelegate, + public gpu::GpuChannelHostFactory, + public base::NonThreadSafe { + public: + typedef base::Callback<void(int client_id, const IPC::ChannelHandle&)> + EstablishGpuChannelCallback; + void EstablishGpuChannel(uint64_t client_tracing_id, + bool preempts, + bool allow_view_command_buffers, + bool allow_real_time_streams, + const EstablishGpuChannelCallback& callback); + gfx::GpuMemoryBufferHandle CreateGpuMemoryBuffer( + gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + int client_id, + gpu::SurfaceHandle surface_handle); + gfx::GpuMemoryBufferHandle CreateGpuMemoryBufferFromeHandle( + gfx::GpuMemoryBufferHandle buffer_handle, + gfx::GpuMemoryBufferId id, + const gfx::Size& size, + gfx::BufferFormat format, + int client_id); + void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id, + int client_id, + const gpu::SyncToken& sync_token); + + gpu::GpuChannelManager* gpu_channel_manager() const { + return gpu_channel_manager_.get(); + } + + gpu::GpuMemoryBufferFactory* gpu_memory_buffer_factory() const { + return gpu_memory_buffer_factory_.get(); + } + + scoped_refptr<gpu::GpuChannelHost> gpu_channel_local() const { + return gpu_channel_local_; + } + + const gpu::GPUInfo& gpu_info() const { return gpu_info_; } + + // GpuChannelManagerDelegate overrides: + void DidCreateOffscreenContext(const GURL& active_url) override; + void DidDestroyChannel(int client_id) override; + void DidDestroyOffscreenContext(const GURL& active_url) override; + void DidLoseContext(bool offscreen, + gpu::error::ContextLostReason reason, + const GURL& active_url) override; + void GpuMemoryUmaStats(const gpu::GPUMemoryUmaStats& params) override; + void StoreShaderToDisk(int client_id, + const std::string& key, + const std::string& shader) override; +#if defined(OS_WIN) + void SendAcceleratedSurfaceCreatedChildWindow( + gpu::SurfaceHandle parent_window, + gpu::SurfaceHandle child_window) override; +#endif + void SetActiveURL(const GURL& url) override; + + // GpuChannelHostFactory overrides: + bool IsMainThread() override; + scoped_refptr<base::SingleThreadTaskRunner> GetIOThreadTaskRunner() override; + std::unique_ptr<base::SharedMemory> AllocateSharedMemory( + size_t size) override; + + static GpuServiceMus* GetInstance(); + + private: + friend struct base::DefaultSingletonTraits<GpuServiceMus>; + + GpuServiceMus(); + ~GpuServiceMus() override; + + void Initialize(); + void InitializeOnGpuThread(IPC::ChannelHandle* channel_handle, + base::WaitableEvent* event); + void EstablishGpuChannelOnGpuThread(int client_id, + uint64_t client_tracing_id, + bool preempts, + bool allow_view_command_buffers, + bool allow_real_time_streams, + IPC::ChannelHandle* channel_handle); + + // The next client id. + int next_client_id_; + + // The main thread task runner. + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; + + // An event that will be signalled when we shutdown. + base::WaitableEvent shutdown_event_; + + // The main thread for GpuService. + base::Thread gpu_thread_; + + // The thread that handles IO events for GpuService. + base::Thread io_thread_; + + std::unique_ptr<gpu::SyncPointManager> owned_sync_point_manager_; + + std::unique_ptr<gpu::GpuChannelManager> gpu_channel_manager_; + + std::unique_ptr<media::MediaService> media_service_; + + std::unique_ptr<gpu::GpuMemoryBufferFactory> gpu_memory_buffer_factory_; + + // A GPU memory buffer manager used locally. + std::unique_ptr<MusGpuMemoryBufferManager> gpu_memory_buffer_manager_local_; + + // A GPU channel used locally. + scoped_refptr<gpu::GpuChannelHost> gpu_channel_local_; + + gpu::GpuPreferences gpu_preferences_; + + // Information about the GPU, such as device and vendor ID. + gpu::GPUInfo gpu_info_; + + DISALLOW_COPY_AND_ASSIGN(GpuServiceMus); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_GPU_SERVICE_MUS_H_ diff --git a/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.cc b/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.cc new file mode 100644 index 00000000000..d9ecbd3257a --- /dev/null +++ b/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.cc @@ -0,0 +1,115 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/gpu/mus_gpu_memory_buffer_manager.h" + +#include "base/logging.h" +#include "components/mus/common/generic_shared_memory_id_generator.h" +#include "components/mus/gpu/gpu_service_mus.h" +#include "gpu/ipc/client/gpu_memory_buffer_impl.h" +#include "gpu/ipc/client/gpu_memory_buffer_impl_shared_memory.h" +#include "gpu/ipc/common/gpu_memory_buffer_support.h" +#include "gpu/ipc/service/gpu_memory_buffer_factory.h" + +namespace mus { + +namespace { + +MusGpuMemoryBufferManager* g_gpu_memory_buffer_manager = nullptr; + +bool IsNativeGpuMemoryBufferFactoryConfigurationSupported( + gfx::BufferFormat format, + gfx::BufferUsage usage) { + switch (gpu::GetNativeGpuMemoryBufferType()) { + case gfx::SHARED_MEMORY_BUFFER: + return false; + case gfx::IO_SURFACE_BUFFER: + case gfx::SURFACE_TEXTURE_BUFFER: + case gfx::OZONE_NATIVE_PIXMAP: + return gpu::IsNativeGpuMemoryBufferConfigurationSupported(format, usage); + default: + NOTREACHED(); + return false; + } +} +} + +MusGpuMemoryBufferManager* MusGpuMemoryBufferManager::current() { + return g_gpu_memory_buffer_manager; +} + +MusGpuMemoryBufferManager::MusGpuMemoryBufferManager(GpuServiceMus* gpu_service, + int client_id) + : gpu_service_(gpu_service), client_id_(client_id), weak_factory_(this) { + DCHECK(!g_gpu_memory_buffer_manager); + g_gpu_memory_buffer_manager = this; +} + +MusGpuMemoryBufferManager::~MusGpuMemoryBufferManager() { + g_gpu_memory_buffer_manager = nullptr; +} + +std::unique_ptr<gfx::GpuMemoryBuffer> +MusGpuMemoryBufferManager::AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) { + gfx::GpuMemoryBufferId id = GetNextGenericSharedMemoryId(); + const bool is_native = + IsNativeGpuMemoryBufferFactoryConfigurationSupported(format, usage); + if (is_native) { + gfx::GpuMemoryBufferHandle handle = + gpu_service_->gpu_memory_buffer_factory()->CreateGpuMemoryBuffer( + id, size, format, usage, client_id_, surface_handle); + if (handle.is_null()) + return nullptr; + return gpu::GpuMemoryBufferImpl::CreateFromHandle( + handle, size, format, usage, + base::Bind(&MusGpuMemoryBufferManager::DestroyGpuMemoryBuffer, + weak_factory_.GetWeakPtr(), id, client_id_, is_native)); + } + + DCHECK(gpu::GpuMemoryBufferImplSharedMemory::IsUsageSupported(usage)) + << static_cast<int>(usage); + return gpu::GpuMemoryBufferImplSharedMemory::Create( + id, size, format, + base::Bind(&MusGpuMemoryBufferManager::DestroyGpuMemoryBuffer, + weak_factory_.GetWeakPtr(), id, client_id_, is_native)); +} + +std::unique_ptr<gfx::GpuMemoryBuffer> +MusGpuMemoryBufferManager::CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) { + NOTIMPLEMENTED(); + return nullptr; +} + +gfx::GpuMemoryBuffer* +MusGpuMemoryBufferManager::GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) { + return gpu::GpuMemoryBufferImpl::FromClientBuffer(buffer); +} + +void MusGpuMemoryBufferManager::SetDestructionSyncToken( + gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) { + static_cast<gpu::GpuMemoryBufferImpl*>(buffer)->set_destruction_sync_token( + sync_token); +} + +void MusGpuMemoryBufferManager::DestroyGpuMemoryBuffer( + gfx::GpuMemoryBufferId id, + int client_id, + bool is_native, + const gpu::SyncToken& sync_token) { + if (is_native) { + gpu_service_->gpu_channel_manager()->DestroyGpuMemoryBuffer(id, client_id, + sync_token); + } +} + +} // namespace mus diff --git a/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.h b/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.h new file mode 100644 index 00000000000..b705dd68bb8 --- /dev/null +++ b/chromium/components/mus/gpu/mus_gpu_memory_buffer_manager.h @@ -0,0 +1,57 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_GPU_MUS_GPU_MEMORY_BUFFER_MANAGER_H_ +#define COMPONENTS_MUS_GPU_MUS_GPU_MEMORY_BUFFER_MANAGER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" + +namespace mus { + +class GpuServiceMus; + +// This GpuMemoryBufferManager is for establishing a GpuChannelHost used by +// mus locally. +class MusGpuMemoryBufferManager : public gpu::GpuMemoryBufferManager { + public: + MusGpuMemoryBufferManager(GpuServiceMus* gpu_service, int client_id); + ~MusGpuMemoryBufferManager() override; + + static MusGpuMemoryBufferManager* current(); + + // Overridden from gpu::GpuMemoryBufferManager: + std::unique_ptr<gfx::GpuMemoryBuffer> AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) override; + std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) override; + gfx::GpuMemoryBuffer* GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) override; + void SetDestructionSyncToken(gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) override; + + private: + DISALLOW_COPY_AND_ASSIGN(MusGpuMemoryBufferManager); + + void DestroyGpuMemoryBuffer(gfx::GpuMemoryBufferId id, + int client_id, + bool is_native, + const gpu::SyncToken& sync_token); + + GpuServiceMus* gpu_service_; + const int client_id_; + base::WeakPtrFactory<MusGpuMemoryBufferManager> weak_factory_; +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_GPU_MUS_GPU_MEMORY_BUFFER_MANAGER_H_ diff --git a/chromium/components/mus/gpu/mus_gpu_unittests_app_manifest.json b/chromium/components/mus/gpu/mus_gpu_unittests_app_manifest.json new file mode 100644 index 00000000000..26e8caa4802 --- /dev/null +++ b/chromium/components/mus/gpu/mus_gpu_unittests_app_manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 1, + "name": "mojo:mus_gpu_unittests_app", + "display_name": "Mus GPU Unittests", + "capabilities": { + "required": { + "*": { "classes": [ "app" ] } + } + } +} diff --git a/chromium/components/mus/input_devices/input_device_server.cc b/chromium/components/mus/input_devices/input_device_server.cc new file mode 100644 index 00000000000..3514eb35669 --- /dev/null +++ b/chromium/components/mus/input_devices/input_device_server.cc @@ -0,0 +1,110 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/input_devices/input_device_server.h" + +#include <utility> +#include <vector> + +#include "mojo/public/cpp/bindings/array.h" +#include "ui/events/devices/input_device.h" +#include "ui/events/devices/touchscreen_device.h" + +namespace mus { + +InputDeviceServer::InputDeviceServer() {} + +InputDeviceServer::~InputDeviceServer() { + if (manager_ && ui::DeviceDataManager::HasInstance()) { + manager_->RemoveObserver(this); + manager_ = nullptr; + } +} + +void InputDeviceServer::RegisterAsObserver() { + if (!manager_ && ui::DeviceDataManager::HasInstance()) { + manager_ = ui::DeviceDataManager::GetInstance(); + manager_->AddObserver(this); + } +} + +bool InputDeviceServer::IsRegisteredAsObserver() const { + return manager_ != nullptr; +} + +void InputDeviceServer::AddInterface(shell::Connection* connection) { + DCHECK(manager_); + connection->AddInterface<mojom::InputDeviceServer>(this); +} + +void InputDeviceServer::AddObserver( + mojom::InputDeviceObserverMojoPtr observer) { + // We only want to send this message once, so we need to check to make sure + // device lists are actually complete before sending it to a new observer. + if (manager_->AreDeviceListsComplete()) + SendDeviceListsComplete(observer.get()); + observers_.AddPtr(std::move(observer)); +} + +void InputDeviceServer::OnKeyboardDeviceConfigurationChanged() { + if (!manager_->AreDeviceListsComplete()) + return; + + auto& devices = manager_->GetKeyboardDevices(); + observers_.ForAllPtrs([&devices](mojom::InputDeviceObserverMojo* observer) { + observer->OnKeyboardDeviceConfigurationChanged(devices); + }); +} + +void InputDeviceServer::OnTouchscreenDeviceConfigurationChanged() { + if (!manager_->AreDeviceListsComplete()) + return; + + auto& devices = manager_->GetTouchscreenDevices(); + observers_.ForAllPtrs([&devices](mojom::InputDeviceObserverMojo* observer) { + observer->OnTouchscreenDeviceConfigurationChanged(devices); + }); +} + +void InputDeviceServer::OnMouseDeviceConfigurationChanged() { + if (!manager_->AreDeviceListsComplete()) + return; + + auto& devices = manager_->GetMouseDevices(); + observers_.ForAllPtrs([&devices](mojom::InputDeviceObserverMojo* observer) { + observer->OnMouseDeviceConfigurationChanged(devices); + }); +} + +void InputDeviceServer::OnTouchpadDeviceConfigurationChanged() { + if (!manager_->AreDeviceListsComplete()) + return; + + auto& devices = manager_->GetTouchpadDevices(); + observers_.ForAllPtrs([&devices](mojom::InputDeviceObserverMojo* observer) { + observer->OnTouchpadDeviceConfigurationChanged(devices); + }); +} + +void InputDeviceServer::OnDeviceListsComplete() { + observers_.ForAllPtrs([this](mojom::InputDeviceObserverMojo* observer) { + SendDeviceListsComplete(observer); + }); +} + +void InputDeviceServer::SendDeviceListsComplete( + mojom::InputDeviceObserverMojo* observer) { + DCHECK(manager_->AreDeviceListsComplete()); + + observer->OnDeviceListsComplete( + manager_->GetKeyboardDevices(), manager_->GetTouchscreenDevices(), + manager_->GetMouseDevices(), manager_->GetTouchpadDevices()); +} + +void InputDeviceServer::Create(shell::Connection* connection, + mojom::InputDeviceServerRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +} // namespace mus diff --git a/chromium/components/mus/input_devices/input_device_server.h b/chromium/components/mus/input_devices/input_device_server.h new file mode 100644 index 00000000000..af0e0e704e6 --- /dev/null +++ b/chromium/components/mus/input_devices/input_device_server.h @@ -0,0 +1,69 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_INPUT_DEVICES_INPUT_DEVICE_SERVER_H_ +#define COMPONENTS_MUS_INPUT_DEVICES_INPUT_DEVICE_SERVER_H_ + +#include "base/macros.h" +#include "components/mus/public/interfaces/input_devices/input_device_server.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_ptr_set.h" +#include "services/shell/public/cpp/connection.h" +#include "services/shell/public/cpp/interface_factory.h" +#include "ui/events/devices/device_data_manager.h" +#include "ui/events/devices/input_device_event_observer.h" + +namespace mus { + +// Listens to DeviceDataManager for updates on input-devices and forwards those +// updates to any registered InputDeviceObserverMojo in other processes via +// Mojo IPC. This runs in the mus-ws process. +class InputDeviceServer + : public shell::InterfaceFactory<mojom::InputDeviceServer>, + public mojom::InputDeviceServer, + public ui::InputDeviceEventObserver { + public: + InputDeviceServer(); + ~InputDeviceServer() override; + + // Registers this instance as a local observer with DeviceDataManager. + void RegisterAsObserver(); + bool IsRegisteredAsObserver() const; + + // Adds interface with the shell connection so remote observers can connect. + // You should have already called RegisterAsObserver() to get local + // input-device event updates and checked it was successful by calling + // IsRegisteredAsObserver(). + void AddInterface(shell::Connection* connection); + + // mojom::InputDeviceServer: + void AddObserver(mojom::InputDeviceObserverMojoPtr observer) override; + + // ui::InputDeviceEventObserver: + void OnKeyboardDeviceConfigurationChanged() override; + void OnTouchscreenDeviceConfigurationChanged() override; + void OnMouseDeviceConfigurationChanged() override; + void OnTouchpadDeviceConfigurationChanged() override; + void OnDeviceListsComplete() override; + + private: + // Sends the current state of all input-devices to an observer. + void SendDeviceListsComplete(mojom::InputDeviceObserverMojo* observer); + + // mojo::InterfaceFactory<mojom::InputDeviceServer>: + void Create(shell::Connection* connection, + mojom::InputDeviceServerRequest request) override; + + mojo::BindingSet<mojom::InputDeviceServer> bindings_; + mojo::InterfacePtrSet<mojom::InputDeviceObserverMojo> observers_; + + // DeviceDataManager instance we are registered as an observer with. + ui::DeviceDataManager* manager_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(InputDeviceServer); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_INPUT_DEVICES_INPUT_DEVICE_SERVER_H_ diff --git a/chromium/components/mus/main.cc b/chromium/components/mus/main.cc new file mode 100644 index 00000000000..32c40ef407a --- /dev/null +++ b/chromium/components/mus/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 "components/mus/mus_app.h" +#include "mojo/public/c/system/main.h" +#include "services/shell/public/cpp/application_runner.h" + +MojoResult MojoMain(MojoHandle shell_handle) { + shell::ApplicationRunner runner(new mus::MusApp); + runner.set_message_loop_type(base::MessageLoop::TYPE_UI); + return runner.Run(shell_handle); +} diff --git a/chromium/components/mus/manifest.json b/chromium/components/mus/manifest.json new file mode 100644 index 00000000000..1969bec66e7 --- /dev/null +++ b/chromium/components/mus/manifest.json @@ -0,0 +1,26 @@ +{ + "manifest_version": 1, + "name": "mojo:mus", + "display_name": "UI Service", + "capabilities": { + "provided": { + // A collection of interfaces needed by a generic client of mus. + // Additional interfaces may be requested a-la-carte. + "app": [ + "mus::mojom::Clipboard", + "mus::mojom::DisplayManager", + "mus::mojom::Gpu", + "mus::mojom::GpuService", + "mus::mojom::InputDeviceServer", + "mus::mojom::WindowTreeFactory" + ], + "test": [ + "mus::mojom::WindowServerTest" + ] + }, + "required": { + "*": { "classes": [ "app" ] }, + "mojo:shell": { "classes": [ "shell:all_users", "shell:explicit_class" ] } + } + } +} diff --git a/chromium/components/mus/mus_app.cc b/chromium/components/mus/mus_app.cc new file mode 100644 index 00000000000..8ec1901a0c6 --- /dev/null +++ b/chromium/components/mus/mus_app.cc @@ -0,0 +1,388 @@ +// 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 "components/mus/mus_app.h" + +#include <set> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/platform_thread.h" +#include "build/build_config.h" +#include "components/mus/clipboard/clipboard_impl.h" +#include "components/mus/common/switches.h" +#include "components/mus/gles2/gpu_impl.h" +#include "components/mus/gpu/gpu_service_impl.h" +#include "components/mus/gpu/gpu_service_mus.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/platform_screen.h" +#include "components/mus/ws/user_activity_monitor.h" +#include "components/mus/ws/user_display_manager.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_test_impl.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "components/mus/ws/window_tree_factory.h" +#include "components/mus/ws/window_tree_host_factory.h" +#include "mojo/public/c/system/main.h" +#include "services/catalog/public/cpp/resource_loader.h" +#include "services/shell/public/cpp/connection.h" +#include "services/shell/public/cpp/connector.h" +#include "services/tracing/public/cpp/tracing_impl.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/ui_base_paths.h" +#include "ui/events/event_switches.h" +#include "ui/events/platform/platform_event_source.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gl/gl_surface.h" + +#if defined(USE_X11) +#include <X11/Xlib.h> +#include "ui/platform_window/x11/x11_window.h" +#elif defined(USE_OZONE) +#include "ui/events/ozone/layout/keyboard_layout_engine.h" +#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h" +#include "ui/ozone/public/ozone_platform.h" +#endif + +using shell::Connection; +using mojo::InterfaceRequest; +using mus::mojom::Gpu; +using mus::mojom::WindowServerTest; +using mus::mojom::WindowTreeHostFactory; + +namespace mus { + +namespace { + +const char kResourceFileStrings[] = "mus_app_resources_strings.pak"; +const char kResourceFile100[] = "mus_app_resources_100.pak"; +const char kResourceFile200[] = "mus_app_resources_200.pak"; + +} // namespace + +// TODO(sky): this is a pretty typical pattern, make it easier to do. +struct MusApp::PendingRequest { + shell::Connection* connection; + std::unique_ptr<mojom::WindowTreeFactoryRequest> wtf_request; + std::unique_ptr<mojom::DisplayManagerRequest> dm_request; +}; + +struct MusApp::UserState { + std::unique_ptr<clipboard::ClipboardImpl> clipboard; + std::unique_ptr<ws::WindowTreeHostFactory> window_tree_host_factory; +}; + +MusApp::MusApp() + : test_config_(false), + // TODO(penghuang): Kludge: Use mojo command buffer when running on + // Windows since chrome command buffer breaks unit tests +#if defined(OS_WIN) + use_chrome_gpu_command_buffer_(false), +#else + use_chrome_gpu_command_buffer_(true), +#endif + platform_screen_(ws::PlatformScreen::Create()), + weak_ptr_factory_(this) {} + +MusApp::~MusApp() { + // Destroy |window_server_| first, since it depends on |event_source_|. + // WindowServer (or more correctly its Displays) may have state that needs to + // be destroyed before GpuState as well. + window_server_.reset(); + + if (platform_display_init_params_.gpu_state) + platform_display_init_params_.gpu_state->StopThreads(); +} + +void MusApp::InitializeResources(shell::Connector* connector) { + if (ui::ResourceBundle::HasSharedInstance()) + return; + + std::set<std::string> resource_paths; + resource_paths.insert(kResourceFileStrings); + resource_paths.insert(kResourceFile100); + resource_paths.insert(kResourceFile200); + + catalog::ResourceLoader loader; + filesystem::mojom::DirectoryPtr directory; + connector->ConnectToInterface("mojo:catalog", &directory); + CHECK(loader.OpenFiles(std::move(directory), resource_paths)); + + ui::RegisterPathProvider(); + + // Initialize resource bundle with 1x and 2x cursor bitmaps. + ui::ResourceBundle::InitSharedInstanceWithPakFileRegion( + loader.TakeFile(kResourceFileStrings), + base::MemoryMappedFile::Region::kWholeFile); + ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); + rb.AddDataPackFromFile(loader.TakeFile(kResourceFile100), + ui::SCALE_FACTOR_100P); + rb.AddDataPackFromFile(loader.TakeFile(kResourceFile200), + ui::SCALE_FACTOR_200P); +} + +MusApp::UserState* MusApp::GetUserState(shell::Connection* connection) { + const ws::UserId& user_id = connection->GetRemoteIdentity().user_id(); + auto it = user_id_to_user_state_.find(user_id); + if (it != user_id_to_user_state_.end()) + return it->second.get(); + user_id_to_user_state_[user_id] = base::WrapUnique(new UserState); + return user_id_to_user_state_[user_id].get(); +} + +void MusApp::AddUserIfNecessary(shell::Connection* connection) { + window_server_->user_id_tracker()->AddUserId( + connection->GetRemoteIdentity().user_id()); +} + +void MusApp::Initialize(shell::Connector* connector, + const shell::Identity& identity, + uint32_t id) { + platform_display_init_params_.surfaces_state = new SurfacesState; + + base::PlatformThread::SetName("mus"); + tracing_.Initialize(connector, identity.name()); + TRACE_EVENT0("mus", "MusApp::Initialize started"); + + test_config_ = base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kUseTestConfig); +// TODO(penghuang): Kludge: use mojo command buffer when running on Windows +// since Chrome command buffer breaks unit tests +#if defined(OS_WIN) + use_chrome_gpu_command_buffer_ = false; +#else + use_chrome_gpu_command_buffer_ = + !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kUseMojoGpuCommandBufferInMus); +#endif +#if defined(USE_X11) + XInitThreads(); + if (test_config_) + ui::test::SetUseOverrideRedirectWindowByDefault(true); +#endif + + InitializeResources(connector); + +#if defined(USE_OZONE) + // The ozone platform can provide its own event source. So initialize the + // platform before creating the default event source. + // Because GL libraries need to be initialized before entering the sandbox, + // in MUS, |InitializeForUI| will load the GL libraries. + ui::OzonePlatform::InitParams params; + params.connector = connector; + params.single_process = false; + + ui::OzonePlatform::InitializeForUI(params); + + // TODO(kylechar): We might not always want a US keyboard layout. + ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine() + ->SetCurrentLayoutByName("us"); + client_native_pixmap_factory_ = ui::ClientNativePixmapFactory::Create(); + ui::ClientNativePixmapFactory::SetInstance( + client_native_pixmap_factory_.get()); + + DCHECK(ui::ClientNativePixmapFactory::GetInstance()); +#endif + +// TODO(rjkroege): Enter sandbox here before we start threads in GpuState +// http://crbug.com/584532 + +#if !defined(OS_ANDROID) + event_source_ = ui::PlatformEventSource::CreateDefault(); +#endif + + // This needs to happen after DeviceDataManager has been constructed. That + // happens either during OzonePlatform or PlatformEventSource initialization, + // so keep this line below both of those. + input_device_server_.RegisterAsObserver(); + + if (use_chrome_gpu_command_buffer_) { + GpuServiceMus::GetInstance(); + } else { + // TODO(rjkroege): It is possible that we might want to generalize the + // GpuState object. + platform_display_init_params_.gpu_state = new GpuState(); + } + + // Gpu must be running before the PlatformScreen can be initialized. + platform_screen_->Init(); + window_server_.reset( + new ws::WindowServer(this, platform_display_init_params_.surfaces_state)); + + // DeviceDataManager must be initialized before TouchController. On non-Linux + // platforms there is no DeviceDataManager so don't create touch controller. + if (ui::DeviceDataManager::HasInstance()) + touch_controller_.reset( + new ws::TouchController(window_server_->display_manager())); +} + +bool MusApp::AcceptConnection(Connection* connection) { + connection->AddInterface<mojom::Clipboard>(this); + connection->AddInterface<mojom::DisplayManager>(this); + connection->AddInterface<mojom::UserAccessManager>(this); + connection->AddInterface<mojom::UserActivityMonitor>(this); + connection->AddInterface<WindowTreeHostFactory>(this); + connection->AddInterface<mojom::WindowManagerWindowTreeFactory>(this); + connection->AddInterface<mojom::WindowTreeFactory>(this); + if (test_config_) + connection->AddInterface<WindowServerTest>(this); + + if (use_chrome_gpu_command_buffer_) { + connection->AddInterface<mojom::GpuService>(this); + } else { + connection->AddInterface<Gpu>(this); + } + + // On non-Linux platforms there will be no DeviceDataManager instance and no + // purpose in adding the Mojo interface to connect to. + if (input_device_server_.IsRegisteredAsObserver()) + input_device_server_.AddInterface(connection); + +#if defined(USE_OZONE) + ui::OzonePlatform::GetInstance()->AddInterfaces(connection); +#endif + + return true; +} + +void MusApp::OnFirstDisplayReady() { + PendingRequests requests; + requests.swap(pending_requests_); + for (auto& request : requests) { + if (request->wtf_request) + Create(request->connection, std::move(*request->wtf_request)); + else + Create(request->connection, std::move(*request->dm_request)); + } +} + +void MusApp::OnNoMoreDisplays() { + // We may get here from the destructor, in which case there is no messageloop. + if (base::MessageLoop::current()) + base::MessageLoop::current()->QuitWhenIdle(); +} + +bool MusApp::IsTestConfig() const { + return test_config_; +} + +void MusApp::CreateDefaultDisplays() { + // An asynchronous callback will create the Displays once the physical + // displays are ready. + platform_screen_->ConfigurePhysicalDisplay(base::Bind( + &MusApp::OnCreatedPhysicalDisplay, weak_ptr_factory_.GetWeakPtr())); +} + +void MusApp::Create(shell::Connection* connection, + mojom::ClipboardRequest request) { + UserState* user_state = GetUserState(connection); + if (!user_state->clipboard) + user_state->clipboard.reset(new clipboard::ClipboardImpl); + user_state->clipboard->AddBinding(std::move(request)); +} + +void MusApp::Create(shell::Connection* connection, + mojom::DisplayManagerRequest request) { + // DisplayManagerObservers generally expect there to be at least one display. + if (!window_server_->display_manager()->has_displays()) { + std::unique_ptr<PendingRequest> pending_request(new PendingRequest); + pending_request->connection = connection; + pending_request->dm_request.reset( + new mojom::DisplayManagerRequest(std::move(request))); + pending_requests_.push_back(std::move(pending_request)); + return; + } + window_server_->display_manager() + ->GetUserDisplayManager(connection->GetRemoteIdentity().user_id()) + ->AddDisplayManagerBinding(std::move(request)); +} + +void MusApp::Create(shell::Connection* connection, mojom::GpuRequest request) { + if (use_chrome_gpu_command_buffer_) + return; + DCHECK(platform_display_init_params_.gpu_state); + new GpuImpl(std::move(request), platform_display_init_params_.gpu_state); +} + +void MusApp::Create(shell::Connection* connection, + mojom::GpuServiceRequest request) { + if (!use_chrome_gpu_command_buffer_) + return; + new GpuServiceImpl(std::move(request), connection); +} + +void MusApp::Create(shell::Connection* connection, + mojom::UserAccessManagerRequest request) { + window_server_->user_id_tracker()->Bind(std::move(request)); +} + +void MusApp::Create(shell::Connection* connection, + mojom::UserActivityMonitorRequest request) { + AddUserIfNecessary(connection); + const ws::UserId& user_id = connection->GetRemoteIdentity().user_id(); + window_server_->GetUserActivityMonitorForUser(user_id)->Add( + std::move(request)); +} + +void MusApp::Create(shell::Connection* connection, + mojom::WindowManagerWindowTreeFactoryRequest request) { + AddUserIfNecessary(connection); + window_server_->window_manager_window_tree_factory_set()->Add( + connection->GetRemoteIdentity().user_id(), std::move(request)); +} + +void MusApp::Create(Connection* connection, + mojom::WindowTreeFactoryRequest request) { + AddUserIfNecessary(connection); + if (!window_server_->display_manager()->has_displays()) { + std::unique_ptr<PendingRequest> pending_request(new PendingRequest); + pending_request->connection = connection; + pending_request->wtf_request.reset( + new mojom::WindowTreeFactoryRequest(std::move(request))); + pending_requests_.push_back(std::move(pending_request)); + return; + } + AddUserIfNecessary(connection); + new ws::WindowTreeFactory( + window_server_.get(), connection->GetRemoteIdentity().user_id(), + connection->GetRemoteIdentity().name(), std::move(request)); +} + +void MusApp::Create(Connection* connection, + mojom::WindowTreeHostFactoryRequest request) { + UserState* user_state = GetUserState(connection); + if (!user_state->window_tree_host_factory) { + user_state->window_tree_host_factory.reset(new ws::WindowTreeHostFactory( + window_server_.get(), connection->GetRemoteIdentity().user_id(), + platform_display_init_params_)); + } + user_state->window_tree_host_factory->AddBinding(std::move(request)); +} + +void MusApp::Create(Connection* connection, + mojom::WindowServerTestRequest request) { + if (!test_config_) + return; + new ws::WindowServerTestImpl(window_server_.get(), std::move(request)); +} + +void MusApp::OnCreatedPhysicalDisplay(int64_t id, const gfx::Rect& bounds) { + platform_display_init_params_.display_bounds = bounds; + platform_display_init_params_.display_id = id; + + // Display manages its own lifetime. + ws::Display* host_impl = + new ws::Display(window_server_.get(), platform_display_init_params_); + host_impl->Init(nullptr); + + if (touch_controller_) + touch_controller_->UpdateTouchTransforms(); +} + +} // namespace mus diff --git a/chromium/components/mus/mus_app.h b/chromium/components/mus/mus_app.h new file mode 100644 index 00000000000..6bffa5c1a72 --- /dev/null +++ b/chromium/components/mus/mus_app.h @@ -0,0 +1,182 @@ +// 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 COMPONENTS_MUS_MUS_APP_H_ +#define COMPONENTS_MUS_MUS_APP_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/mus/input_devices/input_device_server.h" +#include "components/mus/public/interfaces/clipboard.mojom.h" +#include "components/mus/public/interfaces/display.mojom.h" +#include "components/mus/public/interfaces/gpu.mojom.h" +#include "components/mus/public/interfaces/gpu_service.mojom.h" +#include "components/mus/public/interfaces/user_access_manager.mojom.h" +#include "components/mus/public/interfaces/user_activity_monitor.mojom.h" +#include "components/mus/public/interfaces/window_manager_window_tree_factory.mojom.h" +#include "components/mus/public/interfaces/window_server_test.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/touch_controller.h" +#include "components/mus/ws/user_id.h" +#include "components/mus/ws/window_server_delegate.h" +#include "services/shell/public/cpp/application_runner.h" +#include "services/shell/public/cpp/interface_factory.h" +#include "services/shell/public/cpp/shell_client.h" +#include "services/tracing/public/cpp/tracing_impl.h" + +#if defined(USE_OZONE) +#include "ui/ozone/public/client_native_pixmap_factory.h" +#endif + +namespace gfx { +class Rect; +} + +namespace shell { +class Connector; +} + +namespace ui { +class PlatformEventSource; +} + +namespace mus { +namespace ws { +class ForwardingWindowManager; +class PlatformScreen; +class WindowServer; +} + +class MusApp + : public shell::ShellClient, + public ws::WindowServerDelegate, + public shell::InterfaceFactory<mojom::Clipboard>, + public shell::InterfaceFactory<mojom::DisplayManager>, + public shell::InterfaceFactory<mojom::Gpu>, + public shell::InterfaceFactory<mojom::GpuService>, + public shell::InterfaceFactory<mojom::UserAccessManager>, + public shell::InterfaceFactory<mojom::UserActivityMonitor>, + public shell::InterfaceFactory<mojom::WindowManagerWindowTreeFactory>, + public shell::InterfaceFactory<mojom::WindowTreeFactory>, + public shell::InterfaceFactory<mojom::WindowTreeHostFactory>, + public shell::InterfaceFactory<mojom::WindowServerTest> { + public: + MusApp(); + ~MusApp() override; + + private: + // Holds InterfaceRequests received before the first WindowTreeHost Display + // has been established. + struct PendingRequest; + struct UserState; + + using UserIdToUserState = std::map<ws::UserId, std::unique_ptr<UserState>>; + + void InitializeResources(shell::Connector* connector); + + // Returns the user specific state for the user id of |connection|. MusApp + // owns the return value. + // TODO(sky): if we allow removal of user ids then we need to close anything + // associated with the user (all incoming pipes...) on removal. + UserState* GetUserState(shell::Connection* connection); + + void AddUserIfNecessary(shell::Connection* connection); + + // shell::ShellClient: + void Initialize(shell::Connector* connector, + const shell::Identity& identity, + uint32_t id) override; + bool AcceptConnection(shell::Connection* connection) override; + + // WindowServerDelegate: + void OnFirstDisplayReady() override; + void OnNoMoreDisplays() override; + bool IsTestConfig() const override; + void CreateDefaultDisplays() override; + + // shell::InterfaceFactory<mojom::Clipboard> implementation. + void Create(shell::Connection* connection, + mojom::ClipboardRequest request) override; + + // shell::InterfaceFactory<mojom::DisplayManager> implementation. + void Create(shell::Connection* connection, + mojom::DisplayManagerRequest request) override; + + // shell::InterfaceFactory<mojom::Gpu> implementation. + void Create(shell::Connection* connection, + mojom::GpuRequest request) override; + + // shell::InterfaceFactory<mojom::GpuService> implementation. + void Create(shell::Connection* connection, + mojom::GpuServiceRequest request) override; + + // shell::InterfaceFactory<mojom::UserAccessManager> implementation. + void Create(shell::Connection* connection, + mojom::UserAccessManagerRequest request) override; + + // shell::InterfaceFactory<mojom::UserActivityMonitor> implementation. + void Create(shell::Connection* connection, + mojom::UserActivityMonitorRequest request) override; + + // shell::InterfaceFactory<mojom::WindowManagerWindowTreeFactory> + // implementation. + void Create(shell::Connection* connection, + mojom::WindowManagerWindowTreeFactoryRequest request) override; + + // shell::InterfaceFactory<mojom::WindowTreeFactory>: + void Create(shell::Connection* connection, + mojom::WindowTreeFactoryRequest request) override; + + // shell::InterfaceFactory<mojom::WindowTreeHostFactory>: + void Create(shell::Connection* connection, + mojom::WindowTreeHostFactoryRequest request) override; + + // shell::InterfaceFactory<mojom::WindowServerTest> implementation. + void Create(shell::Connection* connection, + mojom::WindowServerTestRequest request) override; + + // Callback for display configuration. |id| is the identifying token for the + // configured display that will identify a specific physical display across + // configuration changes. |bounds| is the bounds of the display in screen + // coordinates. + void OnCreatedPhysicalDisplay(int64_t id, const gfx::Rect& bounds); + + ws::PlatformDisplayInitParams platform_display_init_params_; + std::unique_ptr<ws::WindowServer> window_server_; + std::unique_ptr<ui::PlatformEventSource> event_source_; + mojo::TracingImpl tracing_; + using PendingRequests = std::vector<std::unique_ptr<PendingRequest>>; + PendingRequests pending_requests_; + + UserIdToUserState user_id_to_user_state_; + + // Provides input-device information via Mojo IPC. + InputDeviceServer input_device_server_; + + bool test_config_; + bool use_chrome_gpu_command_buffer_; +#if defined(USE_OZONE) + std::unique_ptr<ui::ClientNativePixmapFactory> client_native_pixmap_factory_; +#endif + + std::unique_ptr<ws::PlatformScreen> platform_screen_; + std::unique_ptr<ws::TouchController> touch_controller_; + + base::WeakPtrFactory<MusApp> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(MusApp); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_MUS_APP_H_ diff --git a/chromium/components/mus/public/cpp/DEPS b/chromium/components/mus/public/cpp/DEPS new file mode 100644 index 00000000000..9be0bc0fccc --- /dev/null +++ b/chromium/components/mus/public/cpp/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+gpu", +] diff --git a/chromium/components/mus/public/cpp/context_provider.h b/chromium/components/mus/public/cpp/context_provider.h new file mode 100644 index 00000000000..f16366e05c2 --- /dev/null +++ b/chromium/components/mus/public/cpp/context_provider.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 COMPONENTS_MUS_PUBLIC_CPP_CONTEXT_PROVIDER_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_CONTEXT_PROVIDER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "cc/output/context_provider.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "mojo/public/cpp/system/core.h" + +namespace shell { +class Connector; +} + +namespace mus { + +class GLES2Context; + +class ContextProvider : public cc::ContextProvider { + public: + explicit ContextProvider(shell::Connector* connector); + + // cc::ContextProvider implementation. + bool BindToCurrentThread() override; + gpu::gles2::GLES2Interface* ContextGL() override; + gpu::ContextSupport* ContextSupport() override; + class GrContext* GrContext() override; + void InvalidateGrContext(uint32_t state) override; + base::Lock* GetLock() override; + gpu::Capabilities ContextCapabilities() override; + void DeleteCachedResources() override {} + void SetLostContextCallback( + const LostContextCallback& lost_context_callback) override {} + + protected: + friend class base::RefCountedThreadSafe<ContextProvider>; + ~ContextProvider() override; + + private: + std::unique_ptr<shell::Connector> connector_; + std::unique_ptr<GLES2Context> context_; + + DISALLOW_COPY_AND_ASSIGN(ContextProvider); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_CONTEXT_PROVIDER_H_ diff --git a/chromium/components/mus/public/cpp/gles2_context.h b/chromium/components/mus/public/cpp/gles2_context.h new file mode 100644 index 00000000000..03b069f478d --- /dev/null +++ b/chromium/components/mus/public/cpp/gles2_context.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 COMPONENTS_MUS_PUBLIC_CPP_GLES2_CONTEXT_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_GLES2_CONTEXT_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "gpu/command_buffer/client/gles2_implementation.h" + +namespace gpu { +class CommandBufferProxyImpl; +class TransferBuffer; +namespace gles2 { +class GLES2CmdHelper; +} +} + +namespace shell { +class Connector; +} + +namespace mus { + +class CommandBufferClientImpl; + +class GLES2Context { + public: + ~GLES2Context(); + gpu::gles2::GLES2Interface* interface() const { + return implementation_.get(); + } + gpu::ContextSupport* context_support() const { return implementation_.get(); } + + static std::unique_ptr<GLES2Context> CreateOffscreenContext( + const std::vector<int32_t>& attribs, + shell::Connector* connector); + + private: + GLES2Context(); + bool Initialize(const std::vector<int32_t>& attribs, + shell::Connector* connector); + + std::unique_ptr<CommandBufferClientImpl> command_buffer_client_impl_; + std::unique_ptr<gpu::CommandBufferProxyImpl> command_buffer_proxy_impl_; + std::unique_ptr<gpu::gles2::GLES2CmdHelper> gles2_helper_; + std::unique_ptr<gpu::TransferBuffer> transfer_buffer_; + std::unique_ptr<gpu::gles2::GLES2Implementation> implementation_; + + DISALLOW_COPY_AND_ASSIGN(GLES2Context); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_GLES2_CONTEXT_H_ diff --git a/chromium/components/mus/public/cpp/input_devices/input_device_client.cc b/chromium/components/mus/public/cpp/input_devices/input_device_client.cc new file mode 100644 index 00000000000..6b55d778f58 --- /dev/null +++ b/chromium/components/mus/public/cpp/input_devices/input_device_client.cc @@ -0,0 +1,112 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/public/cpp/input_devices/input_device_client.h" + +#include "base/logging.h" + +namespace mus { + +InputDeviceClient::InputDeviceClient() : binding_(this) { + InputDeviceManager::SetInstance(this); +} + +InputDeviceClient::~InputDeviceClient() { + InputDeviceManager::ClearInstance(); +} + +void InputDeviceClient::Connect(mojom::InputDeviceServerPtr server) { + DCHECK(server.is_bound()); + + server->AddObserver(binding_.CreateInterfacePtrAndBind()); +} + +void InputDeviceClient::OnKeyboardDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) { + keyboard_devices_ = devices.To<std::vector<ui::InputDevice>>(); + FOR_EACH_OBSERVER(ui::InputDeviceEventObserver, observers_, + OnKeyboardDeviceConfigurationChanged()); +} + +void InputDeviceClient::OnTouchscreenDeviceConfigurationChanged( + mojo::Array<ui::TouchscreenDevice> devices) { + touchscreen_devices_ = devices.To<std::vector<ui::TouchscreenDevice>>(); + FOR_EACH_OBSERVER(ui::InputDeviceEventObserver, observers_, + OnTouchscreenDeviceConfigurationChanged()); +} + +void InputDeviceClient::OnMouseDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) { + mouse_devices_ = devices.To<std::vector<ui::InputDevice>>(); + FOR_EACH_OBSERVER(ui::InputDeviceEventObserver, observers_, + OnMouseDeviceConfigurationChanged()); +} + +void InputDeviceClient::OnTouchpadDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) { + touchpad_devices_ = devices.To<std::vector<ui::InputDevice>>(); + FOR_EACH_OBSERVER(ui::InputDeviceEventObserver, observers_, + OnTouchpadDeviceConfigurationChanged()); +} + +void InputDeviceClient::OnDeviceListsComplete( + mojo::Array<ui::InputDevice> keyboard_devices, + mojo::Array<ui::TouchscreenDevice> touchscreen_devices, + mojo::Array<ui::InputDevice> mouse_devices, + mojo::Array<ui::InputDevice> touchpad_devices) { + // Update the cached device lists if the received list isn't empty. + if (!keyboard_devices.empty()) + OnKeyboardDeviceConfigurationChanged(std::move(keyboard_devices)); + if (!touchscreen_devices.empty()) + OnTouchscreenDeviceConfigurationChanged(std::move(touchscreen_devices)); + if (!mouse_devices.empty()) + OnMouseDeviceConfigurationChanged(std::move(mouse_devices)); + if (!touchpad_devices.empty()) + OnTouchpadDeviceConfigurationChanged(std::move(touchpad_devices)); + + if (!device_lists_complete_) { + device_lists_complete_ = true; + FOR_EACH_OBSERVER(ui::InputDeviceEventObserver, observers_, + OnDeviceListsComplete()); + } +} + +void InputDeviceClient::AddObserver(ui::InputDeviceEventObserver* observer) { + observers_.AddObserver(observer); +} + +void InputDeviceClient::RemoveObserver(ui::InputDeviceEventObserver* observer) { + observers_.RemoveObserver(observer); +} + +const std::vector<ui::InputDevice>& InputDeviceClient::GetKeyboardDevices() + const { + return keyboard_devices_; +} + +const std::vector<ui::TouchscreenDevice>& +InputDeviceClient::GetTouchscreenDevices() const { + return touchscreen_devices_; +} + +const std::vector<ui::InputDevice>& InputDeviceClient::GetMouseDevices() const { + return mouse_devices_; +} + +const std::vector<ui::InputDevice>& InputDeviceClient::GetTouchpadDevices() + const { + return touchpad_devices_; +} + +bool InputDeviceClient::AreDeviceListsComplete() const { + return device_lists_complete_; +} + +bool InputDeviceClient::AreTouchscreensEnabled() const { + // TODO(kylechar): This obviously isn't right. We either need to pass this + // state around or modify the interface. + return true; +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/input_devices/input_device_client.h b/chromium/components/mus/public/cpp/input_devices/input_device_client.h new file mode 100644 index 00000000000..88fa41d750f --- /dev/null +++ b/chromium/components/mus/public/cpp/input_devices/input_device_client.h @@ -0,0 +1,84 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_PUBLIC_CPP_INPUT_DEVICES_INPUT_DEVICE_CLIENT_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_INPUT_DEVICES_INPUT_DEVICE_CLIENT_H_ + +#include <vector> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/public/interfaces/input_devices/input_device_server.mojom.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "ui/events/devices/input_device.h" +#include "ui/events/devices/input_device_event_observer.h" +#include "ui/events/devices/input_device_manager.h" +#include "ui/events/devices/touchscreen_device.h" + +namespace mus { + +// Allows in-process client code to register as a InputDeviceEventObserver and +// get information about input-devices. InputDeviceClient itself acts as an +// InputDeviceObserverMojo and registers to get updates from InputDeviceServer. +// Essentially, InputDeviceClient forwards input-device events and caches +// input-device state. +class InputDeviceClient : public mojom::InputDeviceObserverMojo, + public ui::InputDeviceManager { + public: + InputDeviceClient(); + ~InputDeviceClient() override; + + // Connects to mojo:mus as an observer on InputDeviceServer to receive input + // device updates. + void Connect(mojom::InputDeviceServerPtr server); + + // ui::InputDeviceManager: + const std::vector<ui::InputDevice>& GetKeyboardDevices() const override; + const std::vector<ui::TouchscreenDevice>& GetTouchscreenDevices() + const override; + const std::vector<ui::InputDevice>& GetMouseDevices() const override; + const std::vector<ui::InputDevice>& GetTouchpadDevices() const override; + + bool AreDeviceListsComplete() const override; + bool AreTouchscreensEnabled() const override; + + void AddObserver(ui::InputDeviceEventObserver* observer) override; + void RemoveObserver(ui::InputDeviceEventObserver* observer) override; + + private: + // mojom::InputDeviceObserverMojo: + void OnKeyboardDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) override; + void OnTouchscreenDeviceConfigurationChanged( + mojo::Array<ui::TouchscreenDevice> devices) override; + void OnMouseDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) override; + void OnTouchpadDeviceConfigurationChanged( + mojo::Array<ui::InputDevice> devices) override; + void OnDeviceListsComplete( + mojo::Array<ui::InputDevice> keyboard_devices, + mojo::Array<ui::TouchscreenDevice> touchscreen_devices, + mojo::Array<ui::InputDevice> mouse_devices, + mojo::Array<ui::InputDevice> touchpad_devices) override; + + mojo::Binding<mojom::InputDeviceObserverMojo> binding_; + + // Holds the list of input devices and signal that we have received the lists + // after initialization. + std::vector<ui::InputDevice> keyboard_devices_; + std::vector<ui::TouchscreenDevice> touchscreen_devices_; + std::vector<ui::InputDevice> mouse_devices_; + std::vector<ui::InputDevice> touchpad_devices_; + bool device_lists_complete_ = false; + + // List of in-process observers. + base::ObserverList<ui::InputDeviceEventObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(InputDeviceClient); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_INPUT_DEVICES_INPUT_DEVICE_CLIENT_H_ diff --git a/chromium/components/mus/public/cpp/input_event_handler.h b/chromium/components/mus/public/cpp/input_event_handler.h new file mode 100644 index 00000000000..3d445b23bad --- /dev/null +++ b/chromium/components/mus/public/cpp/input_event_handler.h @@ -0,0 +1,43 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_INPUT_EVENT_HANDLER_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_INPUT_EVENT_HANDLER_H_ + +#include <memory> + +#include "base/callback_forward.h" + +namespace ui { +class Event; +} + +namespace mus { + +class Window; + +namespace mojom { +enum class EventResult; +} + +// Responsible for processing input events for mus::Window. +class InputEventHandler { + public: + // The event handler can asynchronously ack the event by taking ownership of + // the |ack_callback|. The callback takes an EventResult indicating if the + // handler has consumed the event. If the handler does not take ownership of + // the callback, then WindowTreeClient will ack the event as not consumed. + virtual void OnWindowInputEvent( + Window* target, + const ui::Event& event, + std::unique_ptr<base::Callback<void(mojom::EventResult)>>* + ack_callback) = 0; + + protected: + virtual ~InputEventHandler() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_INPUT_EVENT_HANDLER_H_ diff --git a/chromium/components/mus/public/cpp/lib/DEPS b/chromium/components/mus/public/cpp/lib/DEPS new file mode 100644 index 00000000000..c635ea6f5e4 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/gpu" +] diff --git a/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.cc b/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.cc new file mode 100644 index 00000000000..062c3f78477 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.cc @@ -0,0 +1,370 @@ +// 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 "components/mus/public/cpp/lib/command_buffer_client_impl.h" + +#include <stddef.h> +#include <stdint.h> + +#include <limits> +#include <utility> + +#include "base/logging.h" +#include "base/process/process_handle.h" +#include "base/threading/thread_restrictions.h" +#include "components/mus/common/gpu_type_converters.h" +#include "components/mus/common/mojo_buffer_backing.h" +#include "components/mus/common/mojo_gpu_memory_buffer.h" +#include "gpu/command_buffer/client/gpu_control_client.h" +#include "gpu/command_buffer/common/command_buffer_id.h" +#include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "mojo/public/cpp/system/platform_handle.h" + +namespace mus { + +namespace { + +bool CreateAndMapSharedBuffer(size_t size, + mojo::ScopedSharedBufferMapping* mapping, + mojo::ScopedSharedBufferHandle* handle) { + *handle = mojo::SharedBufferHandle::Create(size); + if (!handle->is_valid()) + return false; + + *mapping = (*handle)->Map(size); + if (!*mapping) + return false; + + return true; +} + +void MakeProgressCallback(gpu::CommandBuffer::State* output, + const gpu::CommandBuffer::State& input) { + *output = input; +} + +void InitializeCallback(mus::mojom::CommandBufferInitializeResultPtr* output, + mus::mojom::CommandBufferInitializeResultPtr input) { + *output = std::move(input); +} + +} // namespace + +CommandBufferClientImpl::CommandBufferClientImpl( + const std::vector<int32_t>& attribs, + mus::mojom::CommandBufferPtr command_buffer_ptr) + : gpu_control_client_(nullptr), + destroyed_(false), + attribs_(attribs), + client_binding_(this), + command_buffer_(std::move(command_buffer_ptr)), + command_buffer_id_(), + last_put_offset_(-1), + next_transfer_buffer_id_(0), + next_image_id_(0), + next_fence_sync_release_(1), + flushed_fence_sync_release_(0) { + command_buffer_.set_connection_error_handler( + base::Bind(&CommandBufferClientImpl::Destroyed, base::Unretained(this), + gpu::error::kUnknown, gpu::error::kLostContext)); +} + +CommandBufferClientImpl::~CommandBufferClientImpl() {} + +bool CommandBufferClientImpl::Initialize() { + const size_t kSharedStateSize = sizeof(gpu::CommandBufferSharedState); + mojo::ScopedSharedBufferHandle handle; + bool result = + CreateAndMapSharedBuffer(kSharedStateSize, &shared_state_, &handle); + if (!result) + return false; + + shared_state()->Initialize(); + + mus::mojom::CommandBufferClientPtr client_ptr; + client_binding_.Bind(GetProxy(&client_ptr)); + + mus::mojom::CommandBufferInitializeResultPtr initialize_result; + command_buffer_->Initialize( + std::move(client_ptr), std::move(handle), + mojo::Array<int32_t>::From(attribs_), + base::Bind(&InitializeCallback, &initialize_result)); + + base::ThreadRestrictions::ScopedAllowWait wait; + if (!command_buffer_.WaitForIncomingResponse()) { + VLOG(1) << "Channel encountered error while creating command buffer."; + return false; + } + + if (!initialize_result) { + VLOG(1) << "Command buffer cannot be initialized successfully."; + return false; + } + + DCHECK_EQ(gpu::CommandBufferNamespace::MOJO, + initialize_result->command_buffer_namespace); + command_buffer_id_ = gpu::CommandBufferId::FromUnsafeValue( + initialize_result->command_buffer_id); + capabilities_ = initialize_result->capabilities; + return true; +} + +gpu::CommandBuffer::State CommandBufferClientImpl::GetLastState() { + return last_state_; +} + +int32_t CommandBufferClientImpl::GetLastToken() { + TryUpdateState(); + return last_state_.token; +} + +void CommandBufferClientImpl::Flush(int32_t put_offset) { + if (last_put_offset_ == put_offset) + return; + + last_put_offset_ = put_offset; + command_buffer_->Flush(put_offset); + flushed_fence_sync_release_ = next_fence_sync_release_ - 1; +} + +void CommandBufferClientImpl::OrderingBarrier(int32_t put_offset) { + // TODO(jamesr): Implement this more efficiently. + Flush(put_offset); +} + +void CommandBufferClientImpl::WaitForTokenInRange(int32_t start, int32_t end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.token) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + } +} + +void CommandBufferClientImpl::WaitForGetOffsetInRange(int32_t start, + int32_t end) { + TryUpdateState(); + while (!InRange(start, end, last_state_.get_offset) && + last_state_.error == gpu::error::kNoError) { + MakeProgressAndUpdateState(); + } +} + +void CommandBufferClientImpl::SetGetBuffer(int32_t shm_id) { + command_buffer_->SetGetBuffer(shm_id); + last_put_offset_ = -1; +} + +scoped_refptr<gpu::Buffer> CommandBufferClientImpl::CreateTransferBuffer( + size_t size, + int32_t* id) { + if (size >= std::numeric_limits<uint32_t>::max()) + return NULL; + + mojo::ScopedSharedBufferMapping mapping; + mojo::ScopedSharedBufferHandle handle; + if (!CreateAndMapSharedBuffer(size, &mapping, &handle)) { + if (last_state_.error == gpu::error::kNoError) + last_state_.error = gpu::error::kLostContext; + return NULL; + } + + *id = ++next_transfer_buffer_id_; + + command_buffer_->RegisterTransferBuffer(*id, std::move(handle), + static_cast<uint32_t>(size)); + + std::unique_ptr<gpu::BufferBacking> backing( + new mus::MojoBufferBacking(std::move(mapping), size)); + scoped_refptr<gpu::Buffer> buffer(new gpu::Buffer(std::move(backing))); + return buffer; +} + +void CommandBufferClientImpl::DestroyTransferBuffer(int32_t id) { + command_buffer_->DestroyTransferBuffer(id); +} + +void CommandBufferClientImpl::SetGpuControlClient(gpu::GpuControlClient* c) { + gpu_control_client_ = c; +} + +gpu::Capabilities CommandBufferClientImpl::GetCapabilities() { + return capabilities_; +} + +int32_t CommandBufferClientImpl::CreateImage(ClientBuffer buffer, + size_t width, + size_t height, + unsigned internalformat) { + int32_t new_id = ++next_image_id_; + + gfx::Size size(static_cast<int32_t>(width), static_cast<int32_t>(height)); + + mus::MojoGpuMemoryBufferImpl* gpu_memory_buffer = + mus::MojoGpuMemoryBufferImpl::FromClientBuffer(buffer); + gfx::GpuMemoryBufferHandle handle = gpu_memory_buffer->GetHandle(); + + bool requires_sync_point = false; + if (handle.type != gfx::SHARED_MEMORY_BUFFER) { + requires_sync_point = true; + NOTIMPLEMENTED(); + return -1; + } + + base::SharedMemoryHandle dupd_handle = + base::SharedMemory::DuplicateHandle(handle.handle); +#if defined(OS_WIN) + HANDLE platform_handle = dupd_handle.GetHandle(); +#else + int platform_handle = dupd_handle.fd; +#endif + + mojo::ScopedHandle scoped_handle = mojo::WrapPlatformFile(platform_handle); + command_buffer_->CreateImage( + new_id, std::move(scoped_handle), handle.type, std::move(size), + static_cast<int32_t>(gpu_memory_buffer->GetFormat()), internalformat); + if (requires_sync_point) { + NOTIMPLEMENTED(); + // TODO(jam): need to support this if we support types other than + // SHARED_MEMORY_BUFFER. + // gpu_memory_buffer_manager->SetDestructionSyncPoint(gpu_memory_buffer, + // InsertSyncPoint()); + } + + return new_id; +} + +void CommandBufferClientImpl::DestroyImage(int32_t id) { + command_buffer_->DestroyImage(id); +} + +int32_t CommandBufferClientImpl::CreateGpuMemoryBufferImage( + size_t width, + size_t height, + unsigned internalformat, + unsigned usage) { + std::unique_ptr<gfx::GpuMemoryBuffer> buffer( + mus::MojoGpuMemoryBufferImpl::Create( + gfx::Size(static_cast<int>(width), static_cast<int>(height)), + gpu::DefaultBufferFormatForImageFormat(internalformat), + gfx::BufferUsage::SCANOUT)); + if (!buffer) + return -1; + + return CreateImage(buffer->AsClientBuffer(), width, height, internalformat); +} + +int32_t CommandBufferClientImpl::GetImageGpuMemoryBufferId(unsigned image_id) { + // TODO(erikchen): Once this class supports IOSurface GpuMemoryBuffer backed + // images, it will also need to keep a local cache from image id to + // GpuMemoryBuffer id. + NOTIMPLEMENTED(); + return -1; +} + +void CommandBufferClientImpl::SignalQuery(uint32_t query, + const base::Closure& callback) { + // TODO(piman) + NOTIMPLEMENTED(); +} + +void CommandBufferClientImpl::Destroyed(int32_t lost_reason, int32_t error) { + if (destroyed_) + return; + last_state_.context_lost_reason = + static_cast<gpu::error::ContextLostReason>(lost_reason); + last_state_.error = static_cast<gpu::error::Error>(error); + if (gpu_control_client_) + gpu_control_client_->OnGpuControlLostContext(); + destroyed_ = true; +} + +void CommandBufferClientImpl::SignalAck(uint32_t id) {} + +void CommandBufferClientImpl::SwapBuffersCompleted(int32_t result) {} + +void CommandBufferClientImpl::UpdateState( + const gpu::CommandBuffer::State& state) {} + +void CommandBufferClientImpl::UpdateVSyncParameters(int64_t timebase, + int64_t interval) {} + +void CommandBufferClientImpl::TryUpdateState() { + if (last_state_.error == gpu::error::kNoError) + shared_state()->Read(&last_state_); +} + +void CommandBufferClientImpl::MakeProgressAndUpdateState() { + gpu::CommandBuffer::State state; + command_buffer_->MakeProgress(last_state_.get_offset, + base::Bind(&MakeProgressCallback, &state)); + + base::ThreadRestrictions::ScopedAllowWait wait; + if (!command_buffer_.WaitForIncomingResponse()) { + VLOG(1) << "Channel encountered error while waiting for command buffer."; + // TODO(piman): is it ok for this to re-enter? + Destroyed(gpu::error::kUnknown, gpu::error::kLostContext); + return; + } + + if (state.generation - last_state_.generation < 0x80000000U) + last_state_ = state; +} + +void CommandBufferClientImpl::SetLock(base::Lock* lock) {} + +void CommandBufferClientImpl::EnsureWorkVisible() { + // This is only relevant for out-of-process command buffers. +} + +gpu::CommandBufferNamespace CommandBufferClientImpl::GetNamespaceID() const { + return gpu::CommandBufferNamespace::MOJO; +} + +gpu::CommandBufferId CommandBufferClientImpl::GetCommandBufferID() const { + return command_buffer_id_; +} + +int32_t CommandBufferClientImpl::GetExtraCommandBufferData() const { + return 0; +} + +uint64_t CommandBufferClientImpl::GenerateFenceSyncRelease() { + return next_fence_sync_release_++; +} + +bool CommandBufferClientImpl::IsFenceSyncRelease(uint64_t release) { + return release != 0 && release < next_fence_sync_release_; +} + +bool CommandBufferClientImpl::IsFenceSyncFlushed(uint64_t release) { + return release != 0 && release <= flushed_fence_sync_release_; +} + +bool CommandBufferClientImpl::IsFenceSyncFlushReceived(uint64_t release) { + return IsFenceSyncFlushed(release); +} + +void CommandBufferClientImpl::SignalSyncToken(const gpu::SyncToken& sync_token, + const base::Closure& callback) { + // TODO(dyen) + NOTIMPLEMENTED(); +} + +bool CommandBufferClientImpl::CanWaitUnverifiedSyncToken( + const gpu::SyncToken* sync_token) { + // Right now, MOJO_LOCAL is only used by trusted code, so it is safe to wait + // on a sync token in MOJO_LOCAL command buffer. + if (sync_token->namespace_id() == gpu::CommandBufferNamespace::MOJO_LOCAL) + return true; + + // It is also safe to wait on the same context. + if (sync_token->namespace_id() == gpu::CommandBufferNamespace::MOJO && + sync_token->command_buffer_id() == GetCommandBufferID()) + return true; + + return false; +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.h b/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.h new file mode 100644 index 00000000000..80a46e93b7f --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/command_buffer_client_impl.h @@ -0,0 +1,117 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_LIB_COMMAND_BUFFER_CLIENT_IMPL_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_LIB_COMMAND_BUFFER_CLIENT_IMPL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <map> +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "gpu/command_buffer/client/gpu_control.h" +#include "gpu/command_buffer/common/command_buffer.h" +#include "gpu/command_buffer/common/command_buffer_id.h" +#include "gpu/command_buffer/common/command_buffer_shared.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace base { +class RunLoop; +} + +namespace mus { +class CommandBufferClientImpl; + +class CommandBufferClientImpl : public mus::mojom::CommandBufferClient, + public gpu::CommandBuffer, + public gpu::GpuControl { + public: + explicit CommandBufferClientImpl( + const std::vector<int32_t>& attribs, + mus::mojom::CommandBufferPtr command_buffer_ptr); + ~CommandBufferClientImpl() override; + bool Initialize(); + + // CommandBuffer implementation: + 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: + void SetGpuControlClient(gpu::GpuControlClient*) override; + 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; + int32_t GetImageGpuMemoryBufferId(unsigned image_id) override; + void SignalQuery(uint32_t query, const base::Closure& callback) override; + void SetLock(base::Lock*) override; + void EnsureWorkVisible() override; + gpu::CommandBufferNamespace GetNamespaceID() const override; + gpu::CommandBufferId GetCommandBufferID() const override; + int32_t GetExtraCommandBufferData() const override; + uint64_t GenerateFenceSyncRelease() override; + bool IsFenceSyncRelease(uint64_t release) override; + bool IsFenceSyncFlushed(uint64_t release) override; + bool IsFenceSyncFlushReceived(uint64_t release) override; + void SignalSyncToken(const gpu::SyncToken& sync_token, + const base::Closure& callback) override; + bool CanWaitUnverifiedSyncToken(const gpu::SyncToken* sync_token) override; + + private: + // mus::mojom::CommandBufferClient implementation: + void Destroyed(int32_t lost_reason, int32_t error) override; + void SignalAck(uint32_t id) override; + void SwapBuffersCompleted(int32_t result) override; + void UpdateState(const gpu::CommandBuffer::State& state) override; + void UpdateVSyncParameters(int64_t timebase, int64_t interval) override; + + void TryUpdateState(); + void MakeProgressAndUpdateState(); + + gpu::CommandBufferSharedState* shared_state() const { + return reinterpret_cast<gpu::CommandBufferSharedState*>( + shared_state_.get()); + } + + gpu::GpuControlClient* gpu_control_client_; + bool destroyed_; + std::vector<int32_t> attribs_; + mojo::Binding<mus::mojom::CommandBufferClient> client_binding_; + mus::mojom::CommandBufferPtr command_buffer_; + + gpu::CommandBufferId command_buffer_id_; + gpu::Capabilities capabilities_; + State last_state_; + mojo::ScopedSharedBufferMapping shared_state_; + int32_t last_put_offset_; + int32_t next_transfer_buffer_id_; + + // Image IDs are allocated in sequence. + int next_image_id_; + + uint64_t next_fence_sync_release_; + uint64_t flushed_fence_sync_release_; +}; + +} // mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_LIB_COMMAND_BUFFER_CLIENT_IMPL_H_ diff --git a/chromium/components/mus/public/cpp/lib/context_provider.cc b/chromium/components/mus/public/cpp/lib/context_provider.cc new file mode 100644 index 00000000000..02717112dd7 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/context_provider.cc @@ -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. + +#include "components/mus/public/cpp/context_provider.h" + +#include <stdint.h> + +#include "base/logging.h" +#include "components/mus/public/cpp/gles2_context.h" +#include "services/shell/public/cpp/connector.h" + +namespace mus { + +ContextProvider::ContextProvider(shell::Connector* connector) + : connector_(connector->Clone()) {} + +bool ContextProvider::BindToCurrentThread() { + if (connector_) { + context_ = GLES2Context::CreateOffscreenContext(std::vector<int32_t>(), + connector_.get()); + // We don't need the connector anymore, so release it. + connector_.reset(); + } + return !!context_; +} + +gpu::gles2::GLES2Interface* ContextProvider::ContextGL() { + return context_->interface(); +} + +gpu::ContextSupport* ContextProvider::ContextSupport() { + if (!context_) + return NULL; + return context_->context_support(); +} + +class GrContext* ContextProvider::GrContext() { + return NULL; +} + +void ContextProvider::InvalidateGrContext(uint32_t state) {} + +gpu::Capabilities ContextProvider::ContextCapabilities() { + gpu::Capabilities capabilities; + // Enabled the CHROMIUM_image extension to use GpuMemoryBuffers. The + // implementation of which is used in CommandBufferDriver. + capabilities.image = true; + return capabilities; +} + +base::Lock* ContextProvider::GetLock() { + // This context provider is not used on multiple threads. + NOTREACHED(); + return nullptr; +} + +ContextProvider::~ContextProvider() { +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/gles2_context.cc b/chromium/components/mus/public/cpp/lib/gles2_context.cc new file mode 100644 index 00000000000..84363b15cfe --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/gles2_context.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 "components/mus/public/cpp/gles2_context.h" + +#include <stddef.h> +#include <stdint.h> + +#include <utility> + +#include "components/mus/common/gpu_service.h" +#include "components/mus/public/cpp/lib/command_buffer_client_impl.h" +#include "components/mus/public/interfaces/command_buffer.mojom.h" +#include "components/mus/public/interfaces/gpu_service.mojom.h" +#include "gpu/command_buffer/client/gles2_cmd_helper.h" +#include "gpu/command_buffer/client/shared_memory_limits.h" +#include "gpu/command_buffer/client/transfer_buffer.h" +#include "gpu/ipc/client/command_buffer_proxy_impl.h" +#include "mojo/public/cpp/system/core.h" +#include "services/shell/public/cpp/connector.h" +#include "url/gurl.h" + +namespace mus { + +GLES2Context::GLES2Context() {} + +GLES2Context::~GLES2Context() {} + +bool GLES2Context::Initialize(const std::vector<int32_t>& attribs, + shell::Connector* connector) { + gpu::CommandBuffer* command_buffer = nullptr; + gpu::GpuControl* gpu_control = nullptr; + // TODO(penghuang): Use type gpu::gles2::ContextCreationAttribHelper for + // attribs. + if (!mus::GpuService::UseChromeGpuCommandBuffer()) { + mojom::GpuPtr gpu; + connector->ConnectToInterface("mojo:mus", &gpu); + mojom::CommandBufferPtr command_buffer_ptr; + gpu->CreateOffscreenGLES2Context(GetProxy(&command_buffer_ptr)); + command_buffer_client_impl_.reset( + new CommandBufferClientImpl(attribs, std::move(command_buffer_ptr))); + if (!command_buffer_client_impl_->Initialize()) + return false; + command_buffer = command_buffer_client_impl_.get(); + gpu_control = command_buffer_client_impl_.get(); + } else { + scoped_refptr<gpu::GpuChannelHost> gpu_channel_host = + GpuService::GetInstance()->EstablishGpuChannelSync(); + if (!gpu_channel_host) + return false; + gpu::SurfaceHandle surface_handle = gfx::kNullAcceleratedWidget; + // TODO(penghuang): support shared group. + gpu::CommandBufferProxyImpl* shared_command_buffer = nullptr; + gpu::GpuStreamId stream_id = gpu::GpuStreamId::GPU_STREAM_DEFAULT; + gpu::GpuStreamPriority stream_priority = gpu::GpuStreamPriority::NORMAL; + gpu::gles2::ContextCreationAttribHelper attributes; + // TODO(penghuang): figure a useful active_url. + GURL active_url; + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + base::ThreadTaskRunnerHandle::Get(); + if (!attributes.Parse(attribs)) + return false; + command_buffer_proxy_impl_ = gpu::CommandBufferProxyImpl::Create( + std::move(gpu_channel_host), surface_handle, shared_command_buffer, + stream_id, stream_priority, attributes, active_url, + std::move(task_runner)); + if (!command_buffer_proxy_impl_) + return false; + command_buffer = command_buffer_proxy_impl_.get(); + gpu_control = command_buffer_proxy_impl_.get(); + } + + constexpr gpu::SharedMemoryLimits default_limits; + gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer)); + if (!gles2_helper_->Initialize(default_limits.command_buffer_size)) + return false; + gles2_helper_->SetAutomaticFlushes(false); + transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get())); + gpu::Capabilities capabilities = gpu_control->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, gpu_control)); + if (!implementation_->Initialize(default_limits.start_transfer_buffer_size, + default_limits.min_transfer_buffer_size, + default_limits.max_transfer_buffer_size, + default_limits.mapped_memory_reclaim_limit)) + return false; + return true; +} + +// static +std::unique_ptr<GLES2Context> GLES2Context::CreateOffscreenContext( + const std::vector<int32_t>& attribs, + shell::Connector* connector) { + std::unique_ptr<GLES2Context> gles2_context(new GLES2Context); + if (!gles2_context->Initialize(attribs, connector)) + gles2_context.reset(); + return gles2_context; +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/in_flight_change.cc b/chromium/components/mus/public/cpp/lib/in_flight_change.cc new file mode 100644 index 00000000000..04878742350 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/in_flight_change.cc @@ -0,0 +1,220 @@ +// 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 "components/mus/public/cpp/lib/in_flight_change.h" + +#include "components/mus/public/cpp/lib/window_private.h" +#include "components/mus/public/cpp/window_tree_client.h" + +namespace mus { + +// InFlightChange ------------------------------------------------------------- + +InFlightChange::InFlightChange(Window* window, ChangeType type) + : window_(window), change_type_(type) {} + +InFlightChange::~InFlightChange() {} + +bool InFlightChange::Matches(const InFlightChange& change) const { + DCHECK(change.window_ == window_ && change.change_type_ == change_type_); + return true; +} + +void InFlightChange::ChangeFailed() {} + +// InFlightBoundsChange ------------------------------------------------------- + +InFlightBoundsChange::InFlightBoundsChange(Window* window, + const gfx::Rect& revert_bounds) + : InFlightChange(window, ChangeType::BOUNDS), + revert_bounds_(revert_bounds) {} + +void InFlightBoundsChange::SetRevertValueFrom(const InFlightChange& change) { + revert_bounds_ = + static_cast<const InFlightBoundsChange&>(change).revert_bounds_; +} + +void InFlightBoundsChange::Revert() { + WindowPrivate(window()).LocalSetBounds(window()->bounds(), revert_bounds_); +} + +// CrashInFlightChange -------------------------------------------------------- + +CrashInFlightChange::CrashInFlightChange(Window* window, ChangeType type) + : InFlightChange(window, type) {} + +CrashInFlightChange::~CrashInFlightChange() {} + +void CrashInFlightChange::SetRevertValueFrom(const InFlightChange& change) { + CHECK(false); +} + +void CrashInFlightChange::ChangeFailed() { + DLOG(ERROR) << "changed failed, type=" << static_cast<int>(change_type()); + CHECK(false); +} + +void CrashInFlightChange::Revert() { + CHECK(false); +} + +// InFlightWindowChange ------------------------------------------------------- + +InFlightWindowTreeClientChange::InFlightWindowTreeClientChange( + WindowTreeClient* client, + Window* revert_value, + ChangeType type) + : InFlightChange(nullptr, type), + client_(client), + revert_window_(nullptr) { + SetRevertWindow(revert_value); +} + +InFlightWindowTreeClientChange::~InFlightWindowTreeClientChange() { + SetRevertWindow(nullptr); +} + +void InFlightWindowTreeClientChange::SetRevertValueFrom( + const InFlightChange& change) { + SetRevertWindow(static_cast<const InFlightWindowTreeClientChange&>(change) + .revert_window_); +} + +void InFlightWindowTreeClientChange::SetRevertWindow(Window* window) { + if (revert_window_) + revert_window_->RemoveObserver(this); + revert_window_ = window; + if (revert_window_) + revert_window_->AddObserver(this); +} + +void InFlightWindowTreeClientChange::OnWindowDestroying(Window* window) { + SetRevertWindow(nullptr); +} + +// InFlightCaptureChange ------------------------------------------------------ + +InFlightCaptureChange::InFlightCaptureChange( + WindowTreeClient* client, Window* revert_value) + : InFlightWindowTreeClientChange(client, + revert_value, + ChangeType::CAPTURE) {} + +InFlightCaptureChange::~InFlightCaptureChange() {} + +void InFlightCaptureChange::Revert() { + client()->LocalSetCapture(revert_window()); +} + +// InFlightFocusChange -------------------------------------------------------- + +InFlightFocusChange::InFlightFocusChange( + WindowTreeClient* client, + Window* revert_value) + : InFlightWindowTreeClientChange(client, + revert_value, + ChangeType::FOCUS) {} + +InFlightFocusChange::~InFlightFocusChange() {} + +void InFlightFocusChange::Revert() { + client()->LocalSetFocus(revert_window()); +} + +// InFlightPropertyChange ----------------------------------------------------- + +InFlightPropertyChange::InFlightPropertyChange( + Window* window, + const std::string& property_name, + const mojo::Array<uint8_t>& revert_value) + : InFlightChange(window, ChangeType::PROPERTY), + property_name_(property_name), + revert_value_(revert_value.Clone()) {} + +InFlightPropertyChange::~InFlightPropertyChange() {} + +bool InFlightPropertyChange::Matches(const InFlightChange& change) const { + return static_cast<const InFlightPropertyChange&>(change).property_name_ == + property_name_; +} + +void InFlightPropertyChange::SetRevertValueFrom(const InFlightChange& change) { + revert_value_ = + static_cast<const InFlightPropertyChange&>(change).revert_value_.Clone(); +} + +void InFlightPropertyChange::Revert() { + WindowPrivate(window()) + .LocalSetSharedProperty(property_name_, std::move(revert_value_)); +} + +// InFlightPredefinedCursorChange --------------------------------------------- + +InFlightPredefinedCursorChange::InFlightPredefinedCursorChange( + Window* window, + mojom::Cursor revert_value) + : InFlightChange(window, ChangeType::PREDEFINED_CURSOR), + revert_cursor_(revert_value) {} + +InFlightPredefinedCursorChange::~InFlightPredefinedCursorChange() {} + +void InFlightPredefinedCursorChange::SetRevertValueFrom( + const InFlightChange& change) { + revert_cursor_ = + static_cast<const InFlightPredefinedCursorChange&>(change).revert_cursor_; +} + +void InFlightPredefinedCursorChange::Revert() { + WindowPrivate(window()).LocalSetPredefinedCursor(revert_cursor_); +} + +// InFlightVisibleChange ------------------------------------------------------- + +InFlightVisibleChange::InFlightVisibleChange(Window* window, + bool revert_value) + : InFlightChange(window, ChangeType::VISIBLE), + revert_visible_(revert_value) {} + +InFlightVisibleChange::~InFlightVisibleChange() {} + +void InFlightVisibleChange::SetRevertValueFrom(const InFlightChange& change) { + revert_visible_ = + static_cast<const InFlightVisibleChange&>(change).revert_visible_; +} + +void InFlightVisibleChange::Revert() { + WindowPrivate(window()).LocalSetVisible(revert_visible_); +} + +// InFlightOpacityChange ------------------------------------------------------- + +InFlightOpacityChange::InFlightOpacityChange(Window* window, float revert_value) + : InFlightChange(window, ChangeType::OPACITY), + revert_opacity_(revert_value) {} + +InFlightOpacityChange::~InFlightOpacityChange() {} + +void InFlightOpacityChange::SetRevertValueFrom(const InFlightChange& change) { + revert_opacity_ = + static_cast<const InFlightOpacityChange&>(change).revert_opacity_; +} + +void InFlightOpacityChange::Revert() { + WindowPrivate(window()).LocalSetOpacity(revert_opacity_); +} + +// InFlightSetModalChange ------------------------------------------------------ + +InFlightSetModalChange::InFlightSetModalChange(Window* window) + : InFlightChange(window, ChangeType::SET_MODAL) {} + +InFlightSetModalChange::~InFlightSetModalChange() {} + +void InFlightSetModalChange::SetRevertValueFrom(const InFlightChange& change) {} + +void InFlightSetModalChange::Revert() { + WindowPrivate(window()).LocalUnsetModal(); +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/in_flight_change.h b/chromium/components/mus/public/cpp/lib/in_flight_change.h new file mode 100644 index 00000000000..bf77fc6e6b1 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/in_flight_change.h @@ -0,0 +1,298 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_LIB_IN_FLIGHT_CHANGE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_LIB_IN_FLIGHT_CHANGE_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "components/mus/public/cpp/window_observer.h" +#include "mojo/public/cpp/bindings/array.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { + +namespace mojom { +enum class Cursor : int32_t; +} + +class Window; +class WindowTreeClient; + +enum class ChangeType { + ADD_CHILD, + ADD_TRANSIENT_WINDOW, + BOUNDS, + CAPTURE, + DELETE_WINDOW, + FOCUS, + NEW_WINDOW, + NEW_TOP_LEVEL_WINDOW, + OPACITY, + PREDEFINED_CURSOR, + PROPERTY, + REMOVE_CHILD, + REMOVE_TRANSIENT_WINDOW_FROM_PARENT, + REORDER, + SET_MODAL, + VISIBLE, +}; + +// InFlightChange is used to track function calls to the server and take the +// appropriate action when the call fails, or the same property changes while +// waiting for the response. When a function is called on the server an +// InFlightChange is created. The function call is complete when +// OnChangeCompleted() is received from the server. The following may occur +// while waiting for a response: +// . A new value is encountered from the server. For example, the bounds of +// a window is locally changed and while waiting for the ack +// OnWindowBoundsChanged() is received. +// When this happens SetRevertValueFrom() is invoked on the InFlightChange. +// The expectation is SetRevertValueFrom() takes the value to revert from the +// supplied change. +// . While waiting for the ack the property is again modified locally. When +// this happens a new InFlightChange is created. Once the ack for the first +// call is received, and the server rejected the change ChangeFailed() is +// invoked on the first change followed by SetRevertValueFrom() on the second +// InFlightChange. This allows the new change to update the value to revert +// should the second call fail. +// . If the server responds that the call failed and there is not another +// InFlightChange for the same window outstanding, then ChangeFailed() is +// invoked followed by Revert(). The expectation is Revert() sets the +// appropriate value back on the Window. +// +// In general there are two classes of changes: +// 1. We are the only side allowed to make the change. +// 2. The change can also be applied by another client. For example, the +// window manager may change the bounds as well as the local client. +// +// For (1) use CrashInFlightChange. As the name implies this change CHECKs that +// the change succeeded. Use the following pattern for this. This code goes +// where the change is sent to the server (in WindowTreeClient): +// const uint32_t change_id = +// ScheduleInFlightChange(base::WrapUnique(new CrashInFlightChange( +// window, ChangeType::REORDER))); +// +// For (2) use the same pattern as (1), but in the on change callback from the +// server (e.g. OnWindowBoundsChanged()) add the following: +// // value_from_server is the value supplied from the server. It corresponds +// // to the value of the property at the time the server processed the +// // change. If the local change fails, this is the value reverted to. +// InFlightBoundsChange new_change(window, value_from_server); +// if (ApplyServerChangeToExistingInFlightChange(new_change)) { +// // There was an in flight change for the same property. The in flight +// // change takes the value to revert from |new_change|. +// return; +// } +// +// // else case is no flight in change and the new value can be applied +// // immediately. +// WindowPrivate(window).LocalSetValue(new_value_from_server); +// +class InFlightChange { + public: + InFlightChange(Window* window, ChangeType type); + virtual ~InFlightChange(); + + // NOTE: for properties not associated with any window window is null. + Window* window() { return window_; } + const Window* window() const { return window_; } + ChangeType change_type() const { return change_type_; } + + // Returns true if the two changes are considered the same. This is only + // invoked if the window id and ChangeType match. + virtual bool Matches(const InFlightChange& change) const; + + // Called in two cases: + // . When the corresponding property on the server changes. In this case + // |change| corresponds to the value from the server. + // . When |change| unsuccesfully completes. + virtual void SetRevertValueFrom(const InFlightChange& change) = 0; + + // The change failed. Normally you need not take any action here as Revert() + // is called as appropriate. + virtual void ChangeFailed(); + + // The change failed and there is no pending change of the same type + // outstanding, revert the value. + virtual void Revert() = 0; + + private: + Window* window_; + const ChangeType change_type_; +}; + +class InFlightBoundsChange : public InFlightChange { + public: + InFlightBoundsChange(Window* window, const gfx::Rect& revert_bounds); + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + gfx::Rect revert_bounds_; + + DISALLOW_COPY_AND_ASSIGN(InFlightBoundsChange); +}; + +// Inflight change that crashes on failure. This is useful for changes that are +// expected to always complete. +class CrashInFlightChange : public InFlightChange { + public: + CrashInFlightChange(Window* window, ChangeType type); + ~CrashInFlightChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void ChangeFailed() override; + void Revert() override; + + private: + DISALLOW_COPY_AND_ASSIGN(CrashInFlightChange); +}; + +// Use this class for properties that are specific to the client, and not a +// particular window. For example, only a single window can have focus, so focus +// is specific to the client. +// +// This does not implement InFlightChange::Revert, subclasses must implement +// that to update the WindowTreeClient. +class InFlightWindowTreeClientChange : public InFlightChange, + public WindowObserver { + public: + InFlightWindowTreeClientChange(WindowTreeClient* client, + Window* revert_value, + ChangeType type); + ~InFlightWindowTreeClientChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + + protected: + WindowTreeClient* client() { return client_; } + Window* revert_window() { return revert_window_; } + + private: + void SetRevertWindow(Window* window); + + // WindowObserver: + void OnWindowDestroying(Window* window) override; + + WindowTreeClient* client_; + Window* revert_window_; + + DISALLOW_COPY_AND_ASSIGN(InFlightWindowTreeClientChange); +}; + +class InFlightCaptureChange : public InFlightWindowTreeClientChange { + public: + InFlightCaptureChange(WindowTreeClient* client, Window* revert_value); + ~InFlightCaptureChange() override; + + // InFlightChange: + void Revert() override; + + private: + DISALLOW_COPY_AND_ASSIGN(InFlightCaptureChange); +}; + +class InFlightFocusChange : public InFlightWindowTreeClientChange { + public: + InFlightFocusChange(WindowTreeClient* client, Window* revert_value); + ~InFlightFocusChange() override; + + // InFlightChange: + void Revert() override; + + private: + DISALLOW_COPY_AND_ASSIGN(InFlightFocusChange); +}; + +class InFlightPropertyChange : public InFlightChange { + public: + InFlightPropertyChange(Window* window, + const std::string& property_name, + const mojo::Array<uint8_t>& revert_value); + ~InFlightPropertyChange() override; + + // InFlightChange: + bool Matches(const InFlightChange& change) const override; + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + const std::string property_name_; + mojo::Array<uint8_t> revert_value_; + + DISALLOW_COPY_AND_ASSIGN(InFlightPropertyChange); +}; + +class InFlightPredefinedCursorChange : public InFlightChange { + public: + InFlightPredefinedCursorChange(Window* window, mojom::Cursor revert_value); + ~InFlightPredefinedCursorChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + mojom::Cursor revert_cursor_; + + DISALLOW_COPY_AND_ASSIGN(InFlightPredefinedCursorChange); +}; + +class InFlightVisibleChange : public InFlightChange { + public: + InFlightVisibleChange(Window* window, const bool revert_value); + ~InFlightVisibleChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + bool revert_visible_; + + DISALLOW_COPY_AND_ASSIGN(InFlightVisibleChange); +}; + +class InFlightOpacityChange : public InFlightChange { + public: + InFlightOpacityChange(Window* window, float revert_value); + ~InFlightOpacityChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + float revert_opacity_; + + DISALLOW_COPY_AND_ASSIGN(InFlightOpacityChange); +}; + +class InFlightSetModalChange : public InFlightChange { + public: + explicit InFlightSetModalChange(Window* window); + ~InFlightSetModalChange() override; + + // InFlightChange: + void SetRevertValueFrom(const InFlightChange& change) override; + void Revert() override; + + private: + DISALLOW_COPY_AND_ASSIGN(InFlightSetModalChange); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_LIB_IN_FLIGHT_CHANGE_H_ diff --git a/chromium/components/mus/public/cpp/lib/output_surface.cc b/chromium/components/mus/public/cpp/lib/output_surface.cc new file mode 100644 index 00000000000..86462ec95f4 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/output_surface.cc @@ -0,0 +1,70 @@ +// 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 "components/mus/public/cpp/output_surface.h" + +#include "base/bind.h" +#include "cc/output/compositor_frame.h" +#include "cc/output/compositor_frame_ack.h" +#include "cc/output/output_surface_client.h" +#include "components/mus/public/cpp/window_surface.h" + +namespace mus { + +OutputSurface::OutputSurface( + const scoped_refptr<cc::ContextProvider>& context_provider, + std::unique_ptr<mus::WindowSurface> surface) + : cc::OutputSurface(context_provider, nullptr, nullptr), + surface_(std::move(surface)) { + capabilities_.delegated_rendering = true; +} + +OutputSurface::~OutputSurface() {} + +bool OutputSurface::BindToClient(cc::OutputSurfaceClient* client) { + surface_->BindToThread(); + surface_->set_client(this); + return cc::OutputSurface::BindToClient(client); +} + +void OutputSurface::DetachFromClient() { + surface_.reset(); + cc::OutputSurface::DetachFromClient(); +} + +void OutputSurface::BindFramebuffer() { + // This is a delegating output surface, no framebuffer/direct drawing support. + NOTREACHED(); +} + +uint32_t OutputSurface::GetFramebufferCopyTextureFormat() { + // This is a delegating output surface, no framebuffer/direct drawing support. + NOTREACHED(); + return 0; +} + +void OutputSurface::SwapBuffers(cc::CompositorFrame frame) { + // TODO(fsamuel, rjkroege): We should probably throttle compositor frames. + client_->DidSwapBuffers(); + // OutputSurface owns WindowSurface, and so if OutputSurface is + // destroyed then SubmitCompositorFrame's callback will never get called. + // Thus, base::Unretained is safe here. + surface_->SubmitCompositorFrame( + std::move(frame), + base::Bind(&OutputSurface::SwapBuffersComplete, base::Unretained(this))); +} + +void OutputSurface::OnResourcesReturned( + mus::WindowSurface* surface, + mojo::Array<cc::ReturnedResource> resources) { + cc::CompositorFrameAck cfa; + cfa.resources = resources.To<cc::ReturnedResourceArray>(); + ReclaimResources(&cfa); +} + +void OutputSurface::SwapBuffersComplete() { + client_->DidSwapBuffersComplete(); +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/property_type_converters.cc b/chromium/components/mus/public/cpp/lib/property_type_converters.cc new file mode 100644 index 00000000000..17cacf1f249 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/property_type_converters.cc @@ -0,0 +1,207 @@ +// 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 "components/mus/public/cpp/property_type_converters.h" + +#include <stdint.h> + +#include "base/strings/utf_string_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace { + +// Maximum allowed height or width of a bitmap, in pixels. This limit prevents +// malformed bitmap headers from causing arbitrarily large memory allocations +// for pixel data. +const int kMaxBitmapSize = 4096; + +} // namespace + +namespace mojo { + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, gfx::Rect>::Convert( + const gfx::Rect& input) { + std::vector<uint8_t> vec(16); + vec[0] = (input.x() >> 24) & 0xFF; + vec[1] = (input.x() >> 16) & 0xFF; + vec[2] = (input.x() >> 8) & 0xFF; + vec[3] = input.x() & 0xFF; + vec[4] = (input.y() >> 24) & 0xFF; + vec[5] = (input.y() >> 16) & 0xFF; + vec[6] = (input.y() >> 8) & 0xFF; + vec[7] = input.y() & 0xFF; + vec[8] = (input.width() >> 24) & 0xFF; + vec[9] = (input.width() >> 16) & 0xFF; + vec[10] = (input.width() >> 8) & 0xFF; + vec[11] = input.width() & 0xFF; + vec[12] = (input.height() >> 24) & 0xFF; + vec[13] = (input.height() >> 16) & 0xFF; + vec[14] = (input.height() >> 8) & 0xFF; + vec[15] = input.height() & 0xFF; + return vec; +} + +// static +gfx::Rect TypeConverter<gfx::Rect, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + return gfx::Rect( + input[0] << 24 | input[1] << 16 | input[2] << 8 | input[3], + input[4] << 24 | input[5] << 16 | input[6] << 8 | input[7], + input[8] << 24 | input[9] << 16 | input[10] << 8 | input[11], + input[12] << 24 | input[13] << 16 | input[14] << 8 | input[15]); +} + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, gfx::Size>::Convert( + const gfx::Size& input) { + std::vector<uint8_t> vec(8); + vec[0] = (input.width() >> 24) & 0xFF; + vec[1] = (input.width() >> 16) & 0xFF; + vec[2] = (input.width() >> 8) & 0xFF; + vec[3] = input.width() & 0xFF; + vec[4] = (input.height() >> 24) & 0xFF; + vec[5] = (input.height() >> 16) & 0xFF; + vec[6] = (input.height() >> 8) & 0xFF; + vec[7] = input.height() & 0xFF; + return vec; +} + +// static +gfx::Size TypeConverter<gfx::Size, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + return gfx::Size(input[0] << 24 | input[1] << 16 | input[2] << 8 | input[3], + input[4] << 24 | input[5] << 16 | input[6] << 8 | input[7]); +} + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, int32_t>::Convert( + const int32_t& input) { + std::vector<uint8_t> vec(4); + vec[0] = (input >> 24) & 0xFF; + vec[1] = (input >> 16) & 0xFF; + vec[2] = (input >> 8) & 0xFF; + vec[3] = input & 0xFF; + return vec; +} + +// static +int32_t TypeConverter<int32_t, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + return input[0] << 24 | input[1] << 16 | input[2] << 8 | input[3]; +} + +// static +std::vector<uint8_t> +TypeConverter<std::vector<uint8_t>, base::string16>::Convert( + const base::string16& input) { + return ConvertTo<std::vector<uint8_t>>(base::UTF16ToUTF8(input)); +} + +// static +base::string16 TypeConverter<base::string16, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + return base::UTF8ToUTF16(ConvertTo<std::string>(input)); +} + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, std::string>::Convert( + const std::string& input) { + return std::vector<uint8_t>(input.begin(), input.end()); +} + +// static +std::string TypeConverter<std::string, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + return std::string(input.begin(), input.end()); +} + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, SkBitmap>::Convert( + const SkBitmap& input) { + // Empty images are valid to serialize and are represented by an empty vector. + if (input.isNull()) + return std::vector<uint8_t>(); + + // Only RGBA 8888 bitmaps with premultiplied alpha are supported. + if (input.colorType() != kBGRA_8888_SkColorType || + input.alphaType() != kPremul_SkAlphaType) { + NOTREACHED(); + return std::vector<uint8_t>(); + } + + // Sanity check the bitmap size. + int width = input.width(); + int height = input.height(); + if (width < 0 || width > kMaxBitmapSize || height < 0 || + height > kMaxBitmapSize) { + NOTREACHED(); + return std::vector<uint8_t>(); + } + + // Serialize the bitmap. The size is restricted so only 2 bytes are required + // per dimension. + std::vector<uint8_t> vec(4 + input.getSize()); + vec[0] = (width >> 8) & 0xFF; + vec[1] = width & 0xFF; + vec[2] = (height >> 8) & 0xFF; + vec[3] = height & 0xFF; + if (!input.copyPixelsTo(&vec[4], input.getSize())) + return std::vector<uint8_t>(); + return vec; +} + +// static +SkBitmap TypeConverter<SkBitmap, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + // Empty images are represented by empty vectors. + if (input.empty()) + return SkBitmap(); + + // Read and sanity check size. + int width = input[0] << 8 | input[1]; + int height = input[2] << 8 | input[3]; + if (width < 0 || width > kMaxBitmapSize || height < 0 || + height > kMaxBitmapSize) { + NOTREACHED(); + return SkBitmap(); + } + + // Try to allocate a bitmap of the appropriate size. + SkBitmap bitmap; + if (!bitmap.tryAllocPixels(SkImageInfo::Make( + width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType))) { + return SkBitmap(); + } + + // Ensure the vector contains the right amount of data. + if (input.size() != bitmap.getSize() + 4) { + NOTREACHED(); + return SkBitmap(); + } + + // Read the pixel data. + SkAutoLockPixels lock(bitmap); + memcpy(bitmap.getPixels(), &input[4], bitmap.getSize()); + return bitmap; +} + +// static +std::vector<uint8_t> TypeConverter<std::vector<uint8_t>, bool>::Convert( + bool input) { + std::vector<uint8_t> vec(1); + vec[0] = input ? 1 : 0; + return vec; +} + +// static +bool TypeConverter<bool, std::vector<uint8_t>>::Convert( + const std::vector<uint8_t>& input) { + // Empty vectors are interpreted as false. + return !input.empty() && (input[0] == 1); +} + +} // namespace mojo diff --git a/chromium/components/mus/public/cpp/lib/scoped_window_ptr.cc b/chromium/components/mus/public/cpp/lib/scoped_window_ptr.cc new file mode 100644 index 00000000000..360da1ff4b5 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/scoped_window_ptr.cc @@ -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. + +#include "components/mus/public/cpp/scoped_window_ptr.h" + +#include "components/mus/public/cpp/window.h" +#include "components/mus/public/cpp/window_observer.h" +#include "components/mus/public/cpp/window_tree_client.h" + +namespace mus { + +ScopedWindowPtr::ScopedWindowPtr(Window* window) : window_(window) { + window_->AddObserver(this); +} + +ScopedWindowPtr::~ScopedWindowPtr() { + if (window_) + DeleteWindowOrWindowManager(window_); + DetachFromWindow(); +} + +// static +void ScopedWindowPtr::DeleteWindowOrWindowManager(Window* window) { + if (window->window_tree()->GetRoots().count(window) > 0) + delete window->window_tree(); + else + window->Destroy(); +} + +void ScopedWindowPtr::DetachFromWindow() { + if (!window_) + return; + + window_->RemoveObserver(this); + window_ = nullptr; +} + +void ScopedWindowPtr::OnWindowDestroying(Window* window) { + DCHECK_EQ(window_, window); + DetachFromWindow(); +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window.cc b/chromium/components/mus/public/cpp/lib/window.cc new file mode 100644 index 00000000000..83785c4544e --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window.cc @@ -0,0 +1,891 @@ +// 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 "components/mus/public/cpp/window.h" + +#include <stddef.h> +#include <stdint.h> + +#include <set> +#include <string> + +#include "base/bind.h" +#include "base/macros.h" +#include "components/mus/common/transient_window_utils.h" +#include "components/mus/public/cpp/lib/window_private.h" +#include "components/mus/public/cpp/property_type_converters.h" +#include "components/mus/public/cpp/window_observer.h" +#include "components/mus/public/cpp/window_property.h" +#include "components/mus/public/cpp/window_surface.h" +#include "components/mus/public/cpp/window_tracker.h" +#include "components/mus/public/cpp/window_tree_client.h" +#include "components/mus/public/interfaces/window_manager.mojom.h" +#include "ui/display/display.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace mus { + +namespace { + +void NotifyWindowTreeChangeAtReceiver( + Window* receiver, + const WindowObserver::TreeChangeParams& params, + bool change_applied) { + WindowObserver::TreeChangeParams local_params = params; + local_params.receiver = receiver; + if (change_applied) { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(receiver).observers(), + OnTreeChanged(local_params)); + } else { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(receiver).observers(), + OnTreeChanging(local_params)); + } +} + +void NotifyWindowTreeChangeUp(Window* start_at, + const WindowObserver::TreeChangeParams& params, + bool change_applied) { + for (Window* current = start_at; current; current = current->parent()) + NotifyWindowTreeChangeAtReceiver(current, params, change_applied); +} + +void NotifyWindowTreeChangeDown(Window* start_at, + const WindowObserver::TreeChangeParams& params, + bool change_applied) { + NotifyWindowTreeChangeAtReceiver(start_at, params, change_applied); + Window::Children::const_iterator it = start_at->children().begin(); + for (; it != start_at->children().end(); ++it) + NotifyWindowTreeChangeDown(*it, params, change_applied); +} + +void NotifyWindowTreeChange(const WindowObserver::TreeChangeParams& params, + bool change_applied) { + NotifyWindowTreeChangeDown(params.target, params, change_applied); + if (params.old_parent) + NotifyWindowTreeChangeUp(params.old_parent, params, change_applied); + if (params.new_parent) + NotifyWindowTreeChangeUp(params.new_parent, params, change_applied); +} + +class ScopedTreeNotifier { + public: + ScopedTreeNotifier(Window* target, Window* old_parent, Window* new_parent) { + params_.target = target; + params_.old_parent = old_parent; + params_.new_parent = new_parent; + NotifyWindowTreeChange(params_, false); + } + ~ScopedTreeNotifier() { NotifyWindowTreeChange(params_, true); } + + private: + WindowObserver::TreeChangeParams params_; + + DISALLOW_COPY_AND_ASSIGN(ScopedTreeNotifier); +}; + +void RemoveChildImpl(Window* child, Window::Children* children) { + Window::Children::iterator it = + std::find(children->begin(), children->end(), child); + if (it != children->end()) { + children->erase(it); + WindowPrivate(child).ClearParent(); + } +} + +class OrderChangedNotifier { + public: + OrderChangedNotifier(Window* window, + Window* relative_window, + mojom::OrderDirection direction) + : window_(window), + relative_window_(relative_window), + direction_(direction) {} + + ~OrderChangedNotifier() {} + + void NotifyWindowReordering() { + FOR_EACH_OBSERVER( + WindowObserver, *WindowPrivate(window_).observers(), + OnWindowReordering(window_, relative_window_, direction_)); + } + + void NotifyWindowReordered() { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window_).observers(), + OnWindowReordered(window_, relative_window_, direction_)); + } + + private: + Window* window_; + Window* relative_window_; + mojom::OrderDirection direction_; + + DISALLOW_COPY_AND_ASSIGN(OrderChangedNotifier); +}; + +class ScopedSetBoundsNotifier { + public: + ScopedSetBoundsNotifier(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) + : window_(window), old_bounds_(old_bounds), new_bounds_(new_bounds) { + FOR_EACH_OBSERVER( + WindowObserver, *WindowPrivate(window_).observers(), + OnWindowBoundsChanging(window_, old_bounds_, new_bounds_)); + } + ~ScopedSetBoundsNotifier() { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window_).observers(), + OnWindowBoundsChanged(window_, old_bounds_, new_bounds_)); + } + + private: + Window* window_; + const gfx::Rect old_bounds_; + const gfx::Rect new_bounds_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSetBoundsNotifier); +}; + +// Some operations are only permitted in the client that created the window. +bool OwnsWindow(WindowTreeClient* client, Window* window) { + return !client || client->OwnsWindow(window); +} + +bool IsClientRoot(Window* window) { + return window->window_tree() && + window->window_tree()->GetRoots().count(window) > 0; +} + +bool OwnsWindowOrIsRoot(Window* window) { + return OwnsWindow(window->window_tree(), window) || IsClientRoot(window); +} + +void EmptyEmbedCallback(bool result) {} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// Window, public: + +void Window::Destroy() { + if (!OwnsWindowOrIsRoot(this)) + return; + + if (client_) + client_->DestroyWindow(this); + while (!children_.empty()) { + Window* child = children_.front(); + if (!OwnsWindow(client_, child)) { + WindowPrivate(child).ClearParent(); + children_.erase(children_.begin()); + } else { + child->Destroy(); + DCHECK(std::find(children_.begin(), children_.end(), child) == + children_.end()); + } + } + LocalDestroy(); +} + +void Window::SetBounds(const gfx::Rect& bounds) { + if (!OwnsWindowOrIsRoot(this)) + return; + if (bounds_ == bounds) + return; + if (client_) + client_->SetBounds(this, bounds_, bounds); + LocalSetBounds(bounds_, bounds); +} + +gfx::Rect Window::GetBoundsInRoot() const { + gfx::Vector2d offset; + for (const Window* w = parent(); w != nullptr; w = w->parent()) + offset += w->bounds().OffsetFromOrigin(); + return bounds() + offset; +} + +void Window::SetClientArea( + const gfx::Insets& client_area, + const std::vector<gfx::Rect>& additional_client_areas) { + if (!OwnsWindowOrIsRoot(this)) + return; + + if (client_) + client_->SetClientArea(server_id_, client_area, + additional_client_areas); + LocalSetClientArea(client_area, additional_client_areas); +} + +void Window::SetHitTestMask(const gfx::Rect& mask) { + if (!OwnsWindowOrIsRoot(this)) + return; + + if (hit_test_mask_ && *hit_test_mask_ == mask) + return; + + if (client_) + client_->SetHitTestMask(server_id_, mask); + hit_test_mask_.reset(new gfx::Rect(mask)); +} + +void Window::ClearHitTestMask() { + if (!OwnsWindowOrIsRoot(this)) + return; + + if (!hit_test_mask_) + return; + + if (client_) + client_->ClearHitTestMask(server_id_); + hit_test_mask_.reset(); +} + +void Window::SetVisible(bool value) { + if (visible_ == value) + return; + + if (client_) + client_->SetVisible(this, value); + LocalSetVisible(value); +} + +void Window::SetOpacity(float opacity) { + if (client_) + client_->SetOpacity(this, opacity); + LocalSetOpacity(opacity); +} + +void Window::SetPredefinedCursor(mus::mojom::Cursor cursor_id) { + if (cursor_id_ == cursor_id) + return; + + if (client_) + client_->SetPredefinedCursor(server_id_, cursor_id); + LocalSetPredefinedCursor(cursor_id); +} + +bool Window::IsDrawn() const { + if (!visible_) + return false; + return parent_ ? parent_->IsDrawn() : parent_drawn_; +} + +std::unique_ptr<WindowSurface> Window::RequestSurface(mojom::SurfaceType type) { + std::unique_ptr<WindowSurfaceBinding> surface_binding; + std::unique_ptr<WindowSurface> surface = + WindowSurface::Create(&surface_binding); + AttachSurface(type, std::move(surface_binding)); + return surface; +} + +void Window::AttachSurface( + mojom::SurfaceType type, + std::unique_ptr<WindowSurfaceBinding> surface_binding) { + window_tree()->AttachSurface( + server_id_, type, std::move(surface_binding->surface_request_), + mojo::MakeProxy(std::move(surface_binding->surface_client_))); +} + +void Window::ClearSharedProperty(const std::string& name) { + SetSharedPropertyInternal(name, nullptr); +} + +bool Window::HasSharedProperty(const std::string& name) const { + return properties_.count(name) > 0; +} + +void Window::AddObserver(WindowObserver* observer) { + observers_.AddObserver(observer); +} + +void Window::RemoveObserver(WindowObserver* observer) { + observers_.RemoveObserver(observer); +} + +const Window* Window::GetRoot() const { + const Window* root = this; + for (const Window* parent = this; parent; parent = parent->parent()) + root = parent; + return root; +} + +void Window::AddChild(Window* child) { + // TODO(beng): not necessarily valid to all clients, but possibly to the + // embeddee in an embedder-embeddee relationship. + if (client_) + CHECK_EQ(child->client_, client_); + // Roots can not be added as children of other windows. + if (window_tree() && window_tree()->IsRoot(child)) + return; + LocalAddChild(child); + if (client_) + client_->AddChild(this, child->server_id()); +} + +void Window::RemoveChild(Window* child) { + // TODO(beng): not necessarily valid to all clients, but possibly to the + // embeddee in an embedder-embeddee relationship. + if (client_) + CHECK_EQ(child->client_, client_); + LocalRemoveChild(child); + if (client_) + client_->RemoveChild(this, child->server_id()); +} + +void Window::Reorder(Window* relative, mojom::OrderDirection direction) { + if (!LocalReorder(relative, direction)) + return; + if (client_) + client_->Reorder(this, relative->server_id(), direction); +} + +void Window::MoveToFront() { + if (!parent_ || parent_->children_.back() == this) + return; + Reorder(parent_->children_.back(), mojom::OrderDirection::ABOVE); +} + +void Window::MoveToBack() { + if (!parent_ || parent_->children_.front() == this) + return; + Reorder(parent_->children_.front(), mojom::OrderDirection::BELOW); +} + +bool Window::Contains(const Window* child) const { + if (!child) + return false; + if (child == this) + return true; + if (client_) + CHECK_EQ(child->client_, client_); + for (const Window* p = child->parent(); p; p = p->parent()) { + if (p == this) + return true; + } + return false; +} + +void Window::AddTransientWindow(Window* transient_window) { + // A system modal window cannot become a transient child. + DCHECK(!transient_window->is_modal() || transient_window->transient_parent()); + + if (client_) + CHECK_EQ(transient_window->client_, client_); + LocalAddTransientWindow(transient_window); + if (client_) + client_->AddTransientWindow(this, transient_window->server_id()); +} + +void Window::RemoveTransientWindow(Window* transient_window) { + if (client_) + CHECK_EQ(transient_window->window_tree(), client_); + LocalRemoveTransientWindow(transient_window); + if (client_) + client_->RemoveTransientWindowFromParent(transient_window); +} + +void Window::SetModal() { + if (is_modal_) + return; + + LocalSetModal(); + if (client_) + client_->SetModal(this); +} + +Window* Window::GetChildByLocalId(int id) { + if (id == local_id_) + return this; + // TODO(beng): this could be improved depending on how we decide to own + // windows. + for (Window* child : children_) { + Window* matching_child = child->GetChildByLocalId(id); + if (matching_child) + return matching_child; + } + return nullptr; +} + +void Window::SetTextInputState(mojo::TextInputStatePtr state) { + if (client_) + client_->SetWindowTextInputState(server_id_, std::move(state)); +} + +void Window::SetImeVisibility(bool visible, mojo::TextInputStatePtr state) { + // SetImeVisibility() shouldn't be used if the window is not editable. + DCHECK(state.is_null() || state->type != mojo::TextInputType::NONE); + if (client_) + client_->SetImeVisibility(server_id_, visible, std::move(state)); +} + +bool Window::HasCapture() const { + return client_ && client_->GetCaptureWindow() == this; +} + +void Window::SetCapture() { + if (client_) + client_->SetCapture(this); +} + +void Window::ReleaseCapture() { + if (client_) + client_->ReleaseCapture(this); +} + +void Window::SetFocus() { + if (client_ && IsDrawn()) + client_->SetFocus(this); +} + +bool Window::HasFocus() const { + return client_ && client_->GetFocusedWindow() == this; +} + +void Window::SetCanFocus(bool can_focus) { + if (client_) + client_->SetCanFocus(server_id_, can_focus); +} + +void Window::Embed(mus::mojom::WindowTreeClientPtr client, uint32_t flags) { + Embed(std::move(client), base::Bind(&EmptyEmbedCallback), flags); +} + +void Window::Embed(mus::mojom::WindowTreeClientPtr client, + const EmbedCallback& callback, + uint32_t flags) { + if (PrepareForEmbed()) + client_->Embed(server_id_, std::move(client), flags, callback); + else + callback.Run(false); +} + +void Window::RequestClose() { + if (client_) + client_->RequestClose(this); +} + +std::string Window::GetName() const { + if (HasSharedProperty(mojom::WindowManager::kName_Property)) + return GetSharedProperty<std::string>(mojom::WindowManager::kName_Property); + + return std::string(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, protected: + +Window::Window() : Window(nullptr, static_cast<Id>(-1)) {} + +Window::~Window() { + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDestroying(this)); + if (client_) + client_->OnWindowDestroying(this); + + if (HasFocus()) { + // The focused window is being removed. When this happens the server + // advances focus. We don't want to randomly pick a Window to get focus, so + // we update local state only, and wait for the next focus change from the + // server. + client_->LocalSetFocus(nullptr); + } + + // Remove from transient parent. + if (transient_parent_) + transient_parent_->LocalRemoveTransientWindow(this); + + // Remove transient children. + while (!transient_children_.empty()) { + Window* transient_child = transient_children_.front(); + LocalRemoveTransientWindow(transient_child); + transient_child->LocalDestroy(); + DCHECK(transient_children_.empty() || + transient_children_.front() != transient_child); + } + + if (parent_) + parent_->LocalRemoveChild(this); + + // We may still have children. This can happen if the embedder destroys the + // root while we're still alive. + while (!children_.empty()) { + Window* child = children_.front(); + LocalRemoveChild(child); + DCHECK(children_.empty() || children_.front() != child); + } + + // Clear properties. + for (auto& pair : prop_map_) { + if (pair.second.deallocator) + (*pair.second.deallocator)(pair.second.value); + } + prop_map_.clear(); + + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDestroyed(this)); + + // Invoke after observers so that can clean up any internal state observers + // may have changed. + if (window_tree()) + window_tree()->OnWindowDestroyed(this); +} + +//////////////////////////////////////////////////////////////////////////////// +// Window, private: + +Window::Window(WindowTreeClient* client, Id id) + : client_(client), + server_id_(id), + parent_(nullptr), + stacking_target_(nullptr), + transient_parent_(nullptr), + is_modal_(false), + // Matches aura, see aura::Window for details. + observers_(base::ObserverList<WindowObserver>::NOTIFY_EXISTING_ONLY), + input_event_handler_(nullptr), + visible_(false), + opacity_(1.0f), + display_id_(display::Display::kInvalidDisplayID), + cursor_id_(mojom::Cursor::CURSOR_NULL), + parent_drawn_(false) {} + +void Window::SetSharedPropertyInternal(const std::string& name, + const std::vector<uint8_t>* value) { + if (!OwnsWindowOrIsRoot(this)) + return; + + if (client_) { + mojo::Array<uint8_t> transport_value(nullptr); + if (value) { + transport_value.resize(value->size()); + if (value->size()) + memcpy(&transport_value.front(), &(value->front()), value->size()); + } + // TODO: add test coverage of this (450303). + client_->SetProperty(this, name, std::move(transport_value)); + } + LocalSetSharedProperty(name, value); +} + +int64_t Window::SetLocalPropertyInternal(const void* key, + const char* name, + PropertyDeallocator deallocator, + int64_t value, + int64_t default_value) { + int64_t old = GetLocalPropertyInternal(key, default_value); + if (value == default_value) { + prop_map_.erase(key); + } else { + Value prop_value; + prop_value.name = name; + prop_value.value = value; + prop_value.deallocator = deallocator; + prop_map_[key] = prop_value; + } + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowLocalPropertyChanged(this, key, old)); + return old; +} + +int64_t Window::GetLocalPropertyInternal(const void* key, + int64_t default_value) const { + std::map<const void*, Value>::const_iterator iter = prop_map_.find(key); + if (iter == prop_map_.end()) + return default_value; + return iter->second.value; +} + +void Window::LocalDestroy() { + delete this; +} + +void Window::LocalAddChild(Window* child) { + ScopedTreeNotifier notifier(child, child->parent(), this); + if (child->parent()) + RemoveChildImpl(child, &child->parent_->children_); + children_.push_back(child); + child->parent_ = this; + child->display_id_ = display_id_; +} + +void Window::LocalRemoveChild(Window* child) { + DCHECK_EQ(this, child->parent()); + ScopedTreeNotifier notifier(child, this, nullptr); + RemoveChildImpl(child, &children_); +} + +void Window::LocalAddTransientWindow(Window* transient_window) { + if (transient_window->transient_parent()) + RemoveTransientWindowImpl(transient_window); + transient_children_.push_back(transient_window); + transient_window->transient_parent_ = this; + + // Restack |transient_window| properly above its transient parent, if they + // share the same parent. + if (transient_window->parent() == parent()) + RestackTransientDescendants(this, &GetStackingTarget, + &ReorderWithoutNotification); + + // TODO(fsamuel): We might want a notification here. +} + +void Window::LocalRemoveTransientWindow(Window* transient_window) { + DCHECK_EQ(this, transient_window->transient_parent()); + RemoveTransientWindowImpl(transient_window); + // TODO(fsamuel): We might want a notification here. +} + +void Window::LocalSetModal() { + is_modal_ = true; +} + +bool Window::LocalReorder(Window* relative, mojom::OrderDirection direction) { + OrderChangedNotifier notifier(this, relative, direction); + return ReorderImpl(this, relative, direction, ¬ifier); +} + +void Window::LocalSetBounds(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + // If this client owns the window, then it should be the only one to change + // the bounds. + DCHECK(!OwnsWindow(client_, this) || old_bounds == bounds_); + ScopedSetBoundsNotifier notifier(this, old_bounds, new_bounds); + bounds_ = new_bounds; +} + +void Window::LocalSetClientArea( + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& additional_client_areas) { + const std::vector<gfx::Rect> old_additional_client_areas = + additional_client_areas_; + const gfx::Insets old_client_area = client_area_; + client_area_ = new_client_area; + additional_client_areas_ = additional_client_areas; + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowClientAreaChanged(this, old_client_area, + old_additional_client_areas)); +} + +void Window::LocalSetDisplay(int64_t display_id) { + display_id_ = display_id; + // TODO(sad): Notify observers (of this window, and of the descendant windows) + // when a window moves from one display into another. https://crbug.com/614887 +} + +void Window::LocalSetParentDrawn(bool value) { + if (parent_drawn_ == value) + return; + + // As IsDrawn() is derived from |visible_| and |parent_drawn_|, only send + // drawn notification is the value of IsDrawn() is really changing. + if (IsDrawn() == value) { + parent_drawn_ = value; + return; + } + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDrawnChanging(this)); + parent_drawn_ = value; + FOR_EACH_OBSERVER(WindowObserver, observers_, OnWindowDrawnChanged(this)); +} + +void Window::LocalSetVisible(bool visible) { + if (visible_ == visible) + return; + + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowVisibilityChanging(this)); + visible_ = visible; + NotifyWindowVisibilityChanged(this); +} + +void Window::LocalSetOpacity(float opacity) { + if (opacity_ == opacity) + return; + + float old_opacity = opacity_; + opacity_ = opacity; + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowOpacityChanged(this, old_opacity, opacity_)); +} + +void Window::LocalSetPredefinedCursor(mojom::Cursor cursor_id) { + if (cursor_id_ == cursor_id) + return; + + cursor_id_ = cursor_id; + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowPredefinedCursorChanged(this, cursor_id)); +} + +void Window::LocalSetSharedProperty(const std::string& name, + const std::vector<uint8_t>* value) { + std::vector<uint8_t> old_value; + std::vector<uint8_t>* old_value_ptr = nullptr; + auto it = properties_.find(name); + if (it != properties_.end()) { + old_value = it->second; + old_value_ptr = &old_value; + + if (value && old_value == *value) + return; + } else if (!value) { + // This property isn't set in |properties_| and |value| is nullptr, so + // there's no change. + return; + } + + if (value) { + properties_[name] = *value; + } else if (it != properties_.end()) { + properties_.erase(it); + } + + FOR_EACH_OBSERVER( + WindowObserver, observers_, + OnWindowSharedPropertyChanged(this, name, old_value_ptr, value)); +} + +void Window::NotifyWindowStackingChanged() { + if (stacking_target_) { + Children::const_iterator window_i = std::find( + parent()->children().begin(), parent()->children().end(), this); + DCHECK(window_i != parent()->children().end()); + if (window_i != parent()->children().begin() && + (*(window_i - 1) == stacking_target_)) + return; + } + RestackTransientDescendants(this, &GetStackingTarget, + &ReorderWithoutNotification); +} + +void Window::NotifyWindowVisibilityChanged(Window* target) { + if (!NotifyWindowVisibilityChangedDown(target)) { + return; // |this| has been deleted. + } + NotifyWindowVisibilityChangedUp(target); +} + +bool Window::NotifyWindowVisibilityChangedAtReceiver(Window* target) { + // |this| may be deleted during a call to OnWindowVisibilityChanged() on one + // of the observers. We create an local observer for that. In that case we + // exit without further access to any members. + WindowTracker tracker; + tracker.Add(this); + FOR_EACH_OBSERVER(WindowObserver, observers_, + OnWindowVisibilityChanged(target)); + return tracker.Contains(this); +} + +bool Window::NotifyWindowVisibilityChangedDown(Window* target) { + if (!NotifyWindowVisibilityChangedAtReceiver(target)) + return false; // |this| was deleted. + std::set<const Window*> child_already_processed; + bool child_destroyed = false; + do { + child_destroyed = false; + for (Window::Children::const_iterator it = children_.begin(); + it != children_.end(); ++it) { + if (!child_already_processed.insert(*it).second) + continue; + if (!(*it)->NotifyWindowVisibilityChangedDown(target)) { + // |*it| was deleted, |it| is invalid and |children_| has changed. We + // exit the current for-loop and enter a new one. + child_destroyed = true; + break; + } + } + } while (child_destroyed); + return true; +} + +void Window::NotifyWindowVisibilityChangedUp(Window* target) { + // Start with the parent as we already notified |this| + // in NotifyWindowVisibilityChangedDown. + for (Window* window = parent(); window; window = window->parent()) { + bool ret = window->NotifyWindowVisibilityChangedAtReceiver(target); + DCHECK(ret); + } +} + +bool Window::PrepareForEmbed() { + if (!OwnsWindow(client_, this)) + return false; + + while (!children_.empty()) + RemoveChild(children_[0]); + return true; +} + +void Window::RemoveTransientWindowImpl(Window* transient_window) { + Window::Children::iterator it = std::find( + transient_children_.begin(), transient_children_.end(), transient_window); + if (it != transient_children_.end()) { + transient_children_.erase(it); + transient_window->transient_parent_ = nullptr; + } + // If |transient_window| and its former transient parent share the same + // parent, |transient_window| should be restacked properly so it is not among + // transient children of its former parent, anymore. + if (parent() == transient_window->parent()) + RestackTransientDescendants(this, &GetStackingTarget, + &ReorderWithoutNotification); + + // TOOD(fsamuel): We might want to notify observers here. +} + +// static +void Window::ReorderWithoutNotification(Window* window, + Window* relative, + mojom::OrderDirection direction) { + ReorderImpl(window, relative, direction, nullptr); +} + +// static +// Returns true if the order actually changed. +bool Window::ReorderImpl(Window* window, + Window* relative, + mojom::OrderDirection direction, + OrderChangedNotifier* notifier) { + DCHECK(relative); + DCHECK_NE(window, relative); + DCHECK_EQ(window->parent(), relative->parent()); + DCHECK(window->parent()); + + if (!AdjustStackingForTransientWindows(&window, &relative, &direction, + window->stacking_target_)) + return false; + + const size_t child_i = std::find(window->parent_->children_.begin(), + window->parent_->children_.end(), window) - + window->parent_->children_.begin(); + const size_t target_i = + std::find(window->parent_->children_.begin(), + window->parent_->children_.end(), relative) - + window->parent_->children_.begin(); + if ((direction == mojom::OrderDirection::ABOVE && child_i == target_i + 1) || + (direction == mojom::OrderDirection::BELOW && child_i + 1 == target_i)) { + return false; + } + + if (notifier) + notifier->NotifyWindowReordering(); + + const size_t dest_i = direction == mojom::OrderDirection::ABOVE + ? (child_i < target_i ? target_i : target_i + 1) + : (child_i < target_i ? target_i - 1 : target_i); + window->parent_->children_.erase(window->parent_->children_.begin() + + child_i); + window->parent_->children_.insert(window->parent_->children_.begin() + dest_i, + window); + + window->NotifyWindowStackingChanged(); + + if (notifier) + notifier->NotifyWindowReordered(); + + return true; +} + +// static +Window** Window::GetStackingTarget(Window* window) { + return &window->stacking_target_; +} +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_observer.cc b/chromium/components/mus/public/cpp/lib/window_observer.cc new file mode 100644 index 00000000000..26e65078f77 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_observer.cc @@ -0,0 +1,18 @@ +// 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 "components/mus/public/cpp/window_observer.h" + +namespace mus { + +//////////////////////////////////////////////////////////////////////////////// +// WindowObserver, public: + +WindowObserver::TreeChangeParams::TreeChangeParams() + : target(nullptr), + old_parent(nullptr), + new_parent(nullptr), + receiver(nullptr) {} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_private.cc b/chromium/components/mus/public/cpp/lib/window_private.cc new file mode 100644 index 00000000000..11c7ebeefae --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_private.cc @@ -0,0 +1,31 @@ +// 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 "components/mus/public/cpp/lib/window_private.h" + +namespace mus { + +WindowPrivate::WindowPrivate(Window* window) : window_(window) { + CHECK(window); +} + +WindowPrivate::~WindowPrivate() {} + +// static +Window* WindowPrivate::LocalCreate() { + return new Window; +} + +void WindowPrivate::LocalSetSharedProperty(const std::string& name, + mojo::Array<uint8_t> new_data) { + std::vector<uint8_t> data; + std::vector<uint8_t>* data_ptr = nullptr; + if (!new_data.is_null()) { + data = new_data.To<std::vector<uint8_t>>(); + data_ptr = &data; + } + LocalSetSharedProperty(name, data_ptr); +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_private.h b/chromium/components/mus/public/cpp/lib/window_private.h new file mode 100644 index 00000000000..4b2640e8b37 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_private.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 COMPONENTS_MUS_PUBLIC_CPP_LIB_WINDOW_PRIVATE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_LIB_WINDOW_PRIVATE_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/macros.h" +#include "components/mus/public/cpp/window.h" +#include "mojo/public/cpp/bindings/array.h" + +namespace mus { + +// This class is a friend of a Window and contains functions to mutate internal +// state of Window. +class WindowPrivate { + public: + explicit WindowPrivate(Window* window); + ~WindowPrivate(); + + // Creates and returns a new Window. Caller owns the return value. + static Window* LocalCreate(); + + base::ObserverList<WindowObserver>* observers() { + return &window_->observers_; + } + + void ClearParent() { window_->parent_ = nullptr; } + + void ClearTransientParent() { window_->transient_parent_ = nullptr; } + + void set_visible(bool visible) { window_->visible_ = visible; } + + void set_parent_drawn(bool drawn) { window_->parent_drawn_ = drawn; } + bool parent_drawn() { return window_->parent_drawn_; } + + void set_server_id(Id id) { window_->server_id_ = id; } + Id server_id() { return window_->server_id_; } + + void set_client(WindowTreeClient* client) { + window_->client_ = client; + } + + void set_properties(const std::map<std::string, std::vector<uint8_t>>& data) { + window_->properties_ = data; + } + + void LocalSetDisplay(int64_t new_display) { + window_->LocalSetDisplay(new_display); + } + + void LocalDestroy() { window_->LocalDestroy(); } + void LocalAddChild(Window* child) { window_->LocalAddChild(child); } + void LocalRemoveChild(Window* child) { window_->LocalRemoveChild(child); } + void LocalAddTransientWindow(Window* child) { + window_->LocalAddTransientWindow(child); + } + void LocalRemoveTransientWindow(Window* child) { + window_->LocalRemoveTransientWindow(child); + } + void LocalUnsetModal() { window_->is_modal_ = false; } + void LocalReorder(Window* relative, mojom::OrderDirection direction) { + window_->LocalReorder(relative, direction); + } + void LocalSetBounds(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + window_->LocalSetBounds(old_bounds, new_bounds); + } + void LocalSetClientArea( + const gfx::Insets& client_area, + const std::vector<gfx::Rect>& additional_client_areas) { + window_->LocalSetClientArea(client_area, additional_client_areas); + } + void LocalSetParentDrawn(bool drawn) { window_->LocalSetParentDrawn(drawn); } + void LocalSetVisible(bool visible) { window_->LocalSetVisible(visible); } + void LocalSetOpacity(float opacity) { window_->LocalSetOpacity(opacity); } + void LocalSetPredefinedCursor(mojom::Cursor cursor) { + window_->LocalSetPredefinedCursor(cursor); + } + void LocalSetSharedProperty(const std::string& name, + mojo::Array<uint8_t> new_data); + void LocalSetSharedProperty(const std::string& name, + const std::vector<uint8_t>* data) { + window_->LocalSetSharedProperty(name, data); + } + void NotifyWindowStackingChanged() { window_->NotifyWindowStackingChanged(); } + + private: + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(WindowPrivate); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_LIB_WINDOW_PRIVATE_H_ diff --git a/chromium/components/mus/public/cpp/lib/window_surface.cc b/chromium/components/mus/public/cpp/lib/window_surface.cc new file mode 100644 index 00000000000..1f0b840a102 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_surface.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 "components/mus/public/cpp/window_surface.h" + +#include "base/memory/ptr_util.h" +#include "components/mus/public/cpp/window_surface_client.h" + +namespace mus { + +// static +std::unique_ptr<WindowSurface> WindowSurface::Create( + std::unique_ptr<WindowSurfaceBinding>* surface_binding) { + mojom::SurfacePtr surface; + mojom::SurfaceClientPtr surface_client; + mojo::InterfaceRequest<mojom::SurfaceClient> surface_client_request = + GetProxy(&surface_client); + + surface_binding->reset(new WindowSurfaceBinding( + GetProxy(&surface), surface_client.PassInterface())); + return base::WrapUnique(new WindowSurface(surface.PassInterface(), + std::move(surface_client_request))); +} + +WindowSurface::~WindowSurface() {} + +void WindowSurface::BindToThread() { + DCHECK(!thread_checker_); + thread_checker_.reset(new base::ThreadChecker()); + surface_.Bind(std::move(surface_info_)); + client_binding_.reset(new mojo::Binding<mojom::SurfaceClient>( + this, std::move(client_request_))); +} + +void WindowSurface::SubmitCompositorFrame(cc::CompositorFrame frame, + const base::Closure& callback) { + DCHECK(thread_checker_); + DCHECK(thread_checker_->CalledOnValidThread()); + if (!surface_) + return; + surface_->SubmitCompositorFrame(std::move(frame), callback); +} + +WindowSurface::WindowSurface( + mojo::InterfacePtrInfo<mojom::Surface> surface_info, + mojo::InterfaceRequest<mojom::SurfaceClient> client_request) + : client_(nullptr), + surface_info_(std::move(surface_info)), + client_request_(std::move(client_request)) {} + +void WindowSurface::ReturnResources( + mojo::Array<cc::ReturnedResource> resources) { + DCHECK(thread_checker_); + DCHECK(thread_checker_->CalledOnValidThread()); + if (!client_) + return; + client_->OnResourcesReturned(this, std::move(resources)); +} + +WindowSurfaceBinding::~WindowSurfaceBinding() {} + +WindowSurfaceBinding::WindowSurfaceBinding( + mojo::InterfaceRequest<mojom::Surface> surface_request, + mojo::InterfacePtrInfo<mojom::SurfaceClient> surface_client) + : surface_request_(std::move(surface_request)), + surface_client_(std::move(surface_client)) {} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_tree_client.cc b/chromium/components/mus/public/cpp/lib/window_tree_client.cc new file mode 100644 index 00000000000..7a43101ff6a --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_tree_client.cc @@ -0,0 +1,1181 @@ +// 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 "components/mus/public/cpp/window_tree_client.h" + +#include <stddef.h> + +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "components/mus/common/util.h" +#include "components/mus/public/cpp/input_event_handler.h" +#include "components/mus/public/cpp/lib/in_flight_change.h" +#include "components/mus/public/cpp/lib/window_private.h" +#include "components/mus/public/cpp/window_manager_delegate.h" +#include "components/mus/public/cpp/window_observer.h" +#include "components/mus/public/cpp/window_tracker.h" +#include "components/mus/public/cpp/window_tree_client_delegate.h" +#include "components/mus/public/cpp/window_tree_client_observer.h" +#include "components/mus/public/interfaces/window_manager_window_tree_factory.mojom.h" +#include "services/shell/public/cpp/connector.h" +#include "ui/display/mojo/display_type_converters.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/size.h" + +namespace mus { + +void DeleteWindowTreeClient(WindowTreeClient* client) { delete client; } + +Id MakeTransportId(ClientSpecificId client_id, ClientSpecificId local_id) { + return (client_id << 16) | local_id; +} + +Id server_id(Window* window) { + return WindowPrivate(window).server_id(); +} + +// Helper called to construct a local window object from transport data. +Window* AddWindowToClient(WindowTreeClient* client, + Window* parent, + const mojom::WindowDataPtr& window_data) { + // We don't use the ctor that takes a WindowTreeClient here, since it will + // call back to the service and attempt to create a new window. + Window* window = WindowPrivate::LocalCreate(); + WindowPrivate private_window(window); + private_window.set_client(client); + private_window.set_server_id(window_data->window_id); + private_window.set_visible(window_data->visible); + private_window.set_properties( + window_data->properties + .To<std::map<std::string, std::vector<uint8_t>>>()); + client->AddWindow(window); + private_window.LocalSetBounds(gfx::Rect(), window_data->bounds); + if (parent) + WindowPrivate(parent).LocalAddChild(window); + return window; +} + +Window* BuildWindowTree(WindowTreeClient* client, + const mojo::Array<mojom::WindowDataPtr>& windows, + Window* initial_parent) { + std::vector<Window*> parents; + Window* root = NULL; + Window* last_window = NULL; + if (initial_parent) + parents.push_back(initial_parent); + for (size_t i = 0; i < windows.size(); ++i) { + if (last_window && windows[i]->parent_id == server_id(last_window)) { + parents.push_back(last_window); + } else if (!parents.empty()) { + while (server_id(parents.back()) != windows[i]->parent_id) + parents.pop_back(); + } + Window* window = AddWindowToClient( + client, !parents.empty() ? parents.back() : NULL, windows[i]); + if (!last_window) + root = window; + last_window = window; + } + return root; +} + +WindowTreeClient::WindowTreeClient( + WindowTreeClientDelegate* delegate, + WindowManagerDelegate* window_manager_delegate, + mojo::InterfaceRequest<mojom::WindowTreeClient> request) + : client_id_(0), + next_window_id_(1), + next_change_id_(1), + delegate_(delegate), + window_manager_delegate_(window_manager_delegate), + capture_window_(nullptr), + focused_window_(nullptr), + binding_(this), + tree_(nullptr), + delete_on_no_roots_(!window_manager_delegate), + in_destructor_(false), + weak_factory_(this) { + // Allow for a null request in tests. + if (request.is_pending()) + binding_.Bind(std::move(request)); + if (window_manager_delegate) + window_manager_delegate->SetWindowManagerClient(this); +} + +WindowTreeClient::~WindowTreeClient() { + in_destructor_ = true; + + std::vector<Window*> non_owned; + WindowTracker tracker; + while (!windows_.empty()) { + IdToWindowMap::iterator it = windows_.begin(); + if (OwnsWindow(it->second)) { + it->second->Destroy(); + } else { + tracker.Add(it->second); + windows_.erase(it); + } + } + + // Delete the non-owned windows last. In the typical case these are roots. The + // exception is the window manager and embed roots, which may know about + // other random windows that it doesn't own. + // NOTE: we manually delete as we're a friend. + while (!tracker.windows().empty()) + delete tracker.windows().front(); + + FOR_EACH_OBSERVER(WindowTreeClientObserver, observers_, + OnWillDestroyClient(this)); + + delegate_->OnWindowTreeClientDestroyed(this); +} + +void WindowTreeClient::ConnectViaWindowTreeFactory( + shell::Connector* connector) { + // Clients created with no root shouldn't delete automatically. + delete_on_no_roots_ = false; + + // The client id doesn't really matter, we use 101 purely for debugging. + client_id_ = 101; + + mojom::WindowTreeFactoryPtr factory; + connector->ConnectToInterface("mojo:mus", &factory); + mojom::WindowTreePtr window_tree; + factory->CreateWindowTree(GetProxy(&window_tree), + binding_.CreateInterfacePtrAndBind()); + SetWindowTree(std::move(window_tree)); +} + +void WindowTreeClient::ConnectAsWindowManager(shell::Connector* connector) { + DCHECK(window_manager_delegate_); + + mojom::WindowManagerWindowTreeFactoryPtr factory; + connector->ConnectToInterface("mojo:mus", &factory); + mojom::WindowTreePtr window_tree; + factory->CreateWindowTree(GetProxy(&window_tree), + binding_.CreateInterfacePtrAndBind()); + SetWindowTree(std::move(window_tree)); +} + +void WindowTreeClient::WaitForEmbed() { + DCHECK(roots_.empty()); + // OnEmbed() is the first function called. + binding_.WaitForIncomingMethodCall(); + // TODO(sky): deal with pipe being closed before we get OnEmbed(). +} + +void WindowTreeClient::DestroyWindow(Window* window) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange(base::WrapUnique( + new CrashInFlightChange(window, ChangeType::DELETE_WINDOW))); + tree_->DeleteWindow(change_id, server_id(window)); +} + +void WindowTreeClient::AddChild(Window* parent, Id child_id) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new CrashInFlightChange(parent, ChangeType::ADD_CHILD))); + tree_->AddWindow(change_id, parent->server_id(), child_id); +} + +void WindowTreeClient::RemoveChild(Window* parent, Id child_id) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange(base::WrapUnique( + new CrashInFlightChange(parent, ChangeType::REMOVE_CHILD))); + tree_->RemoveWindowFromParent(change_id, child_id); +} + +void WindowTreeClient::AddTransientWindow(Window* window, + Id transient_window_id) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange(base::WrapUnique( + new CrashInFlightChange(window, ChangeType::ADD_TRANSIENT_WINDOW))); + tree_->AddTransientWindow(change_id, server_id(window), transient_window_id); +} + +void WindowTreeClient::RemoveTransientWindowFromParent(Window* window) { + DCHECK(tree_); + const uint32_t change_id = + ScheduleInFlightChange(base::WrapUnique(new CrashInFlightChange( + window, ChangeType::REMOVE_TRANSIENT_WINDOW_FROM_PARENT))); + tree_->RemoveTransientWindowFromParent(change_id, server_id(window)); +} + +void WindowTreeClient::SetModal(Window* window) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightSetModalChange(window))); + tree_->SetModal(change_id, server_id(window)); +} + +void WindowTreeClient::Reorder(Window* window, + Id relative_window_id, + mojom::OrderDirection direction) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new CrashInFlightChange(window, ChangeType::REORDER))); + tree_->ReorderWindow(change_id, server_id(window), relative_window_id, + direction); +} + +bool WindowTreeClient::OwnsWindow(Window* window) const { + // Windows created via CreateTopLevelWindow() are not owned by us, but have + // our client id. + return HiWord(server_id(window)) == client_id_ && + roots_.count(window) == 0; +} + +void WindowTreeClient::SetBounds(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& bounds) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightBoundsChange(window, old_bounds))); + tree_->SetWindowBounds(change_id, server_id(window), bounds); +} + +void WindowTreeClient::SetCapture(Window* window) { + // In order for us to get here we had to have exposed a window, which implies + // we got a client. + DCHECK(tree_); + if (capture_window_ == window) + return; + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightCaptureChange(this, capture_window_))); + tree_->SetCapture(change_id, server_id(window)); + LocalSetCapture(window); +} + +void WindowTreeClient::ReleaseCapture(Window* window) { + // In order for us to get here we had to have exposed a window, which implies + // we got a client. + DCHECK(tree_); + if (capture_window_ != window) + return; + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightCaptureChange(this, window))); + tree_->ReleaseCapture(change_id, server_id(window)); + LocalSetCapture(nullptr); +} + +void WindowTreeClient::SetClientArea( + Id window_id, + const gfx::Insets& client_area, + const std::vector<gfx::Rect>& additional_client_areas) { + DCHECK(tree_); + tree_->SetClientArea(window_id, client_area, additional_client_areas); +} + +void WindowTreeClient::SetHitTestMask(Id window_id, const gfx::Rect& mask) { + DCHECK(tree_); + tree_->SetHitTestMask(window_id, mask); +} + +void WindowTreeClient::ClearHitTestMask(Id window_id) { + DCHECK(tree_); + tree_->SetHitTestMask(window_id, {}); +} + +void WindowTreeClient::SetFocus(Window* window) { + // In order for us to get here we had to have exposed a window, which implies + // we got a client. + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightFocusChange(this, focused_window_))); + tree_->SetFocus(change_id, window ? server_id(window) : 0); + LocalSetFocus(window); +} + +void WindowTreeClient::SetCanFocus(Id window_id, bool can_focus) { + DCHECK(tree_); + tree_->SetCanFocus(window_id, can_focus); +} + +void WindowTreeClient::SetPredefinedCursor(Id window_id, + mus::mojom::Cursor cursor_id) { + DCHECK(tree_); + + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + // We make an inflight change thing here. + const uint32_t change_id = ScheduleInFlightChange(base::WrapUnique( + new InFlightPredefinedCursorChange(window, window->predefined_cursor()))); + tree_->SetPredefinedCursor(change_id, window_id, cursor_id); +} + +void WindowTreeClient::SetVisible(Window* window, bool visible) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightVisibleChange(window, !visible))); + tree_->SetWindowVisibility(change_id, server_id(window), visible); +} + +void WindowTreeClient::SetOpacity(Window* window, float opacity) { + DCHECK(tree_); + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightOpacityChange(window, window->opacity()))); + tree_->SetWindowOpacity(change_id, server_id(window), opacity); +} + +void WindowTreeClient::SetProperty(Window* window, + const std::string& name, + mojo::Array<uint8_t> data) { + DCHECK(tree_); + + mojo::Array<uint8_t> old_value(nullptr); + if (window->HasSharedProperty(name)) + old_value = mojo::Array<uint8_t>::From(window->properties_[name]); + + const uint32_t change_id = ScheduleInFlightChange( + base::WrapUnique(new InFlightPropertyChange(window, name, old_value))); + tree_->SetWindowProperty(change_id, server_id(window), mojo::String(name), + std::move(data)); +} + +void WindowTreeClient::SetWindowTextInputState( + Id window_id, + mojo::TextInputStatePtr state) { + DCHECK(tree_); + tree_->SetWindowTextInputState(window_id, std::move(state)); +} + +void WindowTreeClient::SetImeVisibility(Id window_id, + bool visible, + mojo::TextInputStatePtr state) { + DCHECK(tree_); + tree_->SetImeVisibility(window_id, visible, std::move(state)); +} + +void WindowTreeClient::Embed(Id window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + const mojom::WindowTree::EmbedCallback& callback) { + DCHECK(tree_); + tree_->Embed(window_id, std::move(client), flags, callback); +} + +void WindowTreeClient::RequestClose(Window* window) { + if (window_manager_internal_client_) + window_manager_internal_client_->WmRequestClose(server_id(window)); +} + +void WindowTreeClient::AttachSurface( + Id window_id, + mojom::SurfaceType type, + mojo::InterfaceRequest<mojom::Surface> surface, + mojom::SurfaceClientPtr client) { + DCHECK(tree_); + tree_->AttachSurface(window_id, type, std::move(surface), std::move(client)); +} + +void WindowTreeClient::LocalSetCapture(Window* window) { + if (capture_window_ == window) + return; + Window* lost_capture = capture_window_; + capture_window_ = window; + if (lost_capture) { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(lost_capture).observers(), + OnWindowLostCapture(lost_capture)); + } +} + +void WindowTreeClient::LocalSetFocus(Window* focused) { + Window* blurred = focused_window_; + // Update |focused_window_| before calling any of the observers, so that the + // observers get the correct result from calling |Window::HasFocus()|, + // |WindowTreeClient::GetFocusedWindow()| etc. + focused_window_ = focused; + if (blurred) { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(blurred).observers(), + OnWindowFocusChanged(focused, blurred)); + } + if (focused) { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(focused).observers(), + OnWindowFocusChanged(focused, blurred)); + } + FOR_EACH_OBSERVER(WindowTreeClientObserver, observers_, + OnWindowTreeFocusChanged(focused, blurred)); +} + +void WindowTreeClient::AddWindow(Window* window) { + DCHECK(windows_.find(server_id(window)) == windows_.end()); + windows_[server_id(window)] = window; +} + +void WindowTreeClient::OnWindowDestroying(Window* window) { + if (window == capture_window_) { + // Normally the queue updates itself upon window destruction. However since + // |window| is being destroyed, it will not be possible to notify its + // observers of the lost capture. Update local state now. + LocalSetCapture(nullptr); + } + // For |focused_window_| window destruction clears the entire focus state. +} + +void WindowTreeClient::OnWindowDestroyed(Window* window) { + windows_.erase(server_id(window)); + + for (auto& entry : embedded_windows_) { + auto it = entry.second.find(window); + if (it != entry.second.end()) { + entry.second.erase(it); + break; + } + } + + // Remove any InFlightChanges associated with the window. + std::set<uint32_t> in_flight_change_ids_to_remove; + for (const auto& pair : in_flight_map_) { + if (pair.second->window() == window) + in_flight_change_ids_to_remove.insert(pair.first); + } + for (auto change_id : in_flight_change_ids_to_remove) + in_flight_map_.erase(change_id); + + if (roots_.erase(window) > 0 && roots_.empty() && delete_on_no_roots_ && + !in_destructor_) { + delete this; + } +} + +Window* WindowTreeClient::GetWindowByServerId(Id id) { + IdToWindowMap::const_iterator it = windows_.find(id); + return it != windows_.end() ? it->second : NULL; +} + +InFlightChange* WindowTreeClient::GetOldestInFlightChangeMatching( + const InFlightChange& change) { + for (const auto& pair : in_flight_map_) { + if (pair.second->window() == change.window() && + pair.second->change_type() == change.change_type() && + pair.second->Matches(change)) { + return pair.second.get(); + } + } + return nullptr; +} + +uint32_t WindowTreeClient::ScheduleInFlightChange( + std::unique_ptr<InFlightChange> change) { + DCHECK(!change->window() || + windows_.count(change->window()->server_id()) > 0); + const uint32_t change_id = next_change_id_++; + in_flight_map_[change_id] = std::move(change); + return change_id; +} + +bool WindowTreeClient::ApplyServerChangeToExistingInFlightChange( + const InFlightChange& change) { + InFlightChange* existing_change = GetOldestInFlightChangeMatching(change); + if (!existing_change) + return false; + + existing_change->SetRevertValueFrom(change); + return true; +} + +Window* WindowTreeClient::NewWindowImpl( + NewWindowType type, + const Window::SharedProperties* properties) { + DCHECK(tree_); + Window* window = + new Window(this, MakeTransportId(client_id_, next_window_id_++)); + if (properties) + window->properties_ = *properties; + AddWindow(window); + + const uint32_t change_id = ScheduleInFlightChange(base::WrapUnique( + new CrashInFlightChange(window, type == NewWindowType::CHILD + ? ChangeType::NEW_WINDOW + : ChangeType::NEW_TOP_LEVEL_WINDOW))); + mojo::Map<mojo::String, mojo::Array<uint8_t>> transport_properties; + if (properties) { + transport_properties = + mojo::Map<mojo::String, mojo::Array<uint8_t>>::From(*properties); + } + if (type == NewWindowType::CHILD) { + tree_->NewWindow(change_id, server_id(window), + std::move(transport_properties)); + } else { + roots_.insert(window); + tree_->NewTopLevelWindow(change_id, server_id(window), + std::move(transport_properties)); + } + return window; +} + +void WindowTreeClient::SetWindowTree(mojom::WindowTreePtr window_tree_ptr) { + tree_ptr_ = std::move(window_tree_ptr); + tree_ = tree_ptr_.get(); + + tree_ptr_->GetCursorLocationMemory( + base::Bind(&WindowTreeClient::OnReceivedCursorLocationMemory, + weak_factory_.GetWeakPtr())); + + tree_ptr_.set_connection_error_handler(base::Bind( + &WindowTreeClient::OnConnectionLost, weak_factory_.GetWeakPtr())); + + if (window_manager_delegate_) { + tree_ptr_->GetWindowManagerClient(GetProxy(&window_manager_internal_client_, + tree_ptr_.associated_group())); + } +} + +void WindowTreeClient::OnConnectionLost() { + delete this; +} + +void WindowTreeClient::OnEmbedImpl(mojom::WindowTree* window_tree, + ClientSpecificId client_id, + mojom::WindowDataPtr root_data, + int64_t display_id, + Id focused_window_id, + bool drawn) { + // WARNING: this is only called if WindowTreeClient was created as the + // result of an embedding. + tree_ = window_tree; + client_id_ = client_id; + + DCHECK(roots_.empty()); + Window* root = AddWindowToClient(this, nullptr, root_data); + WindowPrivate(root).LocalSetDisplay(display_id); + roots_.insert(root); + + focused_window_ = GetWindowByServerId(focused_window_id); + + WindowPrivate(root).LocalSetParentDrawn(drawn); + + delegate_->OnEmbed(root); + + if (focused_window_) { + FOR_EACH_OBSERVER(WindowTreeClientObserver, observers_, + OnWindowTreeFocusChanged(focused_window_, nullptr)); + } +} + +void WindowTreeClient::WmNewDisplayAddedImpl(const display::Display& display, + mojom::WindowDataPtr root_data, + bool parent_drawn) { + DCHECK(window_manager_delegate_); + + Window* root = AddWindowToClient(this, nullptr, root_data); + WindowPrivate(root).LocalSetDisplay(display.id()); + WindowPrivate(root).LocalSetParentDrawn(parent_drawn); + roots_.insert(root); + + window_manager_delegate_->OnWmNewDisplay(root, display); +} + +void WindowTreeClient::OnReceivedCursorLocationMemory( + mojo::ScopedSharedBufferHandle handle) { + cursor_location_mapping_ = handle->Map(sizeof(base::subtle::Atomic32)); + DCHECK(cursor_location_mapping_); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeClient, WindowTreeClient implementation: + +void WindowTreeClient::SetDeleteOnNoRoots(bool value) { + delete_on_no_roots_ = value; +} + +const std::set<Window*>& WindowTreeClient::GetRoots() { + return roots_; +} + +Window* WindowTreeClient::GetFocusedWindow() { + return focused_window_; +} + +void WindowTreeClient::ClearFocus() { + if (!focused_window_) + return; + + SetFocus(nullptr); +} + +gfx::Point WindowTreeClient::GetCursorScreenPoint() { + // We raced initialization. Return (0, 0). + if (!cursor_location_memory()) + return gfx::Point(); + + base::subtle::Atomic32 location = + base::subtle::NoBarrier_Load(cursor_location_memory()); + return gfx::Point(static_cast<int16_t>(location >> 16), + static_cast<int16_t>(location & 0xFFFF)); +} + +void WindowTreeClient::SetEventObserver(mojom::EventMatcherPtr matcher) { + if (matcher.is_null()) { + has_event_observer_ = false; + tree_->SetEventObserver(nullptr, 0u); + } else { + has_event_observer_ = true; + event_observer_id_++; + tree_->SetEventObserver(std::move(matcher), event_observer_id_); + } +} + +Window* WindowTreeClient::NewWindow( + const Window::SharedProperties* properties) { + return NewWindowImpl(NewWindowType::CHILD, properties); +} + +Window* WindowTreeClient::NewTopLevelWindow( + const Window::SharedProperties* properties) { + Window* window = NewWindowImpl(NewWindowType::TOP_LEVEL, properties); + // Assume newly created top level windows are drawn by default, otherwise + // requests to focus will fail. We will get the real value in + // OnTopLevelCreated(). + window->LocalSetParentDrawn(true); + return window; +} + +#if !defined(NDEBUG) +std::string WindowTreeClient::GetDebugWindowHierarchy() const { + std::string result; + for (Window* root : roots_) + BuildDebugInfo(std::string(), root, &result); + return result; +} + +void WindowTreeClient::BuildDebugInfo(const std::string& depth, + Window* window, + std::string* result) const { + std::string name = window->GetName(); + *result += base::StringPrintf( + "%sid=%d visible=%s bounds=%d,%d %dx%d %s\n", depth.c_str(), + window->server_id(), window->visible() ? "true" : "false", + window->bounds().x(), window->bounds().y(), window->bounds().width(), + window->bounds().height(), !name.empty() ? name.c_str() : "(no name)"); + for (Window* child : window->children()) + BuildDebugInfo(depth + " ", child, result); +} +#endif // !defined(NDEBUG) + +//////////////////////////////////////////////////////////////////////////////// +// WindowTreeClient, WindowTreeClient implementation: + +void WindowTreeClient::AddObserver(WindowTreeClientObserver* observer) { + observers_.AddObserver(observer); +} + +void WindowTreeClient::RemoveObserver(WindowTreeClientObserver* observer) { + observers_.RemoveObserver(observer); +} + +void WindowTreeClient::OnEmbed(ClientSpecificId client_id, + mojom::WindowDataPtr root_data, + mojom::WindowTreePtr tree, + int64_t display_id, + Id focused_window_id, + bool drawn) { + DCHECK(!tree_ptr_); + tree_ptr_ = std::move(tree); + tree_ptr_.set_connection_error_handler( + base::Bind(&DeleteWindowTreeClient, this)); + + if (window_manager_delegate_) { + tree_ptr_->GetWindowManagerClient(GetProxy(&window_manager_internal_client_, + tree_ptr_.associated_group())); + } + + OnEmbedImpl(tree_ptr_.get(), client_id, std::move(root_data), display_id, + focused_window_id, drawn); +} + +void WindowTreeClient::OnEmbeddedAppDisconnected(Id window_id) { + Window* window = GetWindowByServerId(window_id); + if (window) { + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window).observers(), + OnWindowEmbeddedAppDisconnected(window)); + } +} + +void WindowTreeClient::OnUnembed(Id window_id) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + delegate_->OnUnembed(window); + WindowPrivate(window).LocalDestroy(); +} + +void WindowTreeClient::OnLostCapture(Id window_id) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightCaptureChange reset_change(this, nullptr); + if (ApplyServerChangeToExistingInFlightChange(reset_change)) + return; + + LocalSetCapture(nullptr); +} + +void WindowTreeClient::OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr data, + int64_t display_id, + bool drawn) { + // The server ack'd the top level window we created and supplied the state + // of the window at the time the server created it. For properties we do not + // have changes in flight for we can update them immediately. For properties + // with changes in flight we set the revert value from the server. + + if (!in_flight_map_.count(change_id)) { + // The window may have been destroyed locally before the server could finish + // creating the window, and before the server received the notification that + // the window has been destroyed. + return; + } + std::unique_ptr<InFlightChange> change(std::move(in_flight_map_[change_id])); + in_flight_map_.erase(change_id); + + Window* window = change->window(); + WindowPrivate window_private(window); + + // Drawn state and display-id always come from the server (they can't be + // modified locally). + window_private.LocalSetParentDrawn(drawn); + window_private.LocalSetDisplay(display_id); + + // The default visibilty is false, we only need update visibility if it + // differs from that. + if (data->visible) { + InFlightVisibleChange visible_change(window, data->visible); + InFlightChange* current_change = + GetOldestInFlightChangeMatching(visible_change); + if (current_change) + current_change->SetRevertValueFrom(visible_change); + else + window_private.LocalSetVisible(true); + } + + const gfx::Rect bounds(data->bounds); + { + InFlightBoundsChange bounds_change(window, bounds); + InFlightChange* current_change = + GetOldestInFlightChangeMatching(bounds_change); + if (current_change) + current_change->SetRevertValueFrom(bounds_change); + else if (window->bounds() != bounds) + window_private.LocalSetBounds(window->bounds(), bounds); + } + + // There is currently no API to bulk set properties, so we iterate over each + // property individually. + Window::SharedProperties properties = + data->properties.To<std::map<std::string, std::vector<uint8_t>>>(); + for (const auto& pair : properties) { + InFlightPropertyChange property_change( + window, pair.first, mojo::Array<uint8_t>::From(pair.second)); + InFlightChange* current_change = + GetOldestInFlightChangeMatching(property_change); + if (current_change) + current_change->SetRevertValueFrom(property_change); + else + window_private.LocalSetSharedProperty(pair.first, &(pair.second)); + } + + // Top level windows should not have a parent. + DCHECK_EQ(0u, data->parent_id); +} + +void WindowTreeClient::OnWindowBoundsChanged(Id window_id, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightBoundsChange new_change(window, new_bounds); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + WindowPrivate(window).LocalSetBounds(old_bounds, new_bounds); +} + +void WindowTreeClient::OnClientAreaChanged( + uint32_t window_id, + const gfx::Insets& new_client_area, + mojo::Array<gfx::Rect> new_additional_client_areas) { + Window* window = GetWindowByServerId(window_id); + if (window) { + WindowPrivate(window).LocalSetClientArea( + new_client_area, + new_additional_client_areas.To<std::vector<gfx::Rect>>()); + } +} + +void WindowTreeClient::OnTransientWindowAdded( + uint32_t window_id, + uint32_t transient_window_id) { + Window* window = GetWindowByServerId(window_id); + Window* transient_window = GetWindowByServerId(transient_window_id); + // window or transient_window or both may be null if a local delete occurs + // with an in flight add from the server. + if (window && transient_window) + WindowPrivate(window).LocalAddTransientWindow(transient_window); +} + +void WindowTreeClient::OnTransientWindowRemoved( + uint32_t window_id, + uint32_t transient_window_id) { + Window* window = GetWindowByServerId(window_id); + Window* transient_window = GetWindowByServerId(transient_window_id); + // window or transient_window or both may be null if a local delete occurs + // with an in flight delete from the server. + if (window && transient_window) + WindowPrivate(window).LocalRemoveTransientWindow(transient_window); +} + +void WindowTreeClient::OnWindowHierarchyChanged( + Id window_id, + Id old_parent_id, + Id new_parent_id, + mojo::Array<mojom::WindowDataPtr> windows) { + Window* initial_parent = + windows.size() ? GetWindowByServerId(windows[0]->parent_id) : NULL; + + const bool was_window_known = GetWindowByServerId(window_id) != nullptr; + + BuildWindowTree(this, windows, initial_parent); + + // If the window was not known, then BuildWindowTree() will have created it + // and parented the window. + if (!was_window_known) + return; + + Window* new_parent = GetWindowByServerId(new_parent_id); + Window* old_parent = GetWindowByServerId(old_parent_id); + Window* window = GetWindowByServerId(window_id); + if (new_parent) + WindowPrivate(new_parent).LocalAddChild(window); + else + WindowPrivate(old_parent).LocalRemoveChild(window); +} + +void WindowTreeClient::OnWindowReordered(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) { + Window* window = GetWindowByServerId(window_id); + Window* relative_window = GetWindowByServerId(relative_window_id); + if (window && relative_window) + WindowPrivate(window).LocalReorder(relative_window, direction); +} + +void WindowTreeClient::OnWindowDeleted(Id window_id) { + Window* window = GetWindowByServerId(window_id); + if (window) + WindowPrivate(window).LocalDestroy(); +} + +Window* WindowTreeClient::GetCaptureWindow() { + return capture_window_; +} + +void WindowTreeClient::OnWindowVisibilityChanged(Id window_id, + bool visible) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightVisibleChange new_change(window, visible); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + WindowPrivate(window).LocalSetVisible(visible); +} + +void WindowTreeClient::OnWindowOpacityChanged(Id window_id, + float old_opacity, + float new_opacity) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightOpacityChange new_change(window, new_opacity); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + WindowPrivate(window).LocalSetOpacity(new_opacity); +} + +void WindowTreeClient::OnWindowParentDrawnStateChanged(Id window_id, + bool drawn) { + Window* window = GetWindowByServerId(window_id); + if (window) + WindowPrivate(window).LocalSetParentDrawn(drawn); +} + +void WindowTreeClient::OnWindowSharedPropertyChanged( + Id window_id, + const mojo::String& name, + mojo::Array<uint8_t> new_data) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightPropertyChange new_change(window, name, new_data); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + WindowPrivate(window).LocalSetSharedProperty(name, std::move(new_data)); +} + +void WindowTreeClient::OnWindowInputEvent(uint32_t event_id, + Id window_id, + std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) { + DCHECK(event); + Window* window = GetWindowByServerId(window_id); // May be null. + + // Non-zero event_observer_id means it matched an event observer on the + // server. + if (event_observer_id != 0 && has_event_observer_ && + event_observer_id == event_observer_id_) + delegate_->OnEventObserved(*event.get(), window); + + if (!window || !window->input_event_handler_) { + tree_->OnWindowInputEventAck(event_id, mojom::EventResult::UNHANDLED); + return; + } + + std::unique_ptr<base::Callback<void(mojom::EventResult)>> ack_callback( + new base::Callback<void(mojom::EventResult)>( + base::Bind(&mojom::WindowTree::OnWindowInputEventAck, + base::Unretained(tree_), event_id))); + + // TODO(moshayedi): crbug.com/617222. No need to convert to ui::MouseEvent or + // ui::TouchEvent once we have proper support for pointer events. + if (event->IsMousePointerEvent()) { + window->input_event_handler_->OnWindowInputEvent( + window, ui::MouseEvent(*event->AsPointerEvent()), &ack_callback); + } else if (event->IsTouchPointerEvent()) { + window->input_event_handler_->OnWindowInputEvent( + window, ui::TouchEvent(*event->AsPointerEvent()), &ack_callback); + } else { + window->input_event_handler_->OnWindowInputEvent(window, *event.get(), + &ack_callback); + } + + // The handler did not take ownership of the callback, so we send the ack, + // marking the event as not consumed. + if (ack_callback) + ack_callback->Run(mojom::EventResult::UNHANDLED); +} + +void WindowTreeClient::OnEventObserved(std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) { + DCHECK(event); + if (has_event_observer_ && event_observer_id == event_observer_id_) + delegate_->OnEventObserved(*event.get(), nullptr /* target */); +} + +void WindowTreeClient::OnWindowFocused(Id focused_window_id) { + Window* focused_window = GetWindowByServerId(focused_window_id); + InFlightFocusChange new_change(this, focused_window); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + LocalSetFocus(focused_window); +} + +void WindowTreeClient::OnWindowPredefinedCursorChanged( + Id window_id, + mojom::Cursor cursor) { + Window* window = GetWindowByServerId(window_id); + if (!window) + return; + + InFlightPredefinedCursorChange new_change(window, cursor); + if (ApplyServerChangeToExistingInFlightChange(new_change)) + return; + + WindowPrivate(window).LocalSetPredefinedCursor(cursor); +} + +void WindowTreeClient::OnChangeCompleted(uint32_t change_id, bool success) { + std::unique_ptr<InFlightChange> change(std::move(in_flight_map_[change_id])); + in_flight_map_.erase(change_id); + if (!change) + return; + + if (!success) + change->ChangeFailed(); + + InFlightChange* next_change = GetOldestInFlightChangeMatching(*change); + if (next_change) { + if (!success) + next_change->SetRevertValueFrom(*change); + } else if (!success) { + change->Revert(); + } +} + +void WindowTreeClient::GetWindowManager( + mojo::AssociatedInterfaceRequest<WindowManager> internal) { + window_manager_internal_.reset( + new mojo::AssociatedBinding<mojom::WindowManager>(this, + std::move(internal))); +} + +void WindowTreeClient::RequestClose(uint32_t window_id) { + Window* window = GetWindowByServerId(window_id); + if (!window || !IsRoot(window)) + return; + + FOR_EACH_OBSERVER(WindowObserver, *WindowPrivate(window).observers(), + OnRequestClose(window)); +} + +void WindowTreeClient::OnConnect(ClientSpecificId client_id) { + client_id_ = client_id; +} + +void WindowTreeClient::WmNewDisplayAdded(mojom::DisplayPtr display, + mojom::WindowDataPtr root_data, + bool parent_drawn) { + WmNewDisplayAddedImpl(display.To<display::Display>(), std::move(root_data), + parent_drawn); +} + +void WindowTreeClient::WmSetBounds(uint32_t change_id, + Id window_id, + const gfx::Rect& transit_bounds) { + Window* window = GetWindowByServerId(window_id); + bool result = false; + if (window) { + DCHECK(window_manager_delegate_); + gfx::Rect bounds = transit_bounds; + result = window_manager_delegate_->OnWmSetBounds(window, &bounds); + if (result) { + // If the resulting bounds differ return false. Returning false ensures + // the client applies the bounds we set below. + result = bounds == transit_bounds; + window->SetBounds(bounds); + } + } + if (window_manager_internal_client_) + window_manager_internal_client_->WmResponse(change_id, result); +} + +void WindowTreeClient::WmSetProperty(uint32_t change_id, + Id window_id, + const mojo::String& name, + mojo::Array<uint8_t> transit_data) { + Window* window = GetWindowByServerId(window_id); + bool result = false; + if (window) { + DCHECK(window_manager_delegate_); + std::unique_ptr<std::vector<uint8_t>> data; + if (!transit_data.is_null()) { + data.reset( + new std::vector<uint8_t>(transit_data.To<std::vector<uint8_t>>())); + } + result = window_manager_delegate_->OnWmSetProperty(window, name, &data); + if (result) { + // If the resulting bounds differ return false. Returning false ensures + // the client applies the bounds we set below. + window->SetSharedPropertyInternal(name, data.get()); + } + } + if (window_manager_internal_client_) + window_manager_internal_client_->WmResponse(change_id, result); +} + +void WindowTreeClient::WmCreateTopLevelWindow( + uint32_t change_id, + ClientSpecificId requesting_client_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> transport_properties) { + std::map<std::string, std::vector<uint8_t>> properties = + transport_properties.To<std::map<std::string, std::vector<uint8_t>>>(); + Window* window = + window_manager_delegate_->OnWmCreateTopLevelWindow(&properties); + embedded_windows_[requesting_client_id].insert(window); + if (window_manager_internal_client_) { + window_manager_internal_client_->OnWmCreatedTopLevelWindow( + change_id, server_id(window)); + } +} + +void WindowTreeClient::WmClientJankinessChanged(ClientSpecificId client_id, + bool janky) { + if (window_manager_delegate_) { + auto it = embedded_windows_.find(client_id); + CHECK(it != embedded_windows_.end()); + window_manager_delegate_->OnWmClientJankinessChanged( + embedded_windows_[client_id], janky); + } +} + +void WindowTreeClient::OnAccelerator(uint32_t id, + std::unique_ptr<ui::Event> event) { + DCHECK(event); + window_manager_delegate_->OnAccelerator(id, *event.get()); +} + +void WindowTreeClient::SetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) { + if (window_manager_internal_client_) { + window_manager_internal_client_->WmSetFrameDecorationValues( + std::move(values)); + } +} + +void WindowTreeClient::SetNonClientCursor(Window* window, + mus::mojom::Cursor cursor_id) { + window_manager_internal_client_->WmSetNonClientCursor(server_id(window), + cursor_id); +} + +void WindowTreeClient::AddAccelerator( + uint32_t id, + mojom::EventMatcherPtr event_matcher, + const base::Callback<void(bool)>& callback) { + if (window_manager_internal_client_) { + window_manager_internal_client_->AddAccelerator( + id, std::move(event_matcher), callback); + } +} + +void WindowTreeClient::RemoveAccelerator(uint32_t id) { + if (window_manager_internal_client_) { + window_manager_internal_client_->RemoveAccelerator(id); + } +} + +void WindowTreeClient::AddActivationParent(Window* window) { + if (window_manager_internal_client_) + window_manager_internal_client_->AddActivationParent(server_id(window)); +} + +void WindowTreeClient::RemoveActivationParent(Window* window) { + if (window_manager_internal_client_) + window_manager_internal_client_->RemoveActivationParent(server_id(window)); +} + +void WindowTreeClient::ActivateNextWindow() { + if (window_manager_internal_client_) + window_manager_internal_client_->ActivateNextWindow(); +} + +void WindowTreeClient::SetUnderlaySurfaceOffsetAndExtendedHitArea( + Window* window, + const gfx::Vector2d& offset, + const gfx::Insets& hit_area) { + if (window_manager_internal_client_) { + window_manager_internal_client_->SetUnderlaySurfaceOffsetAndExtendedHitArea( + server_id(window), offset.x(), offset.y(), hit_area); + } +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_tree_client_delegate.cc b/chromium/components/mus/public/cpp/lib/window_tree_client_delegate.cc new file mode 100644 index 00000000000..ed53012b227 --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_tree_client_delegate.cc @@ -0,0 +1,11 @@ +// 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 "components/mus/public/cpp/window_tree_client_delegate.h" + +namespace mus { + +void WindowTreeClientDelegate::OnUnembed(Window* root) {} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/lib/window_tree_host_factory.cc b/chromium/components/mus/public/cpp/lib/window_tree_host_factory.cc new file mode 100644 index 00000000000..0d55bae13fe --- /dev/null +++ b/chromium/components/mus/public/cpp/lib/window_tree_host_factory.cc @@ -0,0 +1,32 @@ +// 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 "components/mus/public/cpp/window_tree_host_factory.h" + +#include "components/mus/public/cpp/window_tree_client.h" +#include "components/mus/public/cpp/window_tree_client_delegate.h" +#include "services/shell/public/cpp/connector.h" + +namespace mus { + +void CreateWindowTreeHost(mojom::WindowTreeHostFactory* factory, + WindowTreeClientDelegate* delegate, + mojom::WindowTreeHostPtr* host, + WindowManagerDelegate* window_manager_delegate) { + mojom::WindowTreeClientPtr tree_client; + new WindowTreeClient(delegate, window_manager_delegate, + GetProxy(&tree_client)); + factory->CreateWindowTreeHost(GetProxy(host), std::move(tree_client)); +} + +void CreateWindowTreeHost(shell::Connector* connector, + WindowTreeClientDelegate* delegate, + mojom::WindowTreeHostPtr* host, + WindowManagerDelegate* window_manager_delegate) { + mojom::WindowTreeHostFactoryPtr factory; + connector->ConnectToInterface("mojo:mus", &factory); + CreateWindowTreeHost(factory.get(), delegate, host, window_manager_delegate); +} + +} // namespace mus diff --git a/chromium/components/mus/public/cpp/output_surface.h b/chromium/components/mus/public/cpp/output_surface.h new file mode 100644 index 00000000000..1b1feccff37 --- /dev/null +++ b/chromium/components/mus/public/cpp/output_surface.h @@ -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. + +#ifndef COMPONENTS_MUS_PUBLIC_CPP_OUTPUT_SURFACE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_OUTPUT_SURFACE_H_ + +#include "base/macros.h" +#include "cc/output/output_surface.h" +#include "cc/surfaces/surface_id.h" +#include "components/mus/public/cpp/window_surface.h" +#include "components/mus/public/cpp/window_surface_client.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { + +class OutputSurface : public cc::OutputSurface, public WindowSurfaceClient { + public: + OutputSurface(const scoped_refptr<cc::ContextProvider>& context_provider, + std::unique_ptr<WindowSurface> surface); + ~OutputSurface() override; + + // cc::OutputSurface implementation. + void SwapBuffers(cc::CompositorFrame frame) override; + bool BindToClient(cc::OutputSurfaceClient* client) override; + void DetachFromClient() override; + void BindFramebuffer() override; + uint32_t GetFramebufferCopyTextureFormat() override; + + private: + // WindowSurfaceClient implementation: + void OnResourcesReturned( + WindowSurface* surface, + mojo::Array<cc::ReturnedResource> resources) override; + + void SwapBuffersComplete(); + + std::unique_ptr<WindowSurface> surface_; + + DISALLOW_COPY_AND_ASSIGN(OutputSurface); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_OUTPUT_SURFACE_H_ diff --git a/chromium/components/mus/public/cpp/property_type_converters.h b/chromium/components/mus/public/cpp/property_type_converters.h new file mode 100644 index 00000000000..5676253e810 --- /dev/null +++ b/chromium/components/mus/public/cpp/property_type_converters.h @@ -0,0 +1,96 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_PROPERTY_TYPE_CONVERTERS_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_PROPERTY_TYPE_CONVERTERS_H_ + +#include <stdint.h> +#include <vector> + +#include "base/strings/string16.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +class SkBitmap; + +namespace gfx { +class Rect; +class Size; +} + +namespace mojo { + +// TODO(beng): these methods serialize types used for standard properties +// to vectors of bytes used by Window::SetSharedProperty(). +// replace this with serialization code generated @ bindings. +// This would be especially useful for SkBitmap, which could be +// replaced with the skia.Bitmap mojom struct serialization. + +template <> +struct TypeConverter<std::vector<uint8_t>, gfx::Rect> { + static std::vector<uint8_t> Convert(const gfx::Rect& input); +}; +template <> +struct TypeConverter<gfx::Rect, std::vector<uint8_t>> { + static gfx::Rect Convert(const std::vector<uint8_t>& input); +}; + +template <> +struct TypeConverter<std::vector<uint8_t>, gfx::Size> { + static std::vector<uint8_t> Convert(const gfx::Size& input); +}; +template <> +struct TypeConverter<gfx::Size, std::vector<uint8_t>> { + static gfx::Size Convert(const std::vector<uint8_t>& input); +}; + +template <> +struct TypeConverter<std::vector<uint8_t>, int32_t> { + static std::vector<uint8_t> Convert(const int32_t& input); +}; +template <> +struct TypeConverter<int32_t, std::vector<uint8_t>> { + static int32_t Convert(const std::vector<uint8_t>& input); +}; + +template <> +struct TypeConverter<std::vector<uint8_t>, base::string16> { + static std::vector<uint8_t> Convert(const base::string16& input); +}; +template <> +struct TypeConverter<base::string16, std::vector<uint8_t>> { + static base::string16 Convert(const std::vector<uint8_t>& input); +}; + +template <> +struct TypeConverter<std::vector<uint8_t>, std::string> { + static std::vector<uint8_t> Convert(const std::string& input); +}; +template <> +struct TypeConverter<std::string, std::vector<uint8_t>> { + static std::string Convert(const std::vector<uint8_t>& input); +}; + +// NOTE: These methods only serialize and deserialize the common case of RGBA +// 8888 bitmaps with premultiplied alpha. +template <> +struct TypeConverter<std::vector<uint8_t>, SkBitmap> { + static std::vector<uint8_t> Convert(const SkBitmap& input); +}; +template <> +struct TypeConverter<SkBitmap, std::vector<uint8_t>> { + static SkBitmap Convert(const std::vector<uint8_t>& input); +}; + +template <> +struct TypeConverter<std::vector<uint8_t>, bool> { + static std::vector<uint8_t> Convert(bool input); +}; +template <> +struct TypeConverter<bool, std::vector<uint8_t>> { + static bool Convert(const std::vector<uint8_t>& input); +}; + +} // namespace mojo + +#endif // COMPONENTS_MUS_PUBLIC_CPP_PROPERTY_TYPE_CONVERTERS_H_ diff --git a/chromium/components/mus/public/cpp/scoped_window_ptr.h b/chromium/components/mus/public/cpp/scoped_window_ptr.h new file mode 100644 index 00000000000..b94e0f945f3 --- /dev/null +++ b/chromium/components/mus/public/cpp/scoped_window_ptr.h @@ -0,0 +1,42 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_SCOPED_WINDOW_PTR_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_SCOPED_WINDOW_PTR_H_ + +#include "base/macros.h" +#include "components/mus/public/cpp/window_observer.h" + +namespace mus { + +// Wraps a Window, taking overship of the Window. Also deals with Window being +// destroyed while ScopedWindowPtr still exists. +class ScopedWindowPtr : public WindowObserver { + public: + explicit ScopedWindowPtr(Window* window); + ~ScopedWindowPtr() override; + + // Destroys |window|. If |window| is a root of the WindowManager than the + // WindowManager is destroyed (which in turn destroys |window|). + // + // NOTE: this function (and class) are only useful for clients that only + // ever have a single root. + static void DeleteWindowOrWindowManager(Window* window); + + Window* window() { return window_; } + const Window* window() const { return window_; } + + private: + void DetachFromWindow(); + + void OnWindowDestroying(Window* window) override; + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(ScopedWindowPtr); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_SCOPED_WINDOW_PTR_H_ diff --git a/chromium/components/mus/public/cpp/window.h b/chromium/components/mus/public/cpp/window.h new file mode 100644 index 00000000000..eabc6098a5b --- /dev/null +++ b/chromium/components/mus/public/cpp/window.h @@ -0,0 +1,356 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_H_ + +#include <stdint.h> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/common/types.h" +#include "components/mus/public/interfaces/mus_constants.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/array.h" +#include "services/shell/public/interfaces/interface_provider.mojom.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +class Size; +} + +namespace mus { + +class InputEventHandler; +class ServiceProviderImpl; +class WindowObserver; +class WindowSurface; +class WindowSurfaceBinding; +class WindowTreeClient; +class WindowTreeClientPrivate; + +namespace { +class OrderChangedNotifier; +} + +// Defined in window_property.h (which we do not include) +template <typename T> +struct WindowProperty; + +// Windows are owned by the WindowTreeClient. See WindowTreeClientDelegate for +// details on ownership. +// +// TODO(beng): Right now, you'll have to implement a WindowObserver to track +// destruction and NULL any pointers you have. +// Investigate some kind of smart pointer or weak pointer for these. +class Window { + public: + using Children = std::vector<Window*>; + using EmbedCallback = base::Callback<void(bool)>; + using PropertyDeallocator = void (*)(int64_t value); + using SharedProperties = std::map<std::string, std::vector<uint8_t>>; + + // Destroys this window and all its children. Destruction is allowed for + // windows that were created by this connection, or the roots. For windows + // from other connections (except the roots), Destroy() does nothing. If the + // destruction is allowed observers are notified and the Window is + // immediately deleted. + void Destroy(); + + WindowTreeClient* window_tree() { return client_; } + + // The local_id is provided for client code. The local_id is not set or + // manipulated by mus. The default value is -1. + void set_local_id(int id) { local_id_ = id; } + int local_id() const { return local_id_; } + + int64_t display_id() const { return display_id_; } + + // Geometric disposition relative to parent window. + const gfx::Rect& bounds() const { return bounds_; } + void SetBounds(const gfx::Rect& bounds); + + // Geometric disposition relative to root window. + gfx::Rect GetBoundsInRoot() const; + + const gfx::Insets& client_area() const { return client_area_; } + const std::vector<gfx::Rect>& additional_client_areas() { + return additional_client_areas_; + } + void SetClientArea(const gfx::Insets& new_client_area) { + SetClientArea(new_client_area, std::vector<gfx::Rect>()); + } + void SetClientArea(const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& additional_client_areas); + + // Mouse events outside the hit test mask do not hit the window. Returns null + // if there is no mask. + const gfx::Rect* hit_test_mask() const { return hit_test_mask_.get(); } + void SetHitTestMask(const gfx::Rect& mask_rect); + void ClearHitTestMask(); + + // Visibility (also see IsDrawn()). When created windows are hidden. + bool visible() const { return visible_; } + void SetVisible(bool value); + + float opacity() const { return opacity_; } + void SetOpacity(float opacity); + + // Cursors + mojom::Cursor predefined_cursor() const { return cursor_id_; } + void SetPredefinedCursor(mus::mojom::Cursor cursor_id); + + // A Window is drawn if the Window and all its ancestors are visible and the + // Window is attached to the root. + bool IsDrawn() const; + + std::unique_ptr<WindowSurface> RequestSurface(mojom::SurfaceType type); + + void AttachSurface(mojom::SurfaceType type, + std::unique_ptr<WindowSurfaceBinding> surface_binding); + + // The template-ized versions of the following methods rely on the presence + // of a mojo::TypeConverter<const std::vector<uint8_t>, T>. + // Sets a shared property on the window which is sent to the window server and + // shared with other clients that can view this window. + template <typename T> + void SetSharedProperty(const std::string& name, const T& data); + // Gets a shared property set on the window. The property must exist. Call + // HasSharedProperty() before calling. + template <typename T> + T GetSharedProperty(const std::string& name) const; + // Removes the shared property. + void ClearSharedProperty(const std::string& name); + bool HasSharedProperty(const std::string& name) const; + + // TODO(beng): Test only, should move to a helper. + const SharedProperties& shared_properties() { return properties_; } + + // Sets the |value| of the given window |property|. Setting to the default + // value (e.g., NULL) removes the property. The caller is responsible for the + // lifetime of any object set as a property on the Window. + // + // These properties are not visible to the window server. + template <typename T> + void SetLocalProperty(const WindowProperty<T>* property, T value); + + // Returns the value of the given window |property|. Returns the + // property-specific default value if the property was not previously set. + // + // These properties are only visible in the current process and are not + // shared with other mojo services. + template <typename T> + T GetLocalProperty(const WindowProperty<T>* property) const; + + // Sets the |property| to its default value. Useful for avoiding a cast when + // setting to NULL. + // + // These properties are only visible in the current process and are not + // shared with other mojo services. + template <typename T> + void ClearLocalProperty(const WindowProperty<T>* property); + + void set_input_event_handler(InputEventHandler* input_event_handler) { + input_event_handler_ = input_event_handler; + } + + // Observation. + void AddObserver(WindowObserver* observer); + void RemoveObserver(WindowObserver* observer); + + // Tree. + Window* parent() { return parent_; } + const Window* parent() const { return parent_; } + + Window* GetRoot() { + return const_cast<Window*>(const_cast<const Window*>(this)->GetRoot()); + } + const Window* GetRoot() const; + + void AddChild(Window* child); + void RemoveChild(Window* child); + const Children& children() const { return children_; } + + void Reorder(Window* relative, mojom::OrderDirection direction); + void MoveToFront(); + void MoveToBack(); + + // Returns true if |child| is this or a descendant of this. + bool Contains(const Window* child) const; + + void AddTransientWindow(Window* transient_window); + void RemoveTransientWindow(Window* transient_window); + + // TODO(fsamuel): Figure out if we want to refactor transient window + // management into a separate class. + // Transient tree. + Window* transient_parent() { return transient_parent_; } + const Window* transient_parent() const { return transient_parent_; } + const Children& transient_children() const { return transient_children_; } + + void SetModal(); + bool is_modal() const { return is_modal_; } + + Window* GetChildByLocalId(int id); + + void SetTextInputState(mojo::TextInputStatePtr state); + void SetImeVisibility(bool visible, mojo::TextInputStatePtr state); + + bool HasCapture() const; + void SetCapture(); + void ReleaseCapture(); + + // Focus. See WindowTreeClient::ClearFocus() to reset focus. + void SetFocus(); + bool HasFocus() const; + void SetCanFocus(bool can_focus); + + // Embedding. See window_tree.mojom for details. + void Embed(mus::mojom::WindowTreeClientPtr client, uint32_t flags = 0); + + // NOTE: callback is run synchronously if Embed() is not allowed on this + // Window. + void Embed(mus::mojom::WindowTreeClientPtr client, + const EmbedCallback& callback, + uint32_t flags = 0); + + // TODO(sky): this API is only applicable to the WindowManager. Move it + // to a better place. + void RequestClose(); + + // Returns an internal name, set by a client app when it creates a window. + std::string GetName() const; + + protected: + // This class is subclassed only by test classes that provide a public ctor. + Window(); + ~Window(); + + private: + friend class WindowPrivate; + friend class WindowTreeClient; + friend class WindowTreeClientPrivate; + + Window(WindowTreeClient* client, Id id); + + // Used to identify this Window on the server. Clients can not change this + // value. + Id server_id() const { return server_id_; } + + // Applies a shared property change locally and forwards to the server. If + // |data| is null, this property is deleted. + void SetSharedPropertyInternal(const std::string& name, + const std::vector<uint8_t>* data); + // Called by the public {Set,Get,Clear}Property functions. + int64_t SetLocalPropertyInternal(const void* key, + const char* name, + PropertyDeallocator deallocator, + int64_t value, + int64_t default_value); + int64_t GetLocalPropertyInternal(const void* key, + int64_t default_value) const; + + void LocalDestroy(); + void LocalAddChild(Window* child); + void LocalRemoveChild(Window* child); + void LocalAddTransientWindow(Window* transient_window); + void LocalRemoveTransientWindow(Window* transient_window); + void LocalSetModal(); + // Returns true if the order actually changed. + bool LocalReorder(Window* relative, mojom::OrderDirection direction); + void LocalSetBounds(const gfx::Rect& old_bounds, const gfx::Rect& new_bounds); + void LocalSetClientArea( + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& additional_client_areas); + void LocalSetParentDrawn(bool drawn); + void LocalSetDisplay(int64_t display_id); + void LocalSetVisible(bool visible); + void LocalSetOpacity(float opacity); + void LocalSetPredefinedCursor(mojom::Cursor cursor_id); + void LocalSetSharedProperty(const std::string& name, + const std::vector<uint8_t>* data); + + // Notifies this winodw that its stacking position has changed. + void NotifyWindowStackingChanged(); + // Methods implementing visibility change notifications. See WindowObserver + // for more details. + void NotifyWindowVisibilityChanged(Window* target); + // Notifies this window's observers. Returns false if |this| was deleted + // during the call (by an observer), otherwise true. + bool NotifyWindowVisibilityChangedAtReceiver(Window* target); + // Notifies this window and its child hierarchy. Returns false if |this| was + // deleted during the call (by an observer), otherwise true. + bool NotifyWindowVisibilityChangedDown(Window* target); + // Notifies this window and its parent hierarchy. + void NotifyWindowVisibilityChangedUp(Window* target); + + // Returns true if embed is allowed for this node. If embedding is allowed all + // the children are removed. + bool PrepareForEmbed(); + + void RemoveTransientWindowImpl(Window* child); + static void ReorderWithoutNotification(Window* window, + Window* relative, + mojom::OrderDirection direction); + static bool ReorderImpl(Window* window, + Window* relative, + mojom::OrderDirection direction, + OrderChangedNotifier* notifier); + + // Returns a pointer to the stacking target that can be used by + // RestackTransientDescendants. + static Window** GetStackingTarget(Window* window); + + WindowTreeClient* client_; + Id server_id_; + int local_id_ = -1; + Window* parent_; + Children children_; + + Window* stacking_target_; + Window* transient_parent_; + Children transient_children_; + + bool is_modal_; + + base::ObserverList<WindowObserver> observers_; + InputEventHandler* input_event_handler_; + + gfx::Rect bounds_; + gfx::Insets client_area_; + std::vector<gfx::Rect> additional_client_areas_; + std::unique_ptr<gfx::Rect> hit_test_mask_; + + bool visible_; + float opacity_; + int64_t display_id_; + + mojom::Cursor cursor_id_; + + SharedProperties properties_; + + // Drawn state of our parent. This is only meaningful for root Windows, in + // which the parent Window isn't exposed to the client. + bool parent_drawn_; + + // Value struct to keep the name and deallocator for this property. + // Key cannot be used for this purpose because it can be char* or + // WindowProperty<>. + struct Value { + const char* name; + int64_t value; + PropertyDeallocator deallocator; + }; + + std::map<const void*, Value> prop_map_; + + DISALLOW_COPY_AND_ASSIGN(Window); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_H_ diff --git a/chromium/components/mus/public/cpp/window_manager_delegate.h b/chromium/components/mus/public/cpp/window_manager_delegate.h new file mode 100644 index 00000000000..99ddf34cc4e --- /dev/null +++ b/chromium/components/mus/public/cpp/window_manager_delegate.h @@ -0,0 +1,116 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_MANAGER_DELEGATE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_MANAGER_DELEGATE_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "components/mus/public/interfaces/cursor.mojom.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "components/mus/public/interfaces/window_manager_constants.mojom.h" +#include "ui/events/mojo/event.mojom.h" + +namespace display { +class Display; +} + +namespace gfx { +class Insets; +class Rect; +class Vector2d; +} + +namespace ui { +class Event; +} + +namespace mus { + +class Window; + +// See the mojom with the same name for details on the functions in this +// interface. +class WindowManagerClient { + public: + virtual void SetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) = 0; + virtual void SetNonClientCursor(Window* window, + mojom::Cursor non_client_cursor) = 0; + + virtual void AddAccelerator(uint32_t id, + mojom::EventMatcherPtr event_matcher, + const base::Callback<void(bool)>& callback) = 0; + virtual void RemoveAccelerator(uint32_t id) = 0; + virtual void AddActivationParent(Window* window) = 0; + virtual void RemoveActivationParent(Window* window) = 0; + virtual void ActivateNextWindow() = 0; + virtual void SetUnderlaySurfaceOffsetAndExtendedHitArea( + Window* window, + const gfx::Vector2d& offset, + const gfx::Insets& hit_area) = 0; + + protected: + virtual ~WindowManagerClient() {} +}; + +// Used by clients implementing a window manager. +// TODO(sky): this should be called WindowManager, but that's rather confusing +// currently. +class WindowManagerDelegate { + public: + // Called once to give the delegate access to functions only exposed to + // the WindowManager. + virtual void SetWindowManagerClient(WindowManagerClient* client) = 0; + + // A client requested the bounds of |window| to change to |bounds|. Return + // true if the bounds are allowed to change. A return value of false + // indicates the change is not allowed. + // NOTE: This should not change the bounds of |window|. Instead return the + // bounds the window should be in |bounds|. + virtual bool OnWmSetBounds(Window* window, gfx::Rect* bounds) = 0; + + // A client requested the shared property named |name| to change to + // |new_data|. Return true to allow the change to |new_data|, false + // otherwise. + virtual bool OnWmSetProperty( + Window* window, + const std::string& name, + std::unique_ptr<std::vector<uint8_t>>* new_data) = 0; + + // A client has requested a new top level window. The delegate should create + // and parent the window appropriately and return it. |properties| is the + // supplied properties from the client requesting the new window. The + // delegate may modify |properties| before calling NewWindow(), but the + // delegate does *not* own |properties|, they are valid only for the life + // of OnWmCreateTopLevelWindow(). + virtual Window* OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) = 0; + + // Called when a Mus client's jankiness changes. |windows| is the set of + // windows owned by the window manager in which the client is embedded. + virtual void OnWmClientJankinessChanged( + const std::set<Window*>& client_windows, + bool janky) = 0; + + // Called when a display is added. |window| is the root of the window tree for + // the specified display. + virtual void OnWmNewDisplay(Window* window, + const display::Display& display) = 0; + + virtual void OnAccelerator(uint32_t id, const ui::Event& event) = 0; + + protected: + virtual ~WindowManagerDelegate() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_MANAGER_DELEGATE_H_ diff --git a/chromium/components/mus/public/cpp/window_observer.h b/chromium/components/mus/public/cpp/window_observer.h new file mode 100644 index 00000000000..f1dc27913d7 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_observer.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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_OBSERVER_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_OBSERVER_H_ + +#include <stdint.h> + +#include <vector> + +#include "components/mus/public/cpp/window.h" + +namespace mus { + +class Window; + +// A note on -ing and -ed suffixes: +// +// -ing methods are called before changes are applied to the local window model. +// -ed methods are called after changes are applied to the local window model. +// +// If the change originated from another connection to the window manager, it's +// possible that the change has already been applied to the service-side model +// prior to being called, so for example in the case of OnWindowDestroying(), +// it's possible the window has already been destroyed on the service side. + +class WindowObserver { + public: + struct TreeChangeParams { + TreeChangeParams(); + Window* target; + Window* old_parent; + Window* new_parent; + Window* receiver; + }; + + virtual void OnTreeChanging(const TreeChangeParams& params) {} + virtual void OnTreeChanged(const TreeChangeParams& params) {} + + virtual void OnWindowReordering(Window* window, + Window* relative_window, + mojom::OrderDirection direction) {} + virtual void OnWindowReordered(Window* window, + Window* relative_window, + mojom::OrderDirection direction) {} + + virtual void OnWindowDestroying(Window* window) {} + virtual void OnWindowDestroyed(Window* window) {} + + virtual void OnWindowBoundsChanging(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) {} + virtual void OnWindowBoundsChanged(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) {} + virtual void OnWindowLostCapture(Window* window) {} + virtual void OnWindowClientAreaChanged( + Window* window, + const gfx::Insets& old_client_area, + const std::vector<gfx::Rect>& old_additional_client_areas) {} + + virtual void OnWindowFocusChanged(Window* gained_focus, Window* lost_focus) {} + + virtual void OnWindowPredefinedCursorChanged(Window* window, + mojom::Cursor cursor) {} + virtual void OnWindowVisibilityChanging(Window* window) {} + virtual void OnWindowVisibilityChanged(Window* window) {} + virtual void OnWindowOpacityChanged(Window* window, + float old_opacity, + float new_opacity) {} + + // Invoked when this Window's shared properties have changed. This can either + // be caused by SetSharedProperty() being called locally, or by us receiving + // a mojo message that this property has changed. If this property has been + // added, |old_data| is null. If this property was removed, |new_data| is + // null. + virtual void OnWindowSharedPropertyChanged( + Window* window, + const std::string& name, + const std::vector<uint8_t>* old_data, + const std::vector<uint8_t>* new_data) {} + + // Invoked when SetProperty() or ClearProperty() is called on the window. + // |key| is either a WindowProperty<T>* (SetProperty, ClearProperty). Either + // way, it can simply be compared for equality with the property + // constant. |old| is the old property value, which must be cast to the + // appropriate type before use. + virtual void OnWindowLocalPropertyChanged(Window* window, + const void* key, + intptr_t old) {} + + virtual void OnWindowEmbeddedAppDisconnected(Window* window) {} + + // Sent when the drawn state changes. This is only sent for the root nodes + // when embedded. + virtual void OnWindowDrawnChanging(Window* window) {} + virtual void OnWindowDrawnChanged(Window* window) {} + + // The WindowManager has requested the window to close. If the observer + // allows the close it should destroy the window as appropriate. + virtual void OnRequestClose(Window* window) {} + + protected: + virtual ~WindowObserver() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_OBSERVER_H_ diff --git a/chromium/components/mus/public/cpp/window_property.h b/chromium/components/mus/public/cpp/window_property.h new file mode 100644 index 00000000000..3299ac9db98 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_property.h @@ -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. + +#ifndef COMPONENTS_MUS_PUBLIC_CPP_WINDOW_PROPERTY_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_PROPERTY_H_ + +#include <stdint.h> + +// To define a new WindowProperty: +// +// #include "components/mus/public/cpp/window_property.h" +// +// MUS_DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(FOO_EXPORT, MyType); +// namespace foo { +// // Use this to define an exported property that is primitive, +// // or a pointer you don't want automatically deleted. +// MUS_DEFINE_WINDOW_PROPERTY_KEY(MyType, kMyKey, MyDefault); +// +// // Use this to define an exported property whose value is a heap +// // allocated object, and has to be owned and freed by the window. +// MUS_DEFINE_OWNED_WINDOW_PROPERTY_KEY(gfx::Rect, kRestoreBoundsKey, +// nullptr); +// +// // Use this to define a non exported property that is primitive, +// // or a pointer you don't want to automatically deleted, and is used +// // only in a specific file. This will define the property in an unnamed +// // namespace which cannot be accessed from another file. +// MUS_DEFINE_LOCAL_WINDOW_PROPERTY_KEY(MyType, kMyKey, MyDefault); +// +// } // foo namespace +// +// To define a new type used for WindowProperty. +// +// // outside all namespaces: +// MUS_DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(FOO_EXPORT, MyType) +// +// If a property type is not exported, use +// MUS_DECLARE_WINDOW_PROPERTY_TYPE(MyType) which is a shorthand for +// MUS_DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(, MyType). + +namespace mus { + +template <typename T> +void Window::SetSharedProperty(const std::string& name, const T& data) { + const std::vector<uint8_t> bytes = + mojo::TypeConverter<std::vector<uint8_t>, T>::Convert(data); + SetSharedPropertyInternal(name, &bytes); +} + +template <typename T> +T Window::GetSharedProperty(const std::string& name) const { + DCHECK(HasSharedProperty(name)); + auto it = properties_.find(name); + return mojo::TypeConverter<T, std::vector<uint8_t>>::Convert(it->second); +} + +namespace { + +// No single new-style cast works for every conversion to/from int64_t, so we +// need this helper class. A third specialization is needed for bool because +// MSVC warning C4800 (forcing value to bool) is not suppressed by an explicit +// cast (!). +template <typename T> +class WindowPropertyCaster { + public: + static int64_t ToInt64(T x) { return static_cast<int64_t>(x); } + static T FromInt64(int64_t x) { return static_cast<T>(x); } +}; +template <typename T> +class WindowPropertyCaster<T*> { + public: + static int64_t ToInt64(T* x) { return reinterpret_cast<int64_t>(x); } + static T* FromInt64(int64_t x) { return reinterpret_cast<T*>(x); } +}; +template <> +class WindowPropertyCaster<bool> { + public: + static int64_t ToInt64(bool x) { return static_cast<int64_t>(x); } + static bool FromInt64(int64_t x) { return x != 0; } +}; + +} // namespace + +template <typename T> +struct WindowProperty { + T default_value; + const char* name; + Window::PropertyDeallocator deallocator; +}; + +template <typename T> +void Window::SetLocalProperty(const WindowProperty<T>* property, T value) { + int64_t old = SetLocalPropertyInternal( + property, property->name, + value == property->default_value ? nullptr : property->deallocator, + WindowPropertyCaster<T>::ToInt64(value), + WindowPropertyCaster<T>::ToInt64(property->default_value)); + if (property->deallocator && + old != WindowPropertyCaster<T>::ToInt64(property->default_value)) { + (*property->deallocator)(old); + } +} + +template <typename T> +T Window::GetLocalProperty(const WindowProperty<T>* property) const { + return WindowPropertyCaster<T>::FromInt64(GetLocalPropertyInternal( + property, WindowPropertyCaster<T>::ToInt64(property->default_value))); +} + +template <typename T> +void Window::ClearLocalProperty(const WindowProperty<T>* property) { + SetLocalProperty(property, property->default_value); +} + +} // namespace mus + +// Macros to instantiate the property getter/setter template functions. +#define MUS_DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(EXPORT, T) \ + template EXPORT void mus::Window::SetLocalProperty( \ + const mus::WindowProperty<T>*, T); \ + template EXPORT T mus::Window::GetLocalProperty( \ + const mus::WindowProperty<T>*) const; \ + template EXPORT void mus::Window::ClearLocalProperty( \ + const mus::WindowProperty<T>*); +#define MUS_DECLARE_WINDOW_PROPERTY_TYPE(T) \ + MUS_DECLARE_EXPORTED_WINDOW_PROPERTY_TYPE(, T) + +#define MUS_DEFINE_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + static_assert(sizeof(TYPE) <= sizeof(int64_t), \ + "Property type must fit in 64 bits"); \ + namespace { \ + const mus::WindowProperty<TYPE> NAME##_Value = {DEFAULT, #NAME, nullptr}; \ + } \ + const mus::WindowProperty<TYPE>* const NAME = &NAME##_Value; + +#define MUS_DEFINE_LOCAL_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + static_assert(sizeof(TYPE) <= sizeof(int64_t), \ + "Property type must fit in 64 bits"); \ + namespace { \ + const mus::WindowProperty<TYPE> NAME##_Value = {DEFAULT, #NAME, nullptr}; \ + const mus::WindowProperty<TYPE>* const NAME = &NAME##_Value; \ + } + +#define MUS_DEFINE_OWNED_WINDOW_PROPERTY_KEY(TYPE, NAME, DEFAULT) \ + namespace { \ + void Deallocator##NAME(int64_t p) { \ + enum { type_must_be_complete = sizeof(TYPE) }; \ + delete mus::WindowPropertyCaster<TYPE*>::FromInt64(p); \ + } \ + const mus::WindowProperty<TYPE*> NAME##_Value = {DEFAULT, #NAME, \ + &Deallocator##NAME}; \ + } \ + const mus::WindowProperty<TYPE*>* const NAME = &NAME##_Value; + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_PROPERTY_H_ diff --git a/chromium/components/mus/public/cpp/window_surface.h b/chromium/components/mus/public/cpp/window_surface.h new file mode 100644 index 00000000000..97af8123d61 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_surface.h @@ -0,0 +1,85 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_H_ + +#include <memory> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "components/mus/public/interfaces/surface.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" + +namespace mus { + +class WindowSurfaceBinding; +class WindowSurfaceClient; +class Window; + +// A WindowSurface is wrapper to simplify submitting CompositorFrames to +// Windows, and receiving ReturnedResources. +class WindowSurface : public mojom::SurfaceClient { + public: + // static + static std::unique_ptr<WindowSurface> Create( + std::unique_ptr<WindowSurfaceBinding>* surface_binding); + + ~WindowSurface() override; + + // Called to indicate that the current thread has assumed control of this + // object. + void BindToThread(); + + void SubmitCompositorFrame(cc::CompositorFrame frame, + const base::Closure& callback); + + void set_client(WindowSurfaceClient* client) { client_ = client; } + + private: + friend class Window; + + WindowSurface(mojo::InterfacePtrInfo<mojom::Surface> surface_info, + mojo::InterfaceRequest<mojom::SurfaceClient> client_request); + + // SurfaceClient implementation: + void ReturnResources(mojo::Array<cc::ReturnedResource> resources) override; + + WindowSurfaceClient* client_; + mojo::InterfacePtrInfo<mojom::Surface> surface_info_; + mojo::InterfaceRequest<mojom::SurfaceClient> client_request_; + mojom::SurfacePtr surface_; + std::unique_ptr<mojo::Binding<mojom::SurfaceClient>> client_binding_; + std::unique_ptr<base::ThreadChecker> thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(WindowSurface); +}; + +// A WindowSurfaceBinding is a bundle of mojo interfaces that are to be used by +// or implemented by the Mus window server when passed into +// Window::AttachSurface. WindowSurfaceBinding has no standalone functionality. +class WindowSurfaceBinding { + public: + ~WindowSurfaceBinding(); + + private: + friend class WindowSurface; + friend class Window; + + WindowSurfaceBinding( + mojo::InterfaceRequest<mojom::Surface> surface_request, + mojo::InterfacePtrInfo<mojom::SurfaceClient> surface_client); + + mojo::InterfaceRequest<mojom::Surface> surface_request_; + mojo::InterfacePtrInfo<mojom::SurfaceClient> surface_client_; + + DISALLOW_COPY_AND_ASSIGN(WindowSurfaceBinding); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_H_ diff --git a/chromium/components/mus/public/cpp/window_surface_client.h b/chromium/components/mus/public/cpp/window_surface_client.h new file mode 100644 index 00000000000..1208d2028f2 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_surface_client.h @@ -0,0 +1,24 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_CLIENT_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_CLIENT_H_ + +namespace mus { + +class WindowSurface; + +class WindowSurfaceClient { + public: + virtual void OnResourcesReturned( + WindowSurface* surface, + mojo::Array<cc::ReturnedResource> resources) = 0; + + protected: + virtual ~WindowSurfaceClient() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_SURFACE_CLIENT_H_ diff --git a/chromium/components/mus/public/cpp/window_tracker.h b/chromium/components/mus/public/cpp/window_tracker.h new file mode 100644 index 00000000000..db277531495 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_tracker.h @@ -0,0 +1,21 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TRACKER_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TRACKER_H_ + +#include <stdint.h> +#include <set> + +#include "base/macros.h" +#include "components/mus/public/cpp/window_observer.h" +#include "ui/base/window_tracker_template.h" + +namespace mus { + +using WindowTracker = ui::WindowTrackerTemplate<Window, WindowObserver>; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TRACKER_H_ diff --git a/chromium/components/mus/public/cpp/window_tree_client.h b/chromium/components/mus/public/cpp/window_tree_client.h new file mode 100644 index 00000000000..24ef564f3f5 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_tree_client.h @@ -0,0 +1,407 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_H_ + +#include <stdint.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/atomicops.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "components/mus/common/types.h" +#include "components/mus/public/cpp/window.h" +#include "components/mus/public/cpp/window_manager_delegate.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace display { +class Display; +} + +namespace gfx { +class Insets; +class Size; +} + +namespace shell { +class Connector; +} + +namespace mus { +class InFlightChange; +class WindowTreeClientDelegate; +class WindowTreeClientPrivate; +class WindowTreeClientObserver; +enum class ChangeType; + +// Manages the connection with mus. +// +// WindowTreeClient is deleted by any of the following: +// . If all the roots of the connection are destroyed and the connection is +// configured to delete when there are no roots (true if the WindowTreeClient +// is created with a mojom::WindowTreeClientRequest). This happens +// if the owner of the roots Embed()s another app in all the roots, or all +// the roots are explicitly deleted. +// . The connection to mus is lost. +// . Explicitly by way of calling delete. +// +// When WindowTreeClient is deleted all windows are deleted (and observers +// notified). This is followed by calling +// WindowTreeClientDelegate::OnWindowTreeClientDestroyed(). +class WindowTreeClient : public mojom::WindowTreeClient, + public mojom::WindowManager, + public WindowManagerClient { + public: + WindowTreeClient(WindowTreeClientDelegate* delegate, + WindowManagerDelegate* window_manager_delegate, + mojom::WindowTreeClientRequest request); + ~WindowTreeClient() override; + + // Establishes the connection by way of the WindowTreeFactory. + void ConnectViaWindowTreeFactory(shell::Connector* connector); + + // Establishes the connection by way of WindowManagerWindowTreeFactory. + void ConnectAsWindowManager(shell::Connector* connector); + + // Wait for OnEmbed(), returning when done. + void WaitForEmbed(); + + bool connected() const { return tree_ != nullptr; } + ClientSpecificId client_id() const { return client_id_; } + + // API exposed to the window implementations that pushes local changes to the + // service. + void DestroyWindow(Window* window); + + // These methods take TransportIds. For windows owned by the current client, + // the client id high word can be zero. In all cases, the TransportId 0x1 + // refers to the root window. + void AddChild(Window* parent, Id child_id); + void RemoveChild(Window* parent, Id child_id); + + void AddTransientWindow(Window* window, Id transient_window_id); + void RemoveTransientWindowFromParent(Window* window); + + void SetModal(Window* window); + + void Reorder(Window* window, + Id relative_window_id, + mojom::OrderDirection direction); + + // Returns true if the specified window was created by this client. + bool OwnsWindow(Window* window) const; + + void SetBounds(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& bounds); + void SetCapture(Window* window); + void ReleaseCapture(Window* window); + void SetClientArea(Id window_id, + const gfx::Insets& client_area, + const std::vector<gfx::Rect>& additional_client_areas); + void SetHitTestMask(Id window_id, const gfx::Rect& mask); + void ClearHitTestMask(Id window_id); + void SetFocus(Window* window); + void SetCanFocus(Id window_id, bool can_focus); + void SetPredefinedCursor(Id window_id, mus::mojom::Cursor cursor_id); + void SetVisible(Window* window, bool visible); + void SetOpacity(Window* window, float opacity); + void SetProperty(Window* window, + const std::string& name, + mojo::Array<uint8_t> data); + void SetWindowTextInputState(Id window_id, mojo::TextInputStatePtr state); + void SetImeVisibility(Id window_id, + bool visible, + mojo::TextInputStatePtr state); + + void Embed(Id window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + const mojom::WindowTree::EmbedCallback& callback); + + void RequestClose(Window* window); + + void AttachSurface(Id window_id, + mojom::SurfaceType type, + mojo::InterfaceRequest<mojom::Surface> surface, + mojom::SurfaceClientPtr client); + + // Sets the input capture to |window| without notifying the server. + void LocalSetCapture(Window* window); + // Sets focus to |window| without notifying the server. + void LocalSetFocus(Window* window); + + // Start/stop tracking windows. While tracked, they can be retrieved via + // WindowTreeClient::GetWindowById. + void AddWindow(Window* window); + + bool IsRoot(Window* window) const { return roots_.count(window) > 0; } + + void OnWindowDestroying(Window* window); + + // Called after the window's observers have been notified of destruction (as + // the last step of ~Window). + void OnWindowDestroyed(Window* window); + + Window* GetWindowByServerId(Id id); + + // Sets whether this is deleted when there are no roots. The default is to + // delete when there are no roots. + void SetDeleteOnNoRoots(bool value); + + // Returns the root of this connection. + const std::set<Window*>& GetRoots(); + + // Returns the Window with input capture; null if no window has requested + // input capture, or if another app has capture. + Window* GetCaptureWindow(); + + // Returns the focused window; null if focus is not yet known or another app + // is focused. + Window* GetFocusedWindow(); + + // Sets focus to null. This does nothing if focus is currently null. + void ClearFocus(); + + // Returns the current location of the mouse on screen. Note: this method may + // race the asynchronous initialization; but in that case we return (0, 0). + gfx::Point GetCursorScreenPoint(); + + // See description in window_tree.mojom. When an existing event observer is + // updated or cleared then any future events from the server for that observer + // will be ignored. + void SetEventObserver(mojom::EventMatcherPtr matcher); + + // Creates and returns a new Window (which is owned by the window server). + // Windows are initially hidden, use SetVisible(true) to show. + Window* NewWindow() { return NewWindow(nullptr); } + Window* NewWindow( + const std::map<std::string, std::vector<uint8_t>>* properties); + Window* NewTopLevelWindow( + const std::map<std::string, std::vector<uint8_t>>* properties); + + void AddObserver(WindowTreeClientObserver* observer); + void RemoveObserver(WindowTreeClientObserver* observer); + +#if !defined(NDEBUG) + std::string GetDebugWindowHierarchy() const; + void BuildDebugInfo(const std::string& depth, + Window* window, + std::string* result) const; +#endif + + private: + friend class WindowTreeClientPrivate; + + enum class NewWindowType { + CHILD, + TOP_LEVEL, + }; + + using IdToWindowMap = std::map<Id, Window*>; + + // TODO(sky): this assumes change_ids never wrap, which is a bad assumption. + using InFlightMap = std::map<uint32_t, std::unique_ptr<InFlightChange>>; + + // Returns the oldest InFlightChange that matches |change|. + InFlightChange* GetOldestInFlightChangeMatching(const InFlightChange& change); + + // See InFlightChange for details on how InFlightChanges are used. + uint32_t ScheduleInFlightChange(std::unique_ptr<InFlightChange> change); + + // Returns true if there is an InFlightChange that matches |change|. If there + // is an existing change SetRevertValueFrom() is invoked on it. Returns false + // if there is no InFlightChange matching |change|. + // See InFlightChange for details on how InFlightChanges are used. + bool ApplyServerChangeToExistingInFlightChange(const InFlightChange& change); + + Window* NewWindowImpl(NewWindowType type, + const Window::SharedProperties* properties); + + // Sets the mojom::WindowTree implementation. + void SetWindowTree(mojom::WindowTreePtr window_tree_ptr); + + // Called when the mojom::WindowTree connection is lost, deletes this. + void OnConnectionLost(); + + // OnEmbed() calls into this. Exposed as a separate function for testing. + void OnEmbedImpl(mojom::WindowTree* window_tree, + ClientSpecificId client_id, + mojom::WindowDataPtr root_data, + int64_t display_id, + Id focused_window_id, + bool drawn); + + // Called by WmNewDisplayAdded(). + void WmNewDisplayAddedImpl(const display::Display& display, + mojom::WindowDataPtr root_data, + bool parent_drawn); + + void OnReceivedCursorLocationMemory(mojo::ScopedSharedBufferHandle handle); + + // Overridden from WindowTreeClient: + void OnEmbed(ClientSpecificId client_id, + mojom::WindowDataPtr root, + mojom::WindowTreePtr tree, + int64_t display_id, + Id focused_window_id, + bool drawn) override; + void OnEmbeddedAppDisconnected(Id window_id) override; + void OnUnembed(Id window_id) override; + void OnLostCapture(Id window_id) override; + void OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr data, + int64_t display_id, + bool drawn) override; + void OnWindowBoundsChanged(Id window_id, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnClientAreaChanged( + uint32_t window_id, + const gfx::Insets& new_client_area, + mojo::Array<gfx::Rect> new_additional_client_areas) override; + void OnTransientWindowAdded(uint32_t window_id, + uint32_t transient_window_id) override; + void OnTransientWindowRemoved(uint32_t window_id, + uint32_t transient_window_id) override; + void OnWindowHierarchyChanged( + Id window_id, + Id old_parent_id, + Id new_parent_id, + mojo::Array<mojom::WindowDataPtr> windows) override; + void OnWindowReordered(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) override; + void OnWindowDeleted(Id window_id) override; + void OnWindowVisibilityChanged(Id window_id, bool visible) override; + void OnWindowOpacityChanged(Id window_id, + float old_opacity, + float new_opacity) override; + void OnWindowParentDrawnStateChanged(Id window_id, bool drawn) override; + void OnWindowSharedPropertyChanged(Id window_id, + const mojo::String& name, + mojo::Array<uint8_t> new_data) override; + void OnWindowInputEvent(uint32_t event_id, + Id window_id, + std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) override; + void OnEventObserved(std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) override; + void OnWindowFocused(Id focused_window_id) override; + void OnWindowPredefinedCursorChanged(Id window_id, + mojom::Cursor cursor) override; + void OnChangeCompleted(uint32_t change_id, bool success) override; + void RequestClose(uint32_t window_id) override; + void GetWindowManager( + mojo::AssociatedInterfaceRequest<WindowManager> internal) override; + + // Overridden from WindowManager: + void OnConnect(ClientSpecificId client_id) override; + void WmNewDisplayAdded(mojom::DisplayPtr display, + mojom::WindowDataPtr root_data, + bool parent_drawn) override; + void WmSetBounds(uint32_t change_id, + Id window_id, + const gfx::Rect& transit_bounds) override; + void WmSetProperty(uint32_t change_id, + Id window_id, + const mojo::String& name, + mojo::Array<uint8_t> transit_data) override; + void WmCreateTopLevelWindow(uint32_t change_id, + ClientSpecificId requesting_client_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> + transport_properties) override; + void WmClientJankinessChanged(ClientSpecificId client_id, + bool janky) override; + void OnAccelerator(uint32_t id, std::unique_ptr<ui::Event> event) override; + + // Overridden from WindowManagerClient: + void SetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) override; + void SetNonClientCursor(Window* window, + mus::mojom::Cursor cursor_id) override; + void AddAccelerator(uint32_t id, + mojom::EventMatcherPtr event_matcher, + const base::Callback<void(bool)>& callback) override; + void RemoveAccelerator(uint32_t id) override; + void AddActivationParent(Window* window) override; + void RemoveActivationParent(Window* window) override; + void ActivateNextWindow() override; + void SetUnderlaySurfaceOffsetAndExtendedHitArea( + Window* window, + const gfx::Vector2d& offset, + const gfx::Insets& hit_area) override; + + // The one int in |cursor_location_mapping_|. When we read from this + // location, we must always read from it atomically. + base::subtle::Atomic32* cursor_location_memory() { + return reinterpret_cast<base::subtle::Atomic32*>( + cursor_location_mapping_.get()); + } + + // This is set once and only once when we get OnEmbed(). It gives the unique + // id for this client. + ClientSpecificId client_id_; + + // Id assigned to the next window created. + ClientSpecificId next_window_id_; + + // Id used for the next change id supplied to the server. + uint32_t next_change_id_; + InFlightMap in_flight_map_; + + WindowTreeClientDelegate* delegate_; + + WindowManagerDelegate* window_manager_delegate_; + + std::set<Window*> roots_; + + IdToWindowMap windows_; + std::map<ClientSpecificId, std::set<Window*>> embedded_windows_; + + Window* capture_window_; + + Window* focused_window_; + + mojo::Binding<mojom::WindowTreeClient> binding_; + mojom::WindowTreePtr tree_ptr_; + // Typically this is the value contained in |tree_ptr_|, but tests may + // directly set this. + mojom::WindowTree* tree_; + + bool delete_on_no_roots_; + + bool in_destructor_; + + // A mapping to shared memory that is one 32 bit integer long. The window + // server uses this to let us synchronously read the cursor location. + mojo::ScopedSharedBufferMapping cursor_location_mapping_; + + base::ObserverList<WindowTreeClientObserver> observers_; + + std::unique_ptr<mojo::AssociatedBinding<mojom::WindowManager>> + window_manager_internal_; + mojom::WindowManagerClientAssociatedPtr window_manager_internal_client_; + + bool has_event_observer_ = false; + + // Monotonically increasing ID for event observers. + uint32_t event_observer_id_ = 0u; + + base::WeakPtrFactory<WindowTreeClient> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeClient); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_H_ diff --git a/chromium/components/mus/public/cpp/window_tree_client_delegate.h b/chromium/components/mus/public/cpp/window_tree_client_delegate.h new file mode 100644 index 00000000000..41f09dfdbf0 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_tree_client_delegate.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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_DELEGATE_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_DELEGATE_H_ + +#include <string> + +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "services/shell/public/interfaces/interface_provider.mojom.h" + +namespace ui { +class Event; +} + +namespace mus { + +class Window; +class WindowTreeClient; + +// Interface implemented by an application using mus. +class WindowTreeClientDelegate { + public: + // Called when the application implementing this interface is embedded at + // |root|. + // NOTE: this is only invoked if the WindowTreeClient is created with an + // InterfaceRequest. + virtual void OnEmbed(Window* root) = 0; + + // Sent when another app is embedded in |root| (one of the roots of the + // connection). Afer this call |root| is deleted. If |root| is the only root + // and the connection is configured to delete when there are no roots (the + // default), then after |root| is destroyed the connection is destroyed as + // well. + virtual void OnUnembed(Window* root); + + // Called from the destructor of WindowTreeClient after all the Windows have + // been destroyed. |client| is no longer valid after this call. + virtual void OnWindowTreeClientDestroyed(WindowTreeClient* client) = 0; + + // Called when the WindowTreeClient receives an input event observed via + // SetEventObserver(). |target| may be null for events that were sent to + // windows owned by other processes. + virtual void OnEventObserved(const ui::Event& event, Window* target) = 0; + + protected: + virtual ~WindowTreeClientDelegate() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_DELEGATE_H_ diff --git a/chromium/components/mus/public/cpp/window_tree_client_observer.h b/chromium/components/mus/public/cpp/window_tree_client_observer.h new file mode 100644 index 00000000000..394a5cea1e1 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_tree_client_observer.h @@ -0,0 +1,27 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_OBSERVER_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_OBSERVER_H_ + +namespace mus { + +class Window; +class WindowTreeClient; + +class WindowTreeClientObserver { + public: + virtual void OnWindowTreeFocusChanged(Window* gained_focus, + Window* lost_focus) {} + + // Called right before the client is destroyed. + virtual void OnWillDestroyClient(WindowTreeClient* client) {} + + protected: + virtual ~WindowTreeClientObserver() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_CLIENT_OBSERVER_H_ diff --git a/chromium/components/mus/public/cpp/window_tree_host_factory.h b/chromium/components/mus/public/cpp/window_tree_host_factory.h new file mode 100644 index 00000000000..75661af89d6 --- /dev/null +++ b/chromium/components/mus/public/cpp/window_tree_host_factory.h @@ -0,0 +1,37 @@ +// 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 COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_HOST_FACTORY_H_ +#define COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_HOST_FACTORY_H_ + +#include <memory> + +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace shell { +class Connector; +} + +namespace mus { + +class WindowManagerDelegate; +class WindowTreeClientDelegate; + +// The following create a new window tree host. Supply a |factory| if you have +// already connected to mus, otherwise supply |shell|, which contacts mus and +// obtains a WindowTreeHostFactory. +void CreateWindowTreeHost(mojom::WindowTreeHostFactory* factory, + WindowTreeClientDelegate* delegate, + mojom::WindowTreeHostPtr* host, + WindowManagerDelegate* window_manager_delegate); +void CreateWindowTreeHost(shell::Connector* connector, + WindowTreeClientDelegate* delegate, + mojom::WindowTreeHostPtr* host, + WindowManagerDelegate* window_manager_delegate); + +} // namespace mus + +#endif // COMPONENTS_MUS_PUBLIC_CPP_WINDOW_TREE_HOST_FACTORY_H_ diff --git a/chromium/components/mus/public/interfaces/OWNERS b/chromium/components/mus/public/interfaces/OWNERS new file mode 100644 index 00000000000..08850f42120 --- /dev/null +++ b/chromium/components/mus/public/interfaces/OWNERS @@ -0,0 +1,2 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/mus/public/interfaces/gpu/OWNERS b/chromium/components/mus/public/interfaces/gpu/OWNERS new file mode 100644 index 00000000000..08850f42120 --- /dev/null +++ b/chromium/components/mus/public/interfaces/gpu/OWNERS @@ -0,0 +1,2 @@ +per-file *.mojom=set noparent +per-file *.mojom=file://ipc/SECURITY_OWNERS diff --git a/chromium/components/mus/surfaces/DEPS b/chromium/components/mus/surfaces/DEPS new file mode 100644 index 00000000000..e21114ea1d7 --- /dev/null +++ b/chromium/components/mus/surfaces/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + "+cc", + "+components/display_compositor", + "+components/gpu", + "+gpu", + "+services/shell", + "+mojo/common", + "+mojo/converters", + "+mojo/public", +] diff --git a/chromium/components/mus/surfaces/OWNERS b/chromium/components/mus/surfaces/OWNERS new file mode 100644 index 00000000000..1f1af6bcc54 --- /dev/null +++ b/chromium/components/mus/surfaces/OWNERS @@ -0,0 +1,2 @@ +fsamuel@chromium.org +rjkroege@chromium.org diff --git a/chromium/components/mus/surfaces/direct_output_surface.cc b/chromium/components/mus/surfaces/direct_output_surface.cc new file mode 100644 index 00000000000..7c7bc55f4c9 --- /dev/null +++ b/chromium/components/mus/surfaces/direct_output_surface.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 "components/mus/surfaces/direct_output_surface.h" + +#include <stdint.h> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "cc/output/compositor_frame.h" +#include "cc/output/context_provider.h" +#include "cc/output/output_surface_client.h" +#include "cc/scheduler/begin_frame_source.h" +#include "gpu/command_buffer/client/context_support.h" +#include "gpu/command_buffer/client/gles2_interface.h" + +namespace mus { + +DirectOutputSurface::DirectOutputSurface( + scoped_refptr<SurfacesContextProvider> context_provider, + cc::SyntheticBeginFrameSource* synthetic_begin_frame_source) + : cc::OutputSurface(context_provider, nullptr, nullptr), + synthetic_begin_frame_source_(synthetic_begin_frame_source), + weak_ptr_factory_(this) { + context_provider->SetDelegate(this); +} + +DirectOutputSurface::~DirectOutputSurface() = default; + +bool DirectOutputSurface::BindToClient(cc::OutputSurfaceClient* client) { + if (!cc::OutputSurface::BindToClient(client)) + return false; + + if (capabilities_.uses_default_gl_framebuffer) { + capabilities_.flipped_output_surface = + context_provider()->ContextCapabilities().flips_vertically; + } + return true; +} + +void DirectOutputSurface::OnVSyncParametersUpdated( + const base::TimeTicks& timebase, + const base::TimeDelta& interval) { + // TODO(brianderson): We should not be receiving 0 intervals. + synthetic_begin_frame_source_->OnUpdateVSyncParameters( + timebase, + interval.is_zero() ? cc::BeginFrameArgs::DefaultInterval() : interval); +} + +void DirectOutputSurface::SwapBuffers(cc::CompositorFrame frame) { + DCHECK(context_provider_); + 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); + } + + gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); + const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM(); + gl->ShallowFlushCHROMIUM(); + + gpu::SyncToken sync_token; + gl->GenUnverifiedSyncTokenCHROMIUM(fence_sync, sync_token.GetData()); + + context_provider_->ContextSupport()->SignalSyncToken( + sync_token, base::Bind(&OutputSurface::OnSwapBuffersComplete, + weak_ptr_factory_.GetWeakPtr())); + client_->DidSwapBuffers(); +} + +uint32_t DirectOutputSurface::GetFramebufferCopyTextureFormat() { + // TODO(danakj): What attributes are used for the default framebuffer here? + // Can it have alpha? SurfacesContextProvider doesn't take any attributes. + return GL_RGB; +} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/direct_output_surface.h b/chromium/components/mus/surfaces/direct_output_surface.h new file mode 100644 index 00000000000..6e08485cfbb --- /dev/null +++ b/chromium/components/mus/surfaces/direct_output_surface.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 COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_H_ +#define COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_H_ + +#include <memory> + +#include "cc/output/output_surface.h" +#include "components/mus/surfaces/surfaces_context_provider.h" +#include "components/mus/surfaces/surfaces_context_provider_delegate.h" + +namespace cc { +class CompositorFrame; +class SyntheticBeginFrameSource; +} + +namespace mus { + +// An OutputSurface implementation that directly draws and +// swaps to an actual GL surface. +class DirectOutputSurface : public cc::OutputSurface, + public SurfacesContextProviderDelegate { + public: + explicit DirectOutputSurface( + scoped_refptr<SurfacesContextProvider> context_provider, + cc::SyntheticBeginFrameSource* synthetic_begin_frame_source); + ~DirectOutputSurface() override; + + // cc::OutputSurface implementation + bool BindToClient(cc::OutputSurfaceClient* client) override; + void SwapBuffers(cc::CompositorFrame frame) override; + uint32_t GetFramebufferCopyTextureFormat() override; + + // SurfacesContextProviderDelegate implementation + void OnVSyncParametersUpdated(const base::TimeTicks& timebase, + const base::TimeDelta& interval) override; + + private: + cc::SyntheticBeginFrameSource* const synthetic_begin_frame_source_; + base::WeakPtrFactory<DirectOutputSurface> weak_ptr_factory_; +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_H_ diff --git a/chromium/components/mus/surfaces/direct_output_surface_ozone.cc b/chromium/components/mus/surfaces/direct_output_surface_ozone.cc new file mode 100644 index 00000000000..5148336078a --- /dev/null +++ b/chromium/components/mus/surfaces/direct_output_surface_ozone.cc @@ -0,0 +1,166 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/surfaces/direct_output_surface_ozone.h" + +#include <utility> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "cc/output/compositor_frame.h" +#include "cc/output/context_provider.h" +#include "cc/output/output_surface_client.h" +#include "cc/scheduler/begin_frame_source.h" +#include "components/display_compositor/buffer_queue.h" +#include "components/mus/common/gpu_service.h" +#include "components/mus/common/mojo_gpu_memory_buffer_manager.h" +#include "components/mus/gpu/mus_gpu_memory_buffer_manager.h" +#include "components/mus/surfaces/surfaces_context_provider.h" +#include "gpu/command_buffer/client/context_support.h" +#include "gpu/command_buffer/client/gles2_interface.h" + +using display_compositor::BufferQueue; + +namespace mus { + +DirectOutputSurfaceOzone::DirectOutputSurfaceOzone( + scoped_refptr<SurfacesContextProvider> context_provider, + gfx::AcceleratedWidget widget, + cc::SyntheticBeginFrameSource* synthetic_begin_frame_source, + uint32_t target, + uint32_t internalformat) + : cc::OutputSurface(context_provider, nullptr, nullptr), + gl_helper_(context_provider->ContextGL(), + context_provider->ContextSupport()), + synthetic_begin_frame_source_(synthetic_begin_frame_source), + weak_ptr_factory_(this) { + if (!GpuService::UseChromeGpuCommandBuffer()) { + ozone_gpu_memory_buffer_manager_.reset(new OzoneGpuMemoryBufferManager()); + buffer_queue_.reset(new BufferQueue( + context_provider->ContextGL(), target, internalformat, &gl_helper_, + ozone_gpu_memory_buffer_manager_.get(), widget)); + } else { + buffer_queue_.reset(new BufferQueue( + context_provider->ContextGL(), target, internalformat, &gl_helper_, + MusGpuMemoryBufferManager::current(), widget)); + } + + capabilities_.uses_default_gl_framebuffer = false; + capabilities_.flipped_output_surface = true; + // Set |max_frames_pending| to 2 for surfaceless, which aligns scheduling + // more closely with the previous surfaced behavior. + // With a surface, swap buffer ack used to return early, before actually + // presenting the back buffer, enabling the browser compositor to run ahead. + // Surfaceless implementation acks at the time of actual buffer swap, which + // shifts the start of the new frame forward relative to the old + // implementation. + capabilities_.max_frames_pending = 2; + + buffer_queue_->Initialize(); + + context_provider->SetSwapBuffersCompletionCallback( + base::Bind(&DirectOutputSurfaceOzone::OnGpuSwapBuffersCompleted, + base::Unretained(this))); +} + +DirectOutputSurfaceOzone::~DirectOutputSurfaceOzone() { + // TODO(rjkroege): Support cleanup. +} + +bool DirectOutputSurfaceOzone::IsDisplayedAsOverlayPlane() const { + // TODO(rjkroege): implement remaining overlay functionality. + return true; +} + +unsigned DirectOutputSurfaceOzone::GetOverlayTextureId() const { + DCHECK(buffer_queue_); + return buffer_queue_->current_texture_id(); +} + +void DirectOutputSurfaceOzone::SwapBuffers(cc::CompositorFrame frame) { + DCHECK(buffer_queue_); + DCHECK(frame.gl_frame_data); + + buffer_queue_->SwapBuffers(frame.gl_frame_data->sub_buffer_rect); + + // Code combining GpuBrowserCompositorOutputSurface + DirectOutputSurface + 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); + } + + gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); + const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM(); + gl->ShallowFlushCHROMIUM(); + + gpu::SyncToken sync_token; + gl->GenUnverifiedSyncTokenCHROMIUM(fence_sync, sync_token.GetData()); + + client_->DidSwapBuffers(); +} + +bool DirectOutputSurfaceOzone::BindToClient(cc::OutputSurfaceClient* client) { + if (!cc::OutputSurface::BindToClient(client)) + return false; + + if (capabilities_.uses_default_gl_framebuffer) { + capabilities_.flipped_output_surface = + context_provider()->ContextCapabilities().flips_vertically; + } + return true; +} + +void DirectOutputSurfaceOzone::OnUpdateVSyncParametersFromGpu( + base::TimeTicks timebase, + base::TimeDelta interval) { + DCHECK(HasClient()); + synthetic_begin_frame_source_->OnUpdateVSyncParameters(timebase, interval); +} + +void DirectOutputSurfaceOzone::OnGpuSwapBuffersCompleted( + gfx::SwapResult result) { + DCHECK(buffer_queue_); + bool force_swap = false; + if (result == gfx::SwapResult::SWAP_NAK_RECREATE_BUFFERS) { + // Even through the swap failed, this is a fixable error so we can pretend + // it succeeded to the rest of the system. + result = gfx::SwapResult::SWAP_ACK; + buffer_queue_->RecreateBuffers(); + force_swap = true; + } + + buffer_queue_->PageFlipComplete(); + OnSwapBuffersComplete(); + + if (force_swap) + client_->SetNeedsRedrawRect(gfx::Rect(SurfaceSize())); +} + +void DirectOutputSurfaceOzone::BindFramebuffer() { + DCHECK(buffer_queue_); + buffer_queue_->BindFramebuffer(); +} + +uint32_t DirectOutputSurfaceOzone::GetFramebufferCopyTextureFormat() { + return buffer_queue_->internal_format(); +} + +// We call this on every frame but changing the size once we've allocated +// backing NativePixmapOzone instances will cause a DCHECK because +// Chrome never Reshape(s) after the first one from (0,0). NB: this implies +// that screen size changes need to be plumbed differently. In particular, we +// must create the native window in the size that the hardware reports. +void DirectOutputSurfaceOzone::Reshape(const gfx::Size& size, + float scale_factor, + const gfx::ColorSpace& color_space, + bool alpha) { + OutputSurface::Reshape(size, scale_factor, color_space, alpha); + DCHECK(buffer_queue_); + buffer_queue_->Reshape(SurfaceSize(), scale_factor); +} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/direct_output_surface_ozone.h b/chromium/components/mus/surfaces/direct_output_surface_ozone.h new file mode 100644 index 00000000000..8f6f4e4bb76 --- /dev/null +++ b/chromium/components/mus/surfaces/direct_output_surface_ozone.h @@ -0,0 +1,84 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_OZONE_H_ +#define COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_OZONE_H_ + +#include <memory> + +#include "base/memory/weak_ptr.h" +#include "cc/output/context_provider.h" +#include "cc/output/output_surface.h" +#include "components/display_compositor/gl_helper.h" +#include "components/mus/surfaces/ozone_gpu_memory_buffer_manager.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/swap_result.h" +#include "ui/gl/gl_surface.h" + +namespace display_compositor { +class BufferQueue; +} + +namespace ui { +class LatencyInfo; +} // namespace ui + +namespace cc { +class CompositorFrame; +class SyntheticBeginFrameSource; +} // namespace cc + +namespace mus { + +class SurfacesContextProvider; + +// An OutputSurface implementation that directly draws and swap to a GL +// "surfaceless" surface (aka one backed by a buffer managed explicitly in +// mus/ozone. This class is adapted from +// GpuSurfacelessBrowserCompositorOutputSurface. +class DirectOutputSurfaceOzone : public cc::OutputSurface { + public: + DirectOutputSurfaceOzone( + scoped_refptr<SurfacesContextProvider> context_provider, + gfx::AcceleratedWidget widget, + cc::SyntheticBeginFrameSource* synthetic_begin_frame_source, + uint32_t target, + uint32_t internalformat); + + ~DirectOutputSurfaceOzone() override; + + // TODO(rjkroege): Implement the equivalent of Reflector. + + private: + // cc::OutputSurface implementation. + void SwapBuffers(cc::CompositorFrame frame) override; + void BindFramebuffer() override; + uint32_t GetFramebufferCopyTextureFormat() override; + void Reshape(const gfx::Size& size, + float scale_factor, + const gfx::ColorSpace& color_space, + bool alpha) override; + bool IsDisplayedAsOverlayPlane() const override; + unsigned GetOverlayTextureId() const override; + bool BindToClient(cc::OutputSurfaceClient* client) override; + + // Taken from BrowserCompositor specific API. + void OnUpdateVSyncParametersFromGpu(base::TimeTicks timebase, + base::TimeDelta interval); + + // Called when a swap completion is sent from the GPU process. + void OnGpuSwapBuffersCompleted(gfx::SwapResult result); + + display_compositor::GLHelper gl_helper_; + std::unique_ptr<OzoneGpuMemoryBufferManager> ozone_gpu_memory_buffer_manager_; + std::unique_ptr<display_compositor::BufferQueue> buffer_queue_; + cc::SyntheticBeginFrameSource* const synthetic_begin_frame_source_; + + base::WeakPtrFactory<DirectOutputSurfaceOzone> weak_ptr_factory_; +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACE_OZONE_H_ diff --git a/chromium/components/mus/surfaces/display_compositor.cc b/chromium/components/mus/surfaces/display_compositor.cc new file mode 100644 index 00000000000..8ffa0b86276 --- /dev/null +++ b/chromium/components/mus/surfaces/display_compositor.cc @@ -0,0 +1,124 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/surfaces/display_compositor.h" + +#include "cc/output/copy_output_request.h" +#include "cc/output/output_surface.h" +#include "cc/output/renderer_settings.h" +#include "cc/output/texture_mailbox_deleter.h" +#include "cc/scheduler/begin_frame_source.h" +#include "cc/scheduler/delay_based_time_source.h" +#include "cc/surfaces/display.h" +#include "cc/surfaces/display_scheduler.h" +#include "components/mus/surfaces/direct_output_surface.h" +#include "components/mus/surfaces/surfaces_context_provider.h" + +#if defined(USE_OZONE) +#include "components/mus/surfaces/direct_output_surface_ozone.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#endif + +namespace mus { + +DisplayCompositor::DisplayCompositor( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& gpu_state, + const scoped_refptr<SurfacesState>& surfaces_state) + : task_runner_(task_runner), + surfaces_state_(surfaces_state), + factory_(surfaces_state->manager(), this), + allocator_(surfaces_state->next_id_namespace()) { + allocator_.RegisterSurfaceIdNamespace(surfaces_state_->manager()); + surfaces_state_->manager()->RegisterSurfaceFactoryClient( + allocator_.id_namespace(), this); + + scoped_refptr<SurfacesContextProvider> surfaces_context_provider( + new SurfacesContextProvider(widget, gpu_state)); + // TODO(rjkroege): If there is something better to do than CHECK, add it. + CHECK(surfaces_context_provider->BindToCurrentThread()); + + std::unique_ptr<cc::SyntheticBeginFrameSource> synthetic_begin_frame_source( + new cc::DelayBasedBeginFrameSource( + base::MakeUnique<cc::DelayBasedTimeSource>(task_runner_.get()))); + + std::unique_ptr<cc::OutputSurface> display_output_surface; + if (surfaces_context_provider->ContextCapabilities().surfaceless) { +#if defined(USE_OZONE) + display_output_surface = base::WrapUnique(new DirectOutputSurfaceOzone( + surfaces_context_provider, widget, synthetic_begin_frame_source.get(), + GL_TEXTURE_2D, GL_RGB)); +#else + NOTREACHED(); +#endif + } else { + display_output_surface = base::WrapUnique(new DirectOutputSurface( + surfaces_context_provider, synthetic_begin_frame_source.get())); + } + + int max_frames_pending = + display_output_surface->capabilities().max_frames_pending; + DCHECK_GT(max_frames_pending, 0); + + std::unique_ptr<cc::DisplayScheduler> scheduler( + new cc::DisplayScheduler(synthetic_begin_frame_source.get(), + task_runner_.get(), max_frames_pending)); + + display_.reset(new cc::Display( + surfaces_state_->manager(), nullptr /* bitmap_manager */, + nullptr /* gpu_memory_buffer_manager */, cc::RendererSettings(), + allocator_.id_namespace(), std::move(synthetic_begin_frame_source), + std::move(display_output_surface), std::move(scheduler), + base::MakeUnique<cc::TextureMailboxDeleter>(task_runner_.get()))); + display_->Initialize(this); +} + +DisplayCompositor::~DisplayCompositor() { + surfaces_state_->manager()->UnregisterSurfaceFactoryClient( + allocator_.id_namespace()); +} + +void DisplayCompositor::SubmitCompositorFrame( + cc::CompositorFrame frame, + const base::Callback<void(cc::SurfaceDrawStatus)>& callback) { + gfx::Size frame_size = + frame.delegated_frame_data->render_pass_list.back()->output_rect.size(); + if (frame_size.IsEmpty() || frame_size != display_size_) { + if (!surface_id_.is_null()) + factory_.Destroy(surface_id_); + surface_id_ = allocator_.GenerateId(); + factory_.Create(surface_id_); + display_size_ = frame_size; + display_->Resize(display_size_); + } + display_->SetSurfaceId(surface_id_, frame.metadata.device_scale_factor); + factory_.SubmitCompositorFrame(surface_id_, std::move(frame), callback); +} + +void DisplayCompositor::RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request) { + factory_.RequestCopyOfSurface(surface_id_, std::move(output_request)); +} + +void DisplayCompositor::ReturnResources( + const cc::ReturnedResourceArray& resources) { + // TODO(fsamuel): Implement this. +} + +void DisplayCompositor::SetBeginFrameSource( + cc::BeginFrameSource* begin_frame_source) { + // TODO(fsamuel): Implement this. +} + +void DisplayCompositor::DisplayOutputSurfaceLost() { + // TODO(fsamuel): This looks like it would crash if a frame was in flight and + // will be submitted. + display_.reset(); +} + +void DisplayCompositor::DisplaySetMemoryPolicy( + const cc::ManagedMemoryPolicy& policy) {} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/display_compositor.h b/chromium/components/mus/surfaces/display_compositor.h new file mode 100644 index 00000000000..27d1eebf3cf --- /dev/null +++ b/chromium/components/mus/surfaces/display_compositor.h @@ -0,0 +1,80 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_SURFACES_DISPLAY_COMPOSITOR_H_ +#define COMPONENTS_MUS_SURFACES_DISPLAY_COMPOSITOR_H_ + +#include "cc/surfaces/display_client.h" +#include "cc/surfaces/surface.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_factory_client.h" +#include "cc/surfaces/surface_id_allocator.h" +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "ui/gfx/native_widget_types.h" + +namespace cc { +class Display; +} + +namespace mus { + +// TODO(fsamuel): This should become a mojo interface for the mus-gpu split. +// TODO(fsamuel): This should not be a SurfaceFactoryClient. +// The DisplayCompositor receives CompositorFrames from all sources, +// creates a top-level CompositorFrame once per tick, and generates graphical +// output. +class DisplayCompositor : public cc::SurfaceFactoryClient, + public cc::DisplayClient { + public: + DisplayCompositor(scoped_refptr<base::SingleThreadTaskRunner> task_runner, + gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& gpu_state, + const scoped_refptr<SurfacesState>& surfaces_state); + ~DisplayCompositor() override; + + // DisplayCompositor embedders submit a CompositorFrame when content on the + // display should be changed. A well-behaving embedder should only submit + // a CompositorFrame once per BeginFrame tick. The callback is called the + // first time this frame is used to draw, or if the frame is discarded. + void SubmitCompositorFrame( + cc::CompositorFrame frame, + const base::Callback<void(cc::SurfaceDrawStatus)>& callback); + + // TODO(fsamuel): This is used for surface hittesting and should not be + // exposed outside of DisplayCompositor. + const cc::SurfaceId& surface_id() const { return surface_id_; } + + // This requests the display CompositorFrame be rendered and given to the + // callback within CopyOutputRequest. + void RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request); + + // TODO(fsamuel): Invent an async way to create a SurfaceNamespace + // A SurfaceNamespace can create CompositorFrameSinks where the client can + // make up the ID. + + private: + // SurfaceFactoryClient implementation. + void ReturnResources(const cc::ReturnedResourceArray& resources) override; + void SetBeginFrameSource(cc::BeginFrameSource* begin_frame_source) override; + + // DisplayClient implementation. + void DisplayOutputSurfaceLost() override; + void DisplaySetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) override; + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<SurfacesState> surfaces_state_; + cc::SurfaceFactory factory_; + cc::SurfaceIdAllocator allocator_; + cc::SurfaceId surface_id_; + + gfx::Size display_size_; + std::unique_ptr<cc::Display> display_; + DISALLOW_COPY_AND_ASSIGN(DisplayCompositor); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_DISPLAY_COMPOSITOR_H_ diff --git a/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.cc b/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.cc new file mode 100644 index 00000000000..b14cd1394fe --- /dev/null +++ b/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.cc @@ -0,0 +1,49 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/surfaces/ozone_gpu_memory_buffer_manager.h" + +#include "components/mus/gles2/ozone_gpu_memory_buffer.h" +#include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "ui/gfx/buffer_types.h" + +namespace mus { + +OzoneGpuMemoryBufferManager::OzoneGpuMemoryBufferManager() {} + +OzoneGpuMemoryBufferManager::~OzoneGpuMemoryBufferManager() {} + +std::unique_ptr<gfx::GpuMemoryBuffer> +OzoneGpuMemoryBufferManager::AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) { + return OzoneGpuMemoryBuffer::CreateOzoneGpuMemoryBuffer( + size, format, gfx::BufferUsage::SCANOUT, surface_handle); +} + +std::unique_ptr<gfx::GpuMemoryBuffer> +OzoneGpuMemoryBufferManager::CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) { + NOTIMPLEMENTED(); + return nullptr; +} + +gfx::GpuMemoryBuffer* +OzoneGpuMemoryBufferManager::GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) { + NOTIMPLEMENTED(); + return nullptr; +} + +void OzoneGpuMemoryBufferManager::SetDestructionSyncToken( + gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) { + NOTIMPLEMENTED(); +} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.h b/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.h new file mode 100644 index 00000000000..d6cf15f55fe --- /dev/null +++ b/chromium/components/mus/surfaces/ozone_gpu_memory_buffer_manager.h @@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACES_OZONE_H_ +#define COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACES_OZONE_H_ + +#include "base/macros.h" +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" + +namespace mus { + +class OzoneGpuMemoryBufferManager : public gpu::GpuMemoryBufferManager { + public: + OzoneGpuMemoryBufferManager(); + ~OzoneGpuMemoryBufferManager() override; + + // gpu::GpuMemoryBufferManager: + std::unique_ptr<gfx::GpuMemoryBuffer> AllocateGpuMemoryBuffer( + const gfx::Size& size, + gfx::BufferFormat format, + gfx::BufferUsage usage, + gpu::SurfaceHandle surface_handle) override; + std::unique_ptr<gfx::GpuMemoryBuffer> CreateGpuMemoryBufferFromHandle( + const gfx::GpuMemoryBufferHandle& handle, + const gfx::Size& size, + gfx::BufferFormat format) override; + gfx::GpuMemoryBuffer* GpuMemoryBufferFromClientBuffer( + ClientBuffer buffer) override; + void SetDestructionSyncToken(gfx::GpuMemoryBuffer* buffer, + const gpu::SyncToken& sync_token) override; + + private: + DISALLOW_COPY_AND_ASSIGN(OzoneGpuMemoryBufferManager); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_DIRECT_OUTPUT_SURFACES_OZONE_H_ diff --git a/chromium/components/mus/surfaces/surfaces_context_provider.cc b/chromium/components/mus/surfaces/surfaces_context_provider.cc new file mode 100644 index 00000000000..291a4402eb5 --- /dev/null +++ b/chromium/components/mus/surfaces/surfaces_context_provider.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 "components/mus/surfaces/surfaces_context_provider.h" + +#include <stddef.h> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "components/mus/common/switches.h" +#include "components/mus/gles2/command_buffer_driver.h" +#include "components/mus/gles2/command_buffer_impl.h" +#include "components/mus/gles2/command_buffer_local.h" +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/gpu/gpu_service_mus.h" +#include "components/mus/surfaces/surfaces_context_provider_delegate.h" +#include "gpu/command_buffer/client/gles2_cmd_helper.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "gpu/command_buffer/client/shared_memory_limits.h" +#include "gpu/command_buffer/client/transfer_buffer.h" +#include "gpu/ipc/client/command_buffer_proxy_impl.h" +#include "ui/gl/gpu_preference.h" + +namespace mus { + +SurfacesContextProvider::SurfacesContextProvider( + gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& state) + : use_chrome_gpu_command_buffer_(false), + delegate_(nullptr), + widget_(widget), + command_buffer_local_(nullptr) { +// TODO(penghuang): Kludge: Use mojo command buffer when running on Windows +// since Chrome command buffer breaks unit tests +#if defined(OS_WIN) + use_chrome_gpu_command_buffer_ = false; +#else + use_chrome_gpu_command_buffer_ = + !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kUseMojoGpuCommandBufferInMus); +#endif + if (!use_chrome_gpu_command_buffer_) { + command_buffer_local_ = new CommandBufferLocal(this, widget_, state); + } else { + GpuServiceMus* service = GpuServiceMus::GetInstance(); + gpu::CommandBufferProxyImpl* shared_command_buffer = nullptr; + gpu::GpuStreamId stream_id = gpu::GpuStreamId::GPU_STREAM_DEFAULT; + gpu::GpuStreamPriority stream_priority = gpu::GpuStreamPriority::NORMAL; + gpu::gles2::ContextCreationAttribHelper attributes; + attributes.alpha_size = -1; + attributes.depth_size = 0; + attributes.stencil_size = 0; + attributes.samples = 0; + attributes.sample_buffers = 0; + attributes.bind_generates_resource = false; + attributes.lose_context_when_out_of_memory = true; + GURL active_url; + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + base::ThreadTaskRunnerHandle::Get(); + command_buffer_proxy_impl_ = gpu::CommandBufferProxyImpl::Create( + service->gpu_channel_local(), widget, shared_command_buffer, stream_id, + stream_priority, attributes, active_url, task_runner); + command_buffer_proxy_impl_->SetSwapBuffersCompletionCallback( + base::Bind(&SurfacesContextProvider::OnGpuSwapBuffersCompleted, + base::Unretained(this))); + command_buffer_proxy_impl_->SetUpdateVSyncParametersCallback( + base::Bind(&SurfacesContextProvider::OnUpdateVSyncParameters, + base::Unretained(this))); + } +} + +void SurfacesContextProvider::SetDelegate( + SurfacesContextProviderDelegate* delegate) { + DCHECK(!delegate_); + delegate_ = delegate; +} + +// This routine needs to be safe to call more than once. +// This is called when we have an accelerated widget. +bool SurfacesContextProvider::BindToCurrentThread() { + if (implementation_) + return true; + + // SurfacesContextProvider should always live on the same thread as the + // Window Manager. + DCHECK(CalledOnValidThread()); + gpu::GpuControl* gpu_control = nullptr; + gpu::CommandBuffer* command_buffer = nullptr; + if (!use_chrome_gpu_command_buffer_) { + if (!command_buffer_local_->Initialize()) + return false; + gpu_control = command_buffer_local_; + command_buffer = command_buffer_local_; + } else { + if (!command_buffer_proxy_impl_) + return false; + gpu_control = command_buffer_proxy_impl_.get(); + command_buffer = command_buffer_proxy_impl_.get(); + } + + gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer)); + constexpr gpu::SharedMemoryLimits default_limits; + if (!gles2_helper_->Initialize(default_limits.command_buffer_size)) + return false; + gles2_helper_->SetAutomaticFlushes(false); + transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get())); + capabilities_ = gpu_control->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, gpu_control)); + return implementation_->Initialize( + default_limits.start_transfer_buffer_size, + default_limits.min_transfer_buffer_size, + default_limits.max_transfer_buffer_size, + default_limits.mapped_memory_reclaim_limit); +} + +gpu::gles2::GLES2Interface* SurfacesContextProvider::ContextGL() { + DCHECK(implementation_); + return implementation_.get(); +} + +gpu::ContextSupport* SurfacesContextProvider::ContextSupport() { + return implementation_.get(); +} + +class GrContext* SurfacesContextProvider::GrContext() { + return NULL; +} + +void SurfacesContextProvider::InvalidateGrContext(uint32_t state) {} + +gpu::Capabilities SurfacesContextProvider::ContextCapabilities() { + return capabilities_; +} + +base::Lock* SurfacesContextProvider::GetLock() { + // This context provider is not used on multiple threads. + NOTREACHED(); + return nullptr; +} + +void SurfacesContextProvider::SetLostContextCallback( + const LostContextCallback& lost_context_callback) { + implementation_->SetLostContextCallback(lost_context_callback); +} + +SurfacesContextProvider::~SurfacesContextProvider() { + implementation_->Flush(); + implementation_.reset(); + transfer_buffer_.reset(); + gles2_helper_.reset(); + command_buffer_proxy_impl_.reset(); + if (command_buffer_local_) { + command_buffer_local_->Destroy(); + command_buffer_local_ = nullptr; + } +} + +void SurfacesContextProvider::UpdateVSyncParameters( + const base::TimeTicks& timebase, + const base::TimeDelta& interval) { + if (delegate_) + delegate_->OnVSyncParametersUpdated(timebase, interval); +} + +void SurfacesContextProvider::GpuCompletedSwapBuffers(gfx::SwapResult result) { + if (!swap_buffers_completion_callback_.is_null()) { + swap_buffers_completion_callback_.Run(result); + } +} + +void SurfacesContextProvider::OnGpuSwapBuffersCompleted( + const std::vector<ui::LatencyInfo>& latency_info, + gfx::SwapResult result, + const gpu::GpuProcessHostedCALayerTreeParamsMac* params_mac) { + if (!swap_buffers_completion_callback_.is_null()) { + swap_buffers_completion_callback_.Run(result); + } +} + +void SurfacesContextProvider::OnUpdateVSyncParameters( + base::TimeTicks timebase, + base::TimeDelta interval) { + if (delegate_) + delegate_->OnVSyncParametersUpdated(timebase, interval); +} + +void SurfacesContextProvider::SetSwapBuffersCompletionCallback( + gl::GLSurface::SwapCompletionCallback callback) { + swap_buffers_completion_callback_ = callback; +} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/surfaces_context_provider.h b/chromium/components/mus/surfaces/surfaces_context_provider.h new file mode 100644 index 00000000000..8dab0bcb9d6 --- /dev/null +++ b/chromium/components/mus/surfaces/surfaces_context_provider.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 COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_H_ +#define COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" +#include "base/threading/non_thread_safe.h" +#include "cc/output/context_provider.h" +#include "components/mus/gles2/command_buffer_local_client.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gl/gl_surface.h" + +namespace gpu { + +class CommandBufferProxyImpl; +struct GpuProcessHostedCALayerTreeParamsMac; +class TransferBuffer; + +namespace gles2 { +class GLES2CmdHelper; +class GLES2Implementation; +} + +} // namespace gpu + +namespace ui { +class LatencyInfo; +} + +namespace mus { + +class CommandBufferDriver; +class CommandBufferImpl; +class CommandBufferLocal; +class GpuState; +class SurfacesContextProviderDelegate; + +class SurfacesContextProvider : public cc::ContextProvider, + public CommandBufferLocalClient, + public base::NonThreadSafe { + public: + SurfacesContextProvider(gfx::AcceleratedWidget widget, + const scoped_refptr<GpuState>& state); + + void SetDelegate(SurfacesContextProviderDelegate* delegate); + + // cc::ContextProvider implementation. + bool BindToCurrentThread() override; + gpu::gles2::GLES2Interface* ContextGL() override; + gpu::ContextSupport* ContextSupport() override; + class GrContext* GrContext() override; + void InvalidateGrContext(uint32_t state) override; + gpu::Capabilities ContextCapabilities() override; + void DeleteCachedResources() override {} + void SetLostContextCallback( + const LostContextCallback& lost_context_callback) override; + base::Lock* GetLock() override; + + // SurfacesContextProvider API. + void SetSwapBuffersCompletionCallback( + gl::GLSurface::SwapCompletionCallback callback); + + protected: + friend class base::RefCountedThreadSafe<SurfacesContextProvider>; + ~SurfacesContextProvider() override; + + private: + // CommandBufferLocalClient: + void UpdateVSyncParameters(const base::TimeTicks& timebase, + const base::TimeDelta& interval) override; + void GpuCompletedSwapBuffers(gfx::SwapResult result) override; + + // Callbacks for CommandBufferProxyImpl: + void OnGpuSwapBuffersCompleted( + const std::vector<ui::LatencyInfo>& latency_info, + gfx::SwapResult result, + const gpu::GpuProcessHostedCALayerTreeParamsMac* params_mac); + void OnUpdateVSyncParameters(base::TimeTicks timebase, + base::TimeDelta interval); + + bool use_chrome_gpu_command_buffer_; + + // From GLES2Context: + // Initialized in BindToCurrentThread. + std::unique_ptr<gpu::gles2::GLES2CmdHelper> gles2_helper_; + std::unique_ptr<gpu::TransferBuffer> transfer_buffer_; + std::unique_ptr<gpu::gles2::GLES2Implementation> implementation_; + + gpu::Capabilities capabilities_; + LostContextCallback lost_context_callback_; + + SurfacesContextProviderDelegate* delegate_; + gfx::AcceleratedWidget widget_; + CommandBufferLocal* command_buffer_local_; + std::unique_ptr<gpu::CommandBufferProxyImpl> command_buffer_proxy_impl_; + gl::GLSurface::SwapCompletionCallback swap_buffers_completion_callback_; + + DISALLOW_COPY_AND_ASSIGN(SurfacesContextProvider); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_H_ diff --git a/chromium/components/mus/surfaces/surfaces_context_provider_delegate.h b/chromium/components/mus/surfaces/surfaces_context_provider_delegate.h new file mode 100644 index 00000000000..170190a923f --- /dev/null +++ b/chromium/components/mus/surfaces/surfaces_context_provider_delegate.h @@ -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. + +#ifndef COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_DELEGATE_H_ +#define COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_DELEGATE_H_ + +#include <stdint.h> + +namespace base { +class TimeDelta; +class TimeTicks; +} + +namespace mus { + +class SurfacesContextProviderDelegate { + public: + virtual void OnVSyncParametersUpdated(const base::TimeTicks& timebase, + const base::TimeDelta& interval) = 0; + + protected: + virtual ~SurfacesContextProviderDelegate() {} +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_SURFACES_CONTEXT_PROVIDER_DELEGATE_H_ diff --git a/chromium/components/mus/surfaces/surfaces_state.cc b/chromium/components/mus/surfaces/surfaces_state.cc new file mode 100644 index 00000000000..e3dfeb8d85d --- /dev/null +++ b/chromium/components/mus/surfaces/surfaces_state.cc @@ -0,0 +1,13 @@ +// 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 "components/mus/surfaces/surfaces_state.h" + +namespace mus { + +SurfacesState::SurfacesState() : next_id_namespace_(1u) {} + +SurfacesState::~SurfacesState() {} + +} // namespace mus diff --git a/chromium/components/mus/surfaces/surfaces_state.h b/chromium/components/mus/surfaces/surfaces_state.h new file mode 100644 index 00000000000..daaf5587ab6 --- /dev/null +++ b/chromium/components/mus/surfaces/surfaces_state.h @@ -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. + +#ifndef COMPONENTS_MUS_SURFACES_SURFACES_STATE_H_ +#define COMPONENTS_MUS_SURFACES_SURFACES_STATE_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "cc/surfaces/surface_manager.h" + +namespace cc { +class SurfaceHittest; +class SurfaceManager; +} // namespace cc + +namespace mus { + +// The SurfacesState object is an object global to the Window Manager app that +// holds the SurfaceManager and allocates new Surfaces namespaces. +// This object lives on the main thread of the Window Manager. +// TODO(rjkroege, fsamuel): This object will need to change to support multiple +// displays. +class SurfacesState : public base::RefCounted<SurfacesState> { + public: + SurfacesState(); + + uint32_t next_id_namespace() { return next_id_namespace_++; } + + cc::SurfaceManager* manager() { return &manager_; } + + private: + friend class base::RefCounted<SurfacesState>; + ~SurfacesState(); + + // A Surface ID is an unsigned 64-bit int where the high 32-bits are generated + // by the Surfaces service, and the low 32-bits are generated by the process + // that requested the Surface. + uint32_t next_id_namespace_; + cc::SurfaceManager manager_; + + DISALLOW_COPY_AND_ASSIGN(SurfacesState); +}; + +} // namespace mus + +#endif // COMPONENTS_MUS_SURFACES_SURFACES_STATE_H_ diff --git a/chromium/components/mus/test_wm/manifest.json b/chromium/components/mus/test_wm/manifest.json new file mode 100644 index 00000000000..67f2fa96671 --- /dev/null +++ b/chromium/components/mus/test_wm/manifest.json @@ -0,0 +1,10 @@ +{ + "manifest_version": 1, + "name": "mojo:test_wm", + "display_name": "Test Window Manager", + "capabilities": { + "required": { + "mojo:mus": { "interfaces": [ "mus::mojom::WindowManagerWindowTreeFactory" ] } + } + } +} diff --git a/chromium/components/mus/test_wm/test_wm.cc b/chromium/components/mus/test_wm/test_wm.cc new file mode 100644 index 00000000000..39ad3759839 --- /dev/null +++ b/chromium/components/mus/test_wm/test_wm.cc @@ -0,0 +1,107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/public/cpp/window.h" +#include "components/mus/public/cpp/window_manager_delegate.h" +#include "components/mus/public/cpp/window_tree_client.h" +#include "components/mus/public/cpp/window_tree_client_delegate.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/shell/public/cpp/application_runner.h" +#include "services/shell/public/cpp/connector.h" +#include "services/shell/public/cpp/shell_client.h" +#include "ui/display/display.h" +#include "ui/display/mojo/display_type_converters.h" + +namespace mus { +namespace test { + +class TestWM : public shell::ShellClient, + public mus::WindowTreeClientDelegate, + public mus::WindowManagerDelegate { + public: + TestWM() {} + ~TestWM() override { delete window_tree_client_; } + + private: + // shell::ShellClient: + void Initialize(shell::Connector* connector, + const shell::Identity& identity, + uint32_t id) override { + window_tree_client_ = new mus::WindowTreeClient(this, this, nullptr); + window_tree_client_->ConnectAsWindowManager(connector); + } + bool AcceptConnection(shell::Connection* connection) override { + return true; + } + + // mus::WindowTreeClientDelegate: + void OnEmbed(mus::Window* root) override { + // WindowTreeClients configured as the window manager should never get + // OnEmbed(). + NOTREACHED(); + } + void OnWindowTreeClientDestroyed(mus::WindowTreeClient* client) override { + window_tree_client_ = nullptr; + } + void OnEventObserved(const ui::Event& event, mus::Window* target) override { + // Don't care. + } + + // mus::WindowManagerDelegate: + void SetWindowManagerClient(mus::WindowManagerClient* client) override { + window_manager_client_ = client; + } + bool OnWmSetBounds(mus::Window* window, gfx::Rect* bounds) override { + return true; + } + bool OnWmSetProperty( + mus::Window* window, + const std::string& name, + std::unique_ptr<std::vector<uint8_t>>* new_data) override { + return true; + } + mus::Window* OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) override { + mus::Window* window = root_->window_tree()->NewWindow(properties); + window->SetBounds(gfx::Rect(10, 10, 500, 500)); + root_->AddChild(window); + return window; + } + void OnWmClientJankinessChanged(const std::set<Window*>& client_windows, + bool janky) override { + // Don't care. + } + void OnWmNewDisplay(Window* window, + const display::Display& display) override { + // Only handles a single root. + DCHECK(!root_); + root_ = window; + DCHECK(window_manager_client_); + window_manager_client_->AddActivationParent(root_); + mus::mojom::FrameDecorationValuesPtr frame_decoration_values = + mus::mojom::FrameDecorationValues::New(); + frame_decoration_values->max_title_bar_button_width = 0; + window_manager_client_->SetFrameDecorationValues( + std::move(frame_decoration_values)); + } + void OnAccelerator(uint32_t id, const ui::Event& event) override { + // Don't care. + } + + mus::Window* root_ = nullptr; + mus::WindowManagerClient* window_manager_client_ = nullptr; + // See WindowTreeClient for details on ownership. + mus::WindowTreeClient* window_tree_client_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(TestWM); +}; + +} // namespace test +} // namespace mus + +MojoResult MojoMain(MojoHandle shell_handle) { + shell::ApplicationRunner runner(new mus::test::TestWM); + return runner.Run(shell_handle); +} diff --git a/chromium/components/mus/ws/accelerator.cc b/chromium/components/mus/ws/accelerator.cc new file mode 100644 index 00000000000..fd8cde39668 --- /dev/null +++ b/chromium/components/mus/ws/accelerator.cc @@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/accelerator.h" + +namespace mus { +namespace ws { + +Accelerator::Accelerator(uint32_t id, const mojom::EventMatcher& matcher) + : id_(id), + accelerator_phase_(matcher.accelerator_phase), + event_matcher_(matcher), + weak_factory_(this) {} + +Accelerator::~Accelerator() {} + +bool Accelerator::MatchesEvent(const ui::Event& event, + const ui::mojom::AcceleratorPhase phase) const { + if (accelerator_phase_ != phase) + return false; + if (!event_matcher_.MatchesEvent(event)) + return false; + return true; +} + +bool Accelerator::EqualEventMatcher(const Accelerator* other) const { + return accelerator_phase_ == other->accelerator_phase_ && + event_matcher_.Equals(other->event_matcher_); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/accelerator.h b/chromium/components/mus/ws/accelerator.h new file mode 100644 index 00000000000..2113ad1ec9b --- /dev/null +++ b/chromium/components/mus/ws/accelerator.h @@ -0,0 +1,57 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_ACCELERATOR_H_ +#define COMPONENTS_MUS_WS_ACCELERATOR_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "components/mus/ws/event_matcher.h" + +namespace ui { +class Event; +} + +namespace mus { +namespace ws { + +// An Accelerator encompasses an id defined by the client, along with a unique +// mojom::EventMatcher. See WindowManagerClient. +// +// This provides a WeakPtr, as the client might delete the accelerator between +// an event having been matched and the dispatch of the accelerator to the +// client. +class Accelerator { + public: + Accelerator(uint32_t id, const mojom::EventMatcher& matcher); + ~Accelerator(); + + // Returns true if |event| and |phase | matches the definition in the + // mojom::EventMatcher used for initialization. + bool MatchesEvent(const ui::Event& event, + const ui::mojom::AcceleratorPhase phase) const; + + // Returns true if |other| was created with an identical mojom::EventMatcher. + bool EqualEventMatcher(const Accelerator* other) const; + + base::WeakPtr<Accelerator> GetWeakPtr() { return weak_factory_.GetWeakPtr(); } + + uint32_t id() const { return id_; } + + private: + uint32_t id_; + ui::mojom::AcceleratorPhase accelerator_phase_; + EventMatcher event_matcher_; + base::WeakPtrFactory<Accelerator> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Accelerator); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_ACCELERATOR_H_ diff --git a/chromium/components/mus/ws/access_policy.h b/chromium/components/mus/ws/access_policy.h new file mode 100644 index 00000000000..2f0cb366cc5 --- /dev/null +++ b/chromium/components/mus/ws/access_policy.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_ACCESS_POLICY_H_ +#define COMPONENTS_MUS_WS_ACCESS_POLICY_H_ + +#include <stdint.h> + +#include "components/mus/public/interfaces/mus_constants.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/ids.h" + +namespace mus { +namespace ws { + +class AccessPolicyDelegate; +class ServerWindow; + +// AccessPolicy is used by WindowTree to determine what the WindowTree is +// allowed to do. +class AccessPolicy { + public: + virtual ~AccessPolicy() {} + + virtual void Init(ClientSpecificId client_id, + AccessPolicyDelegate* delegate) = 0; + + // Unless otherwise mentioned all arguments have been validated. That is the + // |window| arguments are non-null unless otherwise stated (eg CanSetWindow() + // is allowed to take a NULL window). + virtual bool CanRemoveWindowFromParent(const ServerWindow* window) const = 0; + virtual bool CanAddWindow(const ServerWindow* parent, + const ServerWindow* child) const = 0; + virtual bool CanAddTransientWindow(const ServerWindow* parent, + const ServerWindow* child) const = 0; + virtual bool CanRemoveTransientWindowFromParent( + const ServerWindow* window) const = 0; + virtual bool CanSetModal(const ServerWindow* window) const = 0; + virtual bool CanReorderWindow(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const = 0; + virtual bool CanDeleteWindow(const ServerWindow* window) const = 0; + virtual bool CanGetWindowTree(const ServerWindow* window) const = 0; + // Used when building a window tree (GetWindowTree()) to decide if we should + // descend into |window|. + virtual bool CanDescendIntoWindowForWindowTree( + const ServerWindow* window) const = 0; + virtual bool CanEmbed(const ServerWindow* window) const = 0; + virtual bool CanChangeWindowVisibility(const ServerWindow* window) const = 0; + virtual bool CanChangeWindowOpacity(const ServerWindow* window) const = 0; + virtual bool CanSetWindowSurface(const ServerWindow* window, + mojom::SurfaceType surface_type) const = 0; + virtual bool CanSetWindowBounds(const ServerWindow* window) const = 0; + virtual bool CanSetWindowProperties(const ServerWindow* window) const = 0; + virtual bool CanSetWindowTextInputState(const ServerWindow* window) const = 0; + virtual bool CanSetCapture(const ServerWindow* window) const = 0; + virtual bool CanSetFocus(const ServerWindow* window) const = 0; + virtual bool CanSetClientArea(const ServerWindow* window) const = 0; + virtual bool CanSetHitTestMask(const ServerWindow* window) const = 0; + // Used for all client controllable cursor properties; which cursor should be + // displayed, visibility, locking, etc. + virtual bool CanSetCursorProperties(const ServerWindow* window) const = 0; + + // Returns whether the client 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 windows. + virtual bool ShouldNotifyOnHierarchyChange( + const ServerWindow* window, + const ServerWindow** new_parent, + const ServerWindow** old_parent) const = 0; + virtual bool CanSetWindowManager() const = 0; + + // Returns the window to supply to the client when focus changes to |focused|. + virtual const ServerWindow* GetWindowForFocusChange( + const ServerWindow* focused) = 0; + + virtual bool IsValidIdForNewWindow(const ClientWindowId& id) const = 0; +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_ACCESS_POLICY_H_ diff --git a/chromium/components/mus/ws/access_policy_delegate.h b/chromium/components/mus/ws/access_policy_delegate.h new file mode 100644 index 00000000000..3e33de55c46 --- /dev/null +++ b/chromium/components/mus/ws/access_policy_delegate.h @@ -0,0 +1,41 @@ +// 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 COMPONENTS_MUS_WS_ACCESS_POLICY_DELEGATE_H_ +#define COMPONENTS_MUS_WS_ACCESS_POLICY_DELEGATE_H_ + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "components/mus/ws/ids.h" + +namespace mus { + +namespace ws { + +class ServerWindow; + +// Delegate used by the AccessPolicy implementations to get state. +class AccessPolicyDelegate { + public: + // Returns true if the tree has |window| as one of its roots. + virtual bool HasRootForAccessPolicy(const ServerWindow* window) const = 0; + + // Returns true if |window| has been exposed to the client. + virtual bool IsWindowKnownForAccessPolicy( + const ServerWindow* window) const = 0; + + // Returns true if Embed(window) has been invoked on |window|. + virtual bool IsWindowRootOfAnotherTreeForAccessPolicy( + const ServerWindow* window) const = 0; + + protected: + virtual ~AccessPolicyDelegate() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_ACCESS_POLICY_DELEGATE_H_ diff --git a/chromium/components/mus/ws/animation_runner.cc b/chromium/components/mus/ws/animation_runner.cc new file mode 100644 index 00000000000..080fb2c0f45 --- /dev/null +++ b/chromium/components/mus/ws/animation_runner.cc @@ -0,0 +1,163 @@ +// 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 "components/mus/ws/animation_runner.h" + +#include "base/memory/scoped_vector.h" +#include "components/mus/ws/animation_runner_observer.h" +#include "components/mus/ws/scheduled_animation_group.h" +#include "components/mus/ws/server_window.h" + +namespace mus { +namespace ws { +namespace { + +bool ConvertWindowAndAnimationPairsToScheduledAnimationGroups( + const std::vector<AnimationRunner::WindowAndAnimationPair>& pairs, + AnimationRunner::AnimationId id, + base::TimeTicks now, + std::vector<std::unique_ptr<ScheduledAnimationGroup>>* groups) { + for (const auto& window_animation_pair : pairs) { + DCHECK(window_animation_pair.first); + DCHECK(window_animation_pair.second); + std::unique_ptr<ScheduledAnimationGroup> group( + ScheduledAnimationGroup::Create(window_animation_pair.first, now, id, + *(window_animation_pair.second))); + if (!group.get()) + return false; + groups->push_back(std::move(group)); + } + 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<WindowAndAnimationPair>& pairs, + base::TimeTicks now) { + DCHECK_GE(now, last_tick_time_); + + const AnimationId animation_id = next_id_++; + std::vector<std::unique_ptr<ScheduledAnimationGroup>> groups; + if (!ConvertWindowAndAnimationPairsToScheduledAnimationGroups( + pairs, animation_id, now, &groups)) { + return 0; + } + + // Cancel any animations for the affected windows. If the cancelled animations + // also animated properties that are not animated by the new group - instantly + // set those to the target value. + for (auto& group : groups) { + ScheduledAnimationGroup* current_group = + window_to_animation_map_[group->window()].get(); + if (current_group) + current_group->SetValuesToTargetValuesForPropertiesNotIn(*group.get()); + + CancelAnimationForWindowImpl(group->window(), CANCEL_SOURCE_SCHEDULE); + } + + // Update internal maps + for (auto& group : groups) { + group->ObtainStartValues(); + ServerWindow* window = group->window(); + window_to_animation_map_[window] = std::move(group); + DCHECK(!id_to_windows_map_[animation_id].count(window)); + id_to_windows_map_[animation_id].insert(window); + } + + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationScheduled(animation_id)); + return animation_id; +} + +void AnimationRunner::CancelAnimation(AnimationId id) { + if (id_to_windows_map_.count(id) == 0) + return; + + std::set<ServerWindow*> windows(id_to_windows_map_[id]); + for (ServerWindow* window : windows) + CancelAnimationForWindow(window); +} + +void AnimationRunner::CancelAnimationForWindow(ServerWindow* window) { + CancelAnimationForWindowImpl(window, CANCEL_SOURCE_CANCEL); +} + +void AnimationRunner::Tick(base::TimeTicks time) { + DCHECK(time >= last_tick_time_); + last_tick_time_ = time; + if (window_to_animation_map_.empty()) + return; + + // The animation ids of any windows whose animation completes are added here. + // We notify after processing all windows 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 (WindowToAnimationMap::iterator i = window_to_animation_map_.begin(); + i != window_to_animation_map_.end();) { + bool animation_done = i->second->Tick(time); + if (animation_done) { + const AnimationId animation_id = i->second->id(); + ServerWindow* window = i->first; + ++i; + bool animation_completed = RemoveWindowFromMaps(window); + if (animation_completed) + animations_completed.insert(animation_id); + } else { + ++i; + } + } + for (const AnimationId& id : animations_completed) { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, OnAnimationDone(id)); + } +} + +void AnimationRunner::CancelAnimationForWindowImpl(ServerWindow* window, + CancelSource source) { + if (!window_to_animation_map_[window]) + return; + + const AnimationId animation_id = window_to_animation_map_[window]->id(); + if (RemoveWindowFromMaps(window)) { + // This was the last window 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::RemoveWindowFromMaps(ServerWindow* window) { + DCHECK(window_to_animation_map_[window]); + + const AnimationId animation_id = window_to_animation_map_[window]->id(); + window_to_animation_map_.erase(window); + + DCHECK(id_to_windows_map_.count(animation_id)); + id_to_windows_map_[animation_id].erase(window); + if (!id_to_windows_map_[animation_id].empty()) + return false; + + id_to_windows_map_.erase(animation_id); + return true; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/animation_runner.h b/chromium/components/mus/ws/animation_runner.h new file mode 100644 index 00000000000..5a7d80c4774 --- /dev/null +++ b/chromium/components/mus/ws/animation_runner.h @@ -0,0 +1,119 @@ +// 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 COMPONENTS_MUS_WS_ANIMATION_RUNNER_H_ +#define COMPONENTS_MUS_WS_ANIMATION_RUNNER_H_ + +#include <algorithm> +#include <map> +#include <memory> +#include <set> +#include <unordered_map> +#include <vector> + +#include "base/observer_list.h" +#include "base/time/time.h" + +namespace mus { +namespace mojom { +class AnimationGroup; +} + +namespace ws { + +class AnimationRunnerObserver; +class ScheduledAnimationGroup; +class ServerWindow; + +// AnimationRunner is responsible for maintaining and running a set of +// animations. The animations are represented as a set of AnimationGroups. New +// animations are scheduled by way of Schedule(). A |window| may only have one +// animation running at a time. Schedule()ing a new animation for a window +// already animating implicitly cancels the current animation for the window. +// Animations progress by way of the Tick() function. +class AnimationRunner { + public: + using AnimationId = uint32_t; + using WindowAndAnimationPair = + std::pair<ServerWindow*, const mojom::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 windows 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<WindowAndAnimationPair>& groups, + 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 |window|. Does nothing if there is no + // animation scheduled for |window|. This does not change |window|. That is, + // any in progress animations are stopped. + void CancelAnimationForWindow(ServerWindow* window); + + // Advance the animations updating values appropriately. + void Tick(base::TimeTicks time); + + // Returns true if there are animations currently scheduled. + bool HasAnimations() const { return !window_to_animation_map_.empty(); } + + // Returns true if the animation identified by |id| is valid and animating. + bool IsAnimating(AnimationId id) const { + return id_to_windows_map_.count(id) > 0; + } + + // Returns the windows that are currently animating for |id|. Returns an empty + // set if |id| does not identify a valid animation. + std::set<ServerWindow*> GetWindowsAnimating(AnimationId id) { + return IsAnimating(id) ? id_to_windows_map_.find(id)->second + : std::set<ServerWindow*>(); + } + + private: + enum CancelSource { + // Cancel is the result of scheduling another animation for the window. + CANCEL_SOURCE_SCHEDULE, + + // Cancel originates from an explicit call to cancel. + CANCEL_SOURCE_CANCEL, + }; + + using WindowToAnimationMap = + std::unordered_map<ServerWindow*, + std::unique_ptr<ScheduledAnimationGroup>>; + using IdToWindowsMap = std::map<AnimationId, std::set<ServerWindow*>>; + + void CancelAnimationForWindowImpl(ServerWindow* window, CancelSource source); + + // Removes |window| from both |window_to_animation_map_| and + // |id_to_windows_map_|. + // Returns true if there are no more windows animating with the animation id + // the window is associated with. + bool RemoveWindowFromMaps(ServerWindow* window); + + AnimationId next_id_; + + base::TimeTicks last_tick_time_; + + base::ObserverList<AnimationRunnerObserver> observers_; + + WindowToAnimationMap window_to_animation_map_; + + IdToWindowsMap id_to_windows_map_; + + DISALLOW_COPY_AND_ASSIGN(AnimationRunner); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_ANIMATION_RUNNER_H_ diff --git a/chromium/components/mus/ws/animation_runner_observer.h b/chromium/components/mus/ws/animation_runner_observer.h new file mode 100644 index 00000000000..4c73e6cdbae --- /dev/null +++ b/chromium/components/mus/ws/animation_runner_observer.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_ANIMATION_RUNNER_OBSERVER_H_ +#define COMPONENTS_MUS_WS_ANIMATION_RUNNER_OBSERVER_H_ + +namespace mus { +namespace ws { + +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 ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_ANIMATION_RUNNER_OBSERVER_H_ diff --git a/chromium/components/mus/ws/animation_runner_unittest.cc b/chromium/components/mus/ws/animation_runner_unittest.cc new file mode 100644 index 00000000000..660a920b313 --- /dev/null +++ b/chromium/components/mus/ws/animation_runner_unittest.cc @@ -0,0 +1,639 @@ +// 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 "components/mus/ws/animation_runner.h" + +#include "base/strings/stringprintf.h" +#include "components/mus/public/interfaces/window_manager_constants.mojom.h" +#include "components/mus/ws/animation_runner_observer.h" +#include "components/mus/ws/scheduled_animation_group.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::TimeDelta; + +namespace mus { +using mojom::AnimationProperty; +using mojom::AnimationTweenType; +using mojom::AnimationElement; +using mojom::AnimationGroup; +using mojom::AnimationProperty; +using mojom::AnimationSequence; +using mojom::AnimationTweenType; +using mojom::AnimationValue; +using mojom::AnimationValuePtr; + +namespace ws { +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; +} + +// Creates an AnimationValuePtr from the specified transform. +AnimationValuePtr TransformAnimationValue(const gfx::Transform& transform) { + AnimationValuePtr value(AnimationValue::New()); + value->transform = transform; + return value; +} + +// 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 = std::move(start_value); + element.target_value = std::move(target_value); +} + +void AddOpacityElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, std::move(start_value), std::move(target_value), + AnimationProperty::OPACITY, AnimationTweenType::LINEAR); +} + +void AddTransformElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, std::move(start_value), std::move(target_value), + AnimationProperty::TRANSFORM, AnimationTweenType::LINEAR); +} + +void AddPauseElement(AnimationGroup* group, TimeDelta time) { + AddElement(group, time, AnimationValuePtr(), AnimationValuePtr(), + AnimationProperty::NONE, AnimationTweenType::LINEAR); +} + +void InitGroupForWindow(AnimationGroup* group, + const WindowId& id, + int cycle_count) { + group->window_id = WindowIdToTransportId(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 window/group pair. + AnimationRunner::AnimationId ScheduleForSingleWindow( + ServerWindow* window, + const AnimationGroup* group, + base::TimeTicks now) { + std::vector<AnimationRunner::WindowAndAnimationPair> pairs; + pairs.push_back(std::make_pair(window, group)); + return runner_.Schedule(pairs, now); + } + + // If |id| is valid and there is only one window schedule against the + // animation it is returned; otherwise returns null. + ServerWindow* GetSingleWindowAnimating(AnimationRunner::AnimationId id) { + std::set<ServerWindow*> windows(runner_.GetWindowsAnimating(id)); + return windows.size() == 1 ? *windows.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + + AnimationGroup group; + InitGroupForWindow(&group, window.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleWindow(&window, &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, window.opacity()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, window.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, window.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + + AnimationGroup group; + InitGroupForWindow(&group, window.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 = + ScheduleForSingleWindow(&window, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the window should have changed yet. + EXPECT_EQ(1.f, window.opacity()); + EXPECT_TRUE(window.transform().IsIdentity()); + + // Animate half way from through opacity animation. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, window.opacity()); + EXPECT_TRUE(window.transform().IsIdentity()); + + // Finish first element (opacity). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.5f, window.opacity()); + EXPECT_TRUE(window.transform().IsIdentity()); + + // Half way through second (transform). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, window.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, window.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(.5f, window.opacity()); + EXPECT_EQ(done_transform, window.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId(1, 1)); + + AnimationGroup group; + InitGroupForWindow(&group, window.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 = + ScheduleForSingleWindow(&window, &group, initial_time_); + + runner_observer_.clear_changes(); + + // Nothing in the window should have changed yet. + EXPECT_EQ(1.f, window.opacity()); + EXPECT_TRUE(window.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, window.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, window.transform()); + + // Animate to 500, which is 1/2 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, window.opacity()); + EXPECT_EQ(done_transform, window.transform()); + + // Animate to 750, which is 3/4 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.875f, window.opacity()); + EXPECT_EQ(done_transform, window.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(1.f, window.opacity()); + EXPECT_EQ(done_transform, window.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId(1, 2)); + + window.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForWindow(&group, window.id(), 3); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + AddPauseElement(&group, TimeDelta::FromMicroseconds(500)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleWindow(&window, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the window should have changed yet. + EXPECT_EQ(.5f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1250)); + EXPECT_EQ(1.f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.75f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2500)); + EXPECT_EQ(.75f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3250)); + EXPECT_EQ(1.f, window.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3750)); + EXPECT_EQ(.75f, window.opacity()); + + // Animate to the end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(6500)); + EXPECT_EQ(.5f, window.opacity()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); +} + +// Verifies scheduling the same window twice sends an interrupt. +TEST_F(AnimationRunnerTest, ScheduleTwice) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId(1, 2)); + + AnimationGroup group; + InitGroupForWindow(&group, window.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleWindow(&window, &group, initial_time_); + runner_observer_.clear_changes(); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, window.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Schedule again. We should get an interrupt, but opacity shouldn't change. + const uint32_t animation2_id = ScheduleForSingleWindow( + &window, &group, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Id should have changed. + EXPECT_NE(animation_id, animation2_id); + + EXPECT_FALSE(runner_.IsAnimating(animation_id)); + EXPECT_EQ(&window, GetSingleWindowAnimating(animation2_id)); + + EXPECT_EQ(.75f, window.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, window.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, window.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, CancelAnimationForWindow) { + // Create an animation and advance it part way. + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + AnimationGroup group; + InitGroupForWindow(&group, window.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleWindow(&window, &group, initial_time_); + runner_observer_.clear_changes(); + EXPECT_EQ(&window, GetSingleWindowAnimating(animation_id)); + + EXPECT_TRUE(runner_.HasAnimations()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, window.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Cancel the animation. + runner_.CancelAnimationForWindow(&window); + + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_EQ(nullptr, GetSingleWindowAnimating(animation_id)); + + EXPECT_EQ(.75f, window.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId(1, 2)); + + window.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForWindow(&group, window.id(), 0); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(1)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleWindow(&window, &group, initial_time_); + runner_observer_.clear_changes(); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000000000750)); + + EXPECT_EQ(.75f, window.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) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + + AnimationGroup group; + InitGroupForWindow(&group, window.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)); + + ScheduleForSingleWindow(&window, &group, initial_time_); + + // Schedule() again, this time without animating opacity. + group.sequences[0]->elements[0]->property = AnimationProperty::NONE; + ScheduleForSingleWindow(&window, &group, initial_time_); + + // Opacity should go to final value. + EXPECT_EQ(.5f, window.opacity()); + // Transform shouldn't have changed since newly scheduled animation also has + // transform in it. + EXPECT_TRUE(window.transform().IsIdentity()); +} + +// Opacity from 1 to .5 over 1000 of v1 and v2 transform to 2x,4x over 500. +TEST_F(AnimationRunnerTest, TwoWindows) { + TestServerWindowDelegate window_delegate; + ServerWindow window1(&window_delegate, WindowId()); + ServerWindow window2(&window_delegate, WindowId(1, 2)); + + AnimationGroup group1; + InitGroupForWindow(&group1, window1.id(), 1); + AddOpacityElement(&group1, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + AnimationGroup group2; + InitGroupForWindow(&group2, window2.id(), 1); + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group2, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + std::vector<AnimationRunner::WindowAndAnimationPair> pairs; + pairs.push_back(std::make_pair(&window1, &group1)); + pairs.push_back(std::make_pair(&window2, &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, window1.opacity()); + EXPECT_TRUE(window2.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, window1.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, window2.transform()); + std::set<ServerWindow*> windows_animating( + runner_.GetWindowsAnimating(animation_id)); + EXPECT_EQ(2u, windows_animating.size()); + EXPECT_EQ(1u, windows_animating.count(&window1)); + EXPECT_EQ(1u, windows_animating.count(&window2)); + + // Animate 750ms in, window1 should be done 3/4 done, and window2 done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.625, window1.opacity()); + EXPECT_EQ(done_transform, window2.transform()); + windows_animating = runner_.GetWindowsAnimating(animation_id); + EXPECT_EQ(1u, windows_animating.size()); + EXPECT_EQ(1u, windows_animating.count(&window1)); + EXPECT_TRUE(runner_.HasAnimations()); + EXPECT_TRUE(runner_.IsAnimating(animation_id)); + + // Animate to end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.5, window1.opacity()); + EXPECT_EQ(done_transform, window2.transform()); + windows_animating = runner_.GetWindowsAnimating(animation_id); + EXPECT_TRUE(windows_animating.empty()); + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_FALSE(runner_.IsAnimating(animation_id)); +} + +TEST_F(AnimationRunnerTest, Reschedule) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + + // Animation from 1-0 over 1ms and in parallel transform to 2x,4x over 1ms. + AnimationGroup group; + InitGroupForWindow(&group, window.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 = + ScheduleForSingleWindow(&window, &group, initial_time_); + + // Animate half way in. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.5f, window.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, window.transform()); + + runner_observer_.clear_changes(); + + // Schedule the same window animating opacity to 1. + AnimationGroup group2; + InitGroupForWindow(&group2, window.id(), 1); + AddOpacityElement(&group2, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + const uint32_t animation_id2 = ScheduleForSingleWindow( + &window, &group2, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Opacity should remain at .5, but transform should go to end state. + EXPECT_EQ(.5f, window.opacity()); + EXPECT_EQ(done_transform, window.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, window.opacity()); + EXPECT_EQ(done_transform, window.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)); +} + +} // ws +} // mus diff --git a/chromium/components/mus/ws/cursor_unittest.cc b/chromium/components/mus/ws/cursor_unittest.cc new file mode 100644 index 00000000000..995318e85f0 --- /dev/null +++ b/chromium/components/mus/ws/cursor_unittest.cc @@ -0,0 +1,198 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "components/mus/common/types.h" +#include "components/mus/common/util.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/test_utils.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { +namespace ws { +namespace test { + +const UserId kTestId1 = "20"; + +class CursorTest : public testing::Test { + public: + CursorTest() : cursor_id_(-1), platform_display_factory_(&cursor_id_) {} + ~CursorTest() override {} + + protected: + // testing::Test: + void SetUp() override { + PlatformDisplay::set_factory_for_testing(&platform_display_factory_); + window_server_.reset( + new WindowServer(&window_server_delegate_, + scoped_refptr<SurfacesState>(new SurfacesState))); + window_server_delegate_.set_window_server(window_server_.get()); + + window_server_delegate_.set_num_displays_to_create(1); + + // As a side effect, this allocates Displays. + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + window_server_->user_id_tracker()->AddUserId(kTestId1); + window_server_->user_id_tracker()->SetActiveUserId(kTestId1); + } + + ServerWindow* GetRoot() { + DisplayManager* display_manager = window_server_->display_manager(); + // ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + return display->GetWindowManagerDisplayRootForUser(kTestId1)->root(); + } + + // Create a 30x30 window where the outer 10 pixels is non-client. + ServerWindow* BuildServerWindow() { + DisplayManager* display_manager = window_server_->display_manager(); + Display* display = *display_manager->displays().begin(); + WindowManagerDisplayRoot* active_display_root = + display->GetActiveWindowManagerDisplayRoot(); + WindowTree* tree = + active_display_root->window_manager_state()->window_tree(); + ClientWindowId child_window_id; + if (!NewWindowInTree(tree, &child_window_id)) + return nullptr; + + ServerWindow* w = tree->GetWindowByClientId(child_window_id); + w->SetBounds(gfx::Rect(10, 10, 30, 30)); + w->SetClientArea(gfx::Insets(10, 10), std::vector<gfx::Rect>()); + w->SetVisible(true); + + ServerWindowSurfaceManagerTestApi test_api(w->GetOrCreateSurfaceManager()); + test_api.CreateEmptyDefaultSurface(); + + return w; + } + + void MoveCursorTo(const gfx::Point& p) { + DisplayManager* display_manager = window_server_->display_manager(); + ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + WindowManagerDisplayRoot* active_display_root = + display->GetActiveWindowManagerDisplayRoot(); + ASSERT_TRUE(active_display_root); + static_cast<PlatformDisplayDelegate*>(display)->OnEvent(ui::PointerEvent( + ui::MouseEvent(ui::ET_MOUSE_MOVED, p, p, base::TimeTicks(), 0, 0))); + WindowManagerState* wms = active_display_root->window_manager_state(); + wms->OnEventAck(wms->window_tree(), mojom::EventResult::HANDLED); + } + + protected: + int32_t cursor_id_; + TestPlatformDisplayFactory platform_display_factory_; + TestWindowServerDelegate window_server_delegate_; + std::unique_ptr<WindowServer> window_server_; + base::MessageLoop message_loop_; + + private: + DISALLOW_COPY_AND_ASSIGN(CursorTest); +}; + +TEST_F(CursorTest, ChangeByMouseMove) { + ServerWindow* win = BuildServerWindow(); + win->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(win->cursor())); + win->SetNonClientCursor(mojom::Cursor::EAST_RESIZE); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, + mojom::Cursor(win->non_client_cursor())); + + // Non client area + MoveCursorTo(gfx::Point(15, 15)); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); + + // Client area + MoveCursorTo(gfx::Point(25, 25)); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(cursor_id_)); +} + +TEST_F(CursorTest, ChangeByClientAreaChange) { + ServerWindow* win = BuildServerWindow(); + win->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(win->cursor())); + win->SetNonClientCursor(mojom::Cursor::EAST_RESIZE); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, + mojom::Cursor(win->non_client_cursor())); + + // Non client area before we move. + MoveCursorTo(gfx::Point(15, 15)); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); + + // Changing the client area should cause a change. + win->SetClientArea(gfx::Insets(1, 1), std::vector<gfx::Rect>()); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(cursor_id_)); +} + +TEST_F(CursorTest, NonClientCursorChange) { + ServerWindow* win = BuildServerWindow(); + win->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(win->cursor())); + win->SetNonClientCursor(mojom::Cursor::EAST_RESIZE); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, + mojom::Cursor(win->non_client_cursor())); + + MoveCursorTo(gfx::Point(15, 15)); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); + + win->SetNonClientCursor(mojom::Cursor::WEST_RESIZE); + EXPECT_EQ(mojom::Cursor::WEST_RESIZE, mojom::Cursor(cursor_id_)); +} + +TEST_F(CursorTest, IgnoreClientCursorChangeInNonClientArea) { + ServerWindow* win = BuildServerWindow(); + win->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(win->cursor())); + win->SetNonClientCursor(mojom::Cursor::EAST_RESIZE); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, + mojom::Cursor(win->non_client_cursor())); + + MoveCursorTo(gfx::Point(15, 15)); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); + + win->SetPredefinedCursor(mojom::Cursor::HELP); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); +} + +TEST_F(CursorTest, NonClientToClientByBoundsChange) { + ServerWindow* win = BuildServerWindow(); + win->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(win->cursor())); + win->SetNonClientCursor(mojom::Cursor::EAST_RESIZE); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, + mojom::Cursor(win->non_client_cursor())); + + // Non client area before we move. + MoveCursorTo(gfx::Point(15, 15)); + EXPECT_EQ(mojom::Cursor::EAST_RESIZE, mojom::Cursor(cursor_id_)); + + win->SetBounds(gfx::Rect(0, 0, 30, 30)); + EXPECT_EQ(mojom::Cursor::IBEAM, mojom::Cursor(cursor_id_)); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/default_access_policy.cc b/chromium/components/mus/ws/default_access_policy.cc new file mode 100644 index 00000000000..1930dc05925 --- /dev/null +++ b/chromium/components/mus/ws/default_access_policy.cc @@ -0,0 +1,203 @@ +// 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 "components/mus/ws/default_access_policy.h" + +#include "components/mus/ws/access_policy_delegate.h" +#include "components/mus/ws/server_window.h" + +namespace mus { +namespace ws { + +DefaultAccessPolicy::DefaultAccessPolicy() {} + +DefaultAccessPolicy::~DefaultAccessPolicy() {} + +void DefaultAccessPolicy::Init(ClientSpecificId client_id, + AccessPolicyDelegate* delegate) { + client_id_ = client_id; + delegate_ = delegate; +} + +bool DefaultAccessPolicy::CanRemoveWindowFromParent( + const ServerWindow* window) const { + if (!WasCreatedByThisClient(window)) + return false; // Can only unparent windows we created. + + return delegate_->HasRootForAccessPolicy(window->parent()) || + WasCreatedByThisClient(window->parent()); +} + +bool DefaultAccessPolicy::CanAddWindow(const ServerWindow* parent, + const ServerWindow* child) const { + return WasCreatedByThisClient(child) && + (delegate_->HasRootForAccessPolicy(parent) || + (WasCreatedByThisClient(parent) && + !delegate_->IsWindowRootOfAnotherTreeForAccessPolicy(parent))); +} + +bool DefaultAccessPolicy::CanAddTransientWindow( + const ServerWindow* parent, + const ServerWindow* child) const { + return (delegate_->HasRootForAccessPolicy(child) || + WasCreatedByThisClient(child)) && + (delegate_->HasRootForAccessPolicy(parent) || + WasCreatedByThisClient(parent)); +} + +bool DefaultAccessPolicy::CanRemoveTransientWindowFromParent( + const ServerWindow* window) const { + return (delegate_->HasRootForAccessPolicy(window) || + WasCreatedByThisClient(window)) && + (delegate_->HasRootForAccessPolicy(window->transient_parent()) || + WasCreatedByThisClient(window->transient_parent())); +} + +bool DefaultAccessPolicy::CanSetModal(const ServerWindow* window) const { + return delegate_->HasRootForAccessPolicy(window) || + WasCreatedByThisClient(window); +} + +bool DefaultAccessPolicy::CanReorderWindow( + const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const { + return WasCreatedByThisClient(window) && + WasCreatedByThisClient(relative_window); +} + +bool DefaultAccessPolicy::CanDeleteWindow(const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool DefaultAccessPolicy::CanGetWindowTree(const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanDescendIntoWindowForWindowTree( + const ServerWindow* window) const { + return (WasCreatedByThisClient(window) && + !delegate_->IsWindowRootOfAnotherTreeForAccessPolicy(window)) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanEmbed(const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool DefaultAccessPolicy::CanChangeWindowVisibility( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanChangeWindowOpacity( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetWindowSurface( + const ServerWindow* window, + mojom::SurfaceType surface_type) const { + if (surface_type == mojom::SurfaceType::UNDERLAY) + return WasCreatedByThisClient(window); + + // Once a window embeds another app, the embedder app is no longer able to + // call SetWindowSurfaceId() - this ability is transferred to the embedded + // app. + if (delegate_->IsWindowRootOfAnotherTreeForAccessPolicy(window)) + return false; + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetWindowBounds(const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool DefaultAccessPolicy::CanSetWindowProperties( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool DefaultAccessPolicy::CanSetWindowTextInputState( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetCapture(const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetFocus(const ServerWindow* window) const { + return !window || WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetClientArea(const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetHitTestMask(const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::CanSetCursorProperties( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool DefaultAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerWindow* window, + const ServerWindow** new_parent, + const ServerWindow** old_parent) const { + if (!WasCreatedByThisClient(window)) + return false; + + if (*new_parent && !WasCreatedByThisClient(*new_parent) && + !delegate_->HasRootForAccessPolicy((*new_parent))) { + *new_parent = nullptr; + } + + if (*old_parent && !WasCreatedByThisClient(*old_parent) && + !delegate_->HasRootForAccessPolicy((*old_parent))) { + *old_parent = nullptr; + } + return true; +} + +const ServerWindow* DefaultAccessPolicy::GetWindowForFocusChange( + const ServerWindow* focused) { + if (WasCreatedByThisClient(focused) || + delegate_->HasRootForAccessPolicy(focused)) + return focused; + return nullptr; +} + +bool DefaultAccessPolicy::CanSetWindowManager() const { + return false; +} + +bool DefaultAccessPolicy::WasCreatedByThisClient( + const ServerWindow* window) const { + return window->id().client_id == client_id_; +} + +bool DefaultAccessPolicy::IsValidIdForNewWindow( + const ClientWindowId& id) const { + // Clients using DefaultAccessPolicy only see windows they have created (for + // the embed point they choose the id), so it's ok for clients to use whatever + // id they want. + return true; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/default_access_policy.h b/chromium/components/mus/ws/default_access_policy.h new file mode 100644 index 00000000000..9135fb9901d --- /dev/null +++ b/chromium/components/mus/ws/default_access_policy.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_DEFAULT_ACCESS_POLICY_H_ +#define COMPONENTS_MUS_WS_DEFAULT_ACCESS_POLICY_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "components/mus/ws/access_policy.h" + +namespace mus { +namespace ws { + +class AccessPolicyDelegate; + +// AccessPolicy for all clients, except the window manager. +class DefaultAccessPolicy : public AccessPolicy { + public: + DefaultAccessPolicy(); + ~DefaultAccessPolicy() override; + + // AccessPolicy: + void Init(ClientSpecificId client_id, + AccessPolicyDelegate* delegate) override; + bool CanRemoveWindowFromParent(const ServerWindow* window) const override; + bool CanAddWindow(const ServerWindow* parent, + const ServerWindow* child) const override; + bool CanAddTransientWindow(const ServerWindow* parent, + const ServerWindow* child) const override; + bool CanRemoveTransientWindowFromParent( + const ServerWindow* window) const override; + bool CanSetModal(const ServerWindow* window) const override; + bool CanReorderWindow(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const override; + bool CanDeleteWindow(const ServerWindow* window) const override; + bool CanGetWindowTree(const ServerWindow* window) const override; + bool CanDescendIntoWindowForWindowTree( + const ServerWindow* window) const override; + bool CanEmbed(const ServerWindow* window) const override; + bool CanChangeWindowVisibility(const ServerWindow* window) const override; + bool CanChangeWindowOpacity(const ServerWindow* window) const override; + bool CanSetWindowSurface(const ServerWindow* window, + mus::mojom::SurfaceType surface_type) const override; + bool CanSetWindowBounds(const ServerWindow* window) const override; + bool CanSetWindowProperties(const ServerWindow* window) const override; + bool CanSetWindowTextInputState(const ServerWindow* window) const override; + bool CanSetCapture(const ServerWindow* window) const override; + bool CanSetFocus(const ServerWindow* window) const override; + bool CanSetClientArea(const ServerWindow* window) const override; + bool CanSetHitTestMask(const ServerWindow* window) const override; + bool CanSetCursorProperties(const ServerWindow* window) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerWindow* window, + const ServerWindow** new_parent, + const ServerWindow** old_parent) const override; + const ServerWindow* GetWindowForFocusChange( + const ServerWindow* focused) override; + bool CanSetWindowManager() const override; + bool IsValidIdForNewWindow(const ClientWindowId& id) const override; + + private: + bool WasCreatedByThisClient(const ServerWindow* window) const; + + ClientSpecificId client_id_ = 0u; + AccessPolicyDelegate* delegate_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(DefaultAccessPolicy); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_DEFAULT_ACCESS_POLICY_H_ diff --git a/chromium/components/mus/ws/display.cc b/chromium/components/mus/ws/display.cc new file mode 100644 index 00000000000..84a4162de05 --- /dev/null +++ b/chromium/components/mus/ws/display.cc @@ -0,0 +1,419 @@ +// 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 "components/mus/ws/display.h" + +#include <set> +#include <vector> + +#include "base/debug/debugger.h" +#include "base/strings/utf_string_conversions.h" +#include "components/mus/common/types.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/focus_controller.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/user_activity_monitor.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_manager_window_tree_factory.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "mojo/common/common_type_converters.h" +#include "services/shell/public/interfaces/connector.mojom.h" +#include "ui/base/cursor/cursor.h" + +namespace mus { +namespace ws { + +Display::Display(WindowServer* window_server, + const PlatformDisplayInitParams& platform_display_init_params) + : id_(window_server->display_manager()->GetAndAdvanceNextDisplayId()), + window_server_(window_server), + platform_display_(PlatformDisplay::Create(platform_display_init_params)), + last_cursor_(ui::kCursorNone) { + platform_display_->Init(this); + + window_server_->window_manager_window_tree_factory_set()->AddObserver(this); + window_server_->user_id_tracker()->AddObserver(this); +} + +Display::~Display() { + window_server_->user_id_tracker()->RemoveObserver(this); + + window_server_->window_manager_window_tree_factory_set()->RemoveObserver( + this); + + if (!focus_controller_) { + focus_controller_->RemoveObserver(this); + focus_controller_.reset(); + } + + for (ServerWindow* window : windows_needing_frame_destruction_) + window->RemoveObserver(this); + + // If there is a |binding_| then the tree was created specifically for this + // display (which corresponds to a WindowTreeHost). + if (binding_ && !window_manager_display_root_map_.empty()) { + window_server_->DestroyTree(window_manager_display_root_map_.begin() + ->second->window_manager_state() + ->window_tree()); + } +} + +void Display::Init(std::unique_ptr<DisplayBinding> binding) { + init_called_ = true; + binding_ = std::move(binding); + display_manager()->AddDisplay(this); + InitWindowManagerDisplayRootsIfNecessary(); +} + +DisplayManager* Display::display_manager() { + return window_server_->display_manager(); +} + +const DisplayManager* Display::display_manager() const { + return window_server_->display_manager(); +} + +mojom::DisplayPtr Display::ToMojomDisplay() const { + mojom::DisplayPtr display_ptr = mojom::Display::New(); + display_ptr = mojom::Display::New(); + display_ptr->id = id_; + // TODO(sky): Display should know it's origin. + display_ptr->bounds.SetRect(0, 0, root_->bounds().size().width(), + root_->bounds().size().height()); + // TODO(sky): window manager needs an API to set the work area. + display_ptr->work_area = display_ptr->bounds; + display_ptr->device_pixel_ratio = platform_display_->GetDeviceScaleFactor(); + display_ptr->rotation = platform_display_->GetRotation(); + // TODO(sky): make this real. + display_ptr->is_primary = true; + // TODO(sky): make this real. + display_ptr->touch_support = mojom::TouchSupport::UNKNOWN; + display_ptr->frame_decoration_values = mojom::FrameDecorationValues::New(); + return display_ptr; +} + +void Display::SchedulePaint(const ServerWindow* window, + const gfx::Rect& bounds) { + DCHECK(root_->Contains(window)); + platform_display_->SchedulePaint(window, bounds); +} + +void Display::ScheduleSurfaceDestruction(ServerWindow* window) { + if (!platform_display_->IsFramePending()) { + window->DestroySurfacesScheduledForDestruction(); + return; + } + if (windows_needing_frame_destruction_.count(window)) + return; + windows_needing_frame_destruction_.insert(window); + window->AddObserver(this); +} + +mojom::Rotation Display::GetRotation() const { + return platform_display_->GetRotation(); +} + +gfx::Size Display::GetSize() const { + return root_->bounds().size(); +} + +int64_t Display::GetPlatformDisplayId() const { + return platform_display_->GetDisplayId(); +} + +ServerWindow* Display::GetRootWithId(const WindowId& id) { + if (id == root_->id()) + return root_.get(); + for (auto& pair : window_manager_display_root_map_) { + if (pair.second->root()->id() == id) + return pair.second->root(); + } + return nullptr; +} + +WindowManagerDisplayRoot* Display::GetWindowManagerDisplayRootWithRoot( + const ServerWindow* window) { + for (auto& pair : window_manager_display_root_map_) { + if (pair.second->root() == window) + return pair.second.get(); + } + return nullptr; +} + +const WindowManagerDisplayRoot* Display::GetWindowManagerDisplayRootForUser( + const UserId& user_id) const { + auto iter = window_manager_display_root_map_.find(user_id); + return iter == window_manager_display_root_map_.end() ? nullptr + : iter->second.get(); +} + +const WindowManagerDisplayRoot* Display::GetActiveWindowManagerDisplayRoot() + const { + return GetWindowManagerDisplayRootForUser( + window_server_->user_id_tracker()->active_id()); +} + +bool Display::SetFocusedWindow(ServerWindow* new_focused_window) { + ServerWindow* old_focused_window = focus_controller_->GetFocusedWindow(); + if (old_focused_window == new_focused_window) + return true; + DCHECK(!new_focused_window || root_window()->Contains(new_focused_window)); + return focus_controller_->SetFocusedWindow(new_focused_window); +} + +ServerWindow* Display::GetFocusedWindow() { + return focus_controller_->GetFocusedWindow(); +} + +void Display::ActivateNextWindow() { + // TODO(sky): this is wrong, needs to figure out the next window to activate + // and then route setting through WindowServer. + focus_controller_->ActivateNextWindow(); +} + +void Display::AddActivationParent(ServerWindow* window) { + activation_parents_.Add(window); +} + +void Display::RemoveActivationParent(ServerWindow* window) { + activation_parents_.Remove(window); +} + +void Display::UpdateTextInputState(ServerWindow* window, + const ui::TextInputState& state) { + // Do not need to update text input for unfocused windows. + if (!platform_display_ || focus_controller_->GetFocusedWindow() != window) + return; + platform_display_->UpdateTextInputState(state); +} + +void Display::SetImeVisibility(ServerWindow* window, bool visible) { + // Do not need to show or hide IME for unfocused window. + if (focus_controller_->GetFocusedWindow() != window) + return; + platform_display_->SetImeVisibility(visible); +} + +void Display::OnWillDestroyTree(WindowTree* tree) { + for (auto it = window_manager_display_root_map_.begin(); + it != window_manager_display_root_map_.end(); ++it) { + if (it->second->window_manager_state()->window_tree() == tree) { + window_manager_display_root_map_.erase(it); + break; + } + } +} + +void Display::UpdateNativeCursor(int32_t cursor_id) { + if (cursor_id != last_cursor_) { + platform_display_->SetCursorById(cursor_id); + last_cursor_ = cursor_id; + } +} + +void Display::SetSize(const gfx::Size& size) { + platform_display_->SetViewportSize(size); +} + +void Display::SetTitle(const mojo::String& title) { + platform_display_->SetTitle(title.To<base::string16>()); +} + +void Display::InitWindowManagerDisplayRootsIfNecessary() { + if (!init_called_ || !root_) + return; + + display_manager()->OnDisplayAcceleratedWidgetAvailable(this); + if (binding_) { + std::unique_ptr<WindowManagerDisplayRoot> display_root_ptr( + new WindowManagerDisplayRoot(this)); + WindowManagerDisplayRoot* display_root = display_root_ptr.get(); + // For this case we never create additional displays roots, so any + // id works. + window_manager_display_root_map_[shell::mojom::kRootUserID] = + std::move(display_root_ptr); + WindowTree* window_tree = binding_->CreateWindowTree(display_root->root()); + display_root->window_manager_state_ = window_tree->window_manager_state(); + } else { + CreateWindowManagerDisplayRootsFromFactories(); + } +} + +void Display::CreateWindowManagerDisplayRootsFromFactories() { + std::vector<WindowManagerWindowTreeFactory*> factories = + window_server_->window_manager_window_tree_factory_set()->GetFactories(); + for (WindowManagerWindowTreeFactory* factory : factories) { + if (factory->window_tree()) + CreateWindowManagerDisplayRootFromFactory(factory); + } +} + +void Display::CreateWindowManagerDisplayRootFromFactory( + WindowManagerWindowTreeFactory* factory) { + std::unique_ptr<WindowManagerDisplayRoot> display_root_ptr( + new WindowManagerDisplayRoot(this)); + WindowManagerDisplayRoot* display_root = display_root_ptr.get(); + window_manager_display_root_map_[factory->user_id()] = + std::move(display_root_ptr); + display_root->window_manager_state_ = + factory->window_tree()->window_manager_state(); + const bool is_active = + factory->user_id() == window_server_->user_id_tracker()->active_id(); + display_root->root()->SetVisible(is_active); + display_root->window_manager_state()->window_tree()->AddRootForWindowManager( + display_root->root()); +} + +ServerWindow* Display::GetRootWindow() { + return root_.get(); +} + +void Display::OnEvent(const ui::Event& event) { + WindowManagerDisplayRoot* display_root = GetActiveWindowManagerDisplayRoot(); + if (display_root) + display_root->window_manager_state()->ProcessEvent(event); + window_server_ + ->GetUserActivityMonitorForUser( + window_server_->user_id_tracker()->active_id()) + ->OnUserActivity(); +} + +void Display::OnNativeCaptureLost() { + WindowManagerDisplayRoot* display_root = GetActiveWindowManagerDisplayRoot(); + if (display_root) + display_root->window_manager_state()->SetCapture(nullptr, kInvalidClientId); +} + +void Display::OnDisplayClosed() { + display_manager()->DestroyDisplay(this); +} + +void Display::OnViewportMetricsChanged(const ViewportMetrics& old_metrics, + const ViewportMetrics& new_metrics) { + if (!root_) { + root_.reset(window_server_->CreateServerWindow( + display_manager()->GetAndAdvanceNextRootId(), + ServerWindow::Properties())); + root_->SetBounds(gfx::Rect(new_metrics.size_in_pixels)); + root_->SetVisible(true); + focus_controller_.reset(new FocusController(this, root_.get())); + focus_controller_->AddObserver(this); + InitWindowManagerDisplayRootsIfNecessary(); + } else { + root_->SetBounds(gfx::Rect(new_metrics.size_in_pixels)); + const gfx::Rect wm_bounds(root_->bounds().size()); + for (auto& pair : window_manager_display_root_map_) + pair.second->root()->SetBounds(wm_bounds); + } + display_manager()->OnDisplayUpdate(this); +} + +void Display::OnCompositorFrameDrawn() { + std::set<ServerWindow*> windows; + windows.swap(windows_needing_frame_destruction_); + for (ServerWindow* window : windows) { + window->RemoveObserver(this); + window->DestroySurfacesScheduledForDestruction(); + } +} + +bool Display::CanHaveActiveChildren(ServerWindow* window) const { + return window && activation_parents_.Contains(window); +} + +void Display::OnActivationChanged(ServerWindow* old_active_window, + ServerWindow* new_active_window) { + DCHECK_NE(new_active_window, old_active_window); + if (new_active_window && new_active_window->parent()) { + // Raise the new active window. + // TODO(sad): Let the WM dictate whether to raise the window or not? + new_active_window->parent()->StackChildAtTop(new_active_window); + } +} + +void Display::OnFocusChanged(FocusControllerChangeSource change_source, + ServerWindow* old_focused_window, + ServerWindow* new_focused_window) { + // TODO(sky): focus is global, not per windowtreehost. Move. + + // There are up to four clients that need to be notified: + // . the client containing |old_focused_window|. + // . the client with |old_focused_window| as its root. + // . the client containing |new_focused_window|. + // . the client with |new_focused_window| as its root. + // Some of these client may be the same. The following takes care to notify + // each only once. + WindowTree* owning_tree_old = nullptr; + WindowTree* embedded_tree_old = nullptr; + + if (old_focused_window) { + owning_tree_old = + window_server_->GetTreeWithId(old_focused_window->id().client_id); + if (owning_tree_old) { + owning_tree_old->ProcessFocusChanged(old_focused_window, + new_focused_window); + } + embedded_tree_old = window_server_->GetTreeWithRoot(old_focused_window); + if (embedded_tree_old) { + DCHECK_NE(owning_tree_old, embedded_tree_old); + embedded_tree_old->ProcessFocusChanged(old_focused_window, + new_focused_window); + } + } + WindowTree* owning_tree_new = nullptr; + WindowTree* embedded_tree_new = nullptr; + if (new_focused_window) { + owning_tree_new = + window_server_->GetTreeWithId(new_focused_window->id().client_id); + if (owning_tree_new && owning_tree_new != owning_tree_old && + owning_tree_new != embedded_tree_old) { + owning_tree_new->ProcessFocusChanged(old_focused_window, + new_focused_window); + } + embedded_tree_new = window_server_->GetTreeWithRoot(new_focused_window); + if (embedded_tree_new && embedded_tree_new != owning_tree_old && + embedded_tree_new != embedded_tree_old) { + DCHECK_NE(owning_tree_new, embedded_tree_new); + embedded_tree_new->ProcessFocusChanged(old_focused_window, + new_focused_window); + } + } + + // WindowManagers are always notified of focus changes. + WindowManagerDisplayRoot* display_root = GetActiveWindowManagerDisplayRoot(); + if (display_root) { + WindowTree* wm_tree = display_root->window_manager_state()->window_tree(); + if (wm_tree != owning_tree_old && wm_tree != embedded_tree_old && + wm_tree != owning_tree_new && wm_tree != embedded_tree_new) { + wm_tree->ProcessFocusChanged(old_focused_window, new_focused_window); + } + } + + UpdateTextInputState(new_focused_window, + new_focused_window->text_input_state()); +} + +void Display::OnWindowDestroyed(ServerWindow* window) { + windows_needing_frame_destruction_.erase(window); + window->RemoveObserver(this); +} + +void Display::OnUserIdRemoved(const UserId& id) { + window_manager_display_root_map_.erase(id); +} + +void Display::OnWindowManagerWindowTreeFactoryReady( + WindowManagerWindowTreeFactory* factory) { + if (!binding_) + CreateWindowManagerDisplayRootFromFactory(factory); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/display.h b/chromium/components/mus/ws/display.h new file mode 100644 index 00000000000..419b1909c03 --- /dev/null +++ b/chromium/components/mus/ws/display.h @@ -0,0 +1,224 @@ +// 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 COMPONENTS_MUS_WS_DISPLAY_H_ +#define COMPONENTS_MUS_WS_DISPLAY_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <queue> +#include <set> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/mus/common/types.h" +#include "components/mus/public/interfaces/window_manager_constants.mojom.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/ws/focus_controller_delegate.h" +#include "components/mus/ws/focus_controller_observer.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_observer.h" +#include "components/mus/ws/server_window_tracker.h" +#include "components/mus/ws/user_id_tracker_observer.h" +#include "components/mus/ws/window_manager_window_tree_factory_set_observer.h" + +namespace mus { +namespace ws { + +class DisplayBinding; +class DisplayManager; +class FocusController; +struct PlatformDisplayInitParams; +class WindowManagerDisplayRoot; +class WindowServer; +class WindowTree; + +namespace test { +class DisplayTestApi; +} + +// Displays manages the state associated with a single display. Display has a +// single root window whose children are the roots for a per-user +// WindowManager. Display is configured in two distinct +// ways: +// . with a DisplayBinding. In this mode there is only ever one WindowManager +// for the display, which comes from the client that created the +// Display. +// . without a DisplayBinding. In this mode a WindowManager is automatically +// created per user. +class Display : public PlatformDisplayDelegate, + public mojom::WindowTreeHost, + public FocusControllerObserver, + public FocusControllerDelegate, + public ServerWindowObserver, + public UserIdTrackerObserver, + public WindowManagerWindowTreeFactorySetObserver { + public: + Display(WindowServer* window_server, + const PlatformDisplayInitParams& platform_display_init_params); + ~Display() override; + + // Initializes state that depends on the existence of a Display. + void Init(std::unique_ptr<DisplayBinding> binding); + + uint32_t id() const { return id_; } + + DisplayManager* display_manager(); + const DisplayManager* display_manager() const; + + PlatformDisplay* platform_display() { return platform_display_.get(); } + + // Returns a mojom::Display for the specified display. WindowManager specific + // values are not set. + mojom::DisplayPtr ToMojomDisplay() const; + + // Schedules a paint for the specified region in the coordinates of |window|. + void SchedulePaint(const ServerWindow* window, const gfx::Rect& bounds); + + // Schedules destruction of surfaces in |window|. If a frame has been + // scheduled but not drawn surface destruction is delayed until the frame is + // drawn, otherwise destruction is immediate. + void ScheduleSurfaceDestruction(ServerWindow* window); + + mojom::Rotation GetRotation() const; + gfx::Size GetSize() const; + + // Returns the id for the corresponding id. + int64_t GetPlatformDisplayId() const; + + WindowServer* window_server() { return window_server_; } + + // Returns the root of the Display. The root's children are the roots + // of the corresponding WindowManagers. + ServerWindow* root_window() { return root_.get(); } + const ServerWindow* root_window() const { return root_.get(); } + + // Returns the ServerWindow whose id is |id|. This does not do a search over + // all windows, rather just the display and window manager root windows. + // + // In general you shouldn't use this, rather use WindowServer::GetWindow(), + // which calls this as necessary. + ServerWindow* GetRootWithId(const WindowId& id); + + WindowManagerDisplayRoot* GetWindowManagerDisplayRootWithRoot( + const ServerWindow* window); + WindowManagerDisplayRoot* GetWindowManagerDisplayRootForUser( + const UserId& user_id) { + return const_cast<WindowManagerDisplayRoot*>( + const_cast<const Display*>(this)->GetWindowManagerDisplayRootForUser( + user_id)); + } + const WindowManagerDisplayRoot* GetWindowManagerDisplayRootForUser( + const UserId& user_id) const; + WindowManagerDisplayRoot* GetActiveWindowManagerDisplayRoot() { + return const_cast<WindowManagerDisplayRoot*>( + const_cast<const Display*>(this)->GetActiveWindowManagerDisplayRoot()); + } + const WindowManagerDisplayRoot* GetActiveWindowManagerDisplayRoot() const; + size_t num_window_manger_states() const { + return window_manager_display_root_map_.size(); + } + + // TODO(sky): this should only be called by WindowServer, move to interface + // used by WindowServer. + // See description of WindowServer::SetFocusedWindow() for details on return + // value. + bool SetFocusedWindow(ServerWindow* window); + // NOTE: this returns the focused window only if the focused window is in this + // display. If this returns null focus may be in another display. + ServerWindow* GetFocusedWindow(); + + void ActivateNextWindow(); + + void AddActivationParent(ServerWindow* window); + void RemoveActivationParent(ServerWindow* window); + + void UpdateTextInputState(ServerWindow* window, + const ui::TextInputState& state); + void SetImeVisibility(ServerWindow* window, bool visible); + + // Called just before |tree| is destroyed. + void OnWillDestroyTree(WindowTree* tree); + + void UpdateNativeCursor(int32_t cursor_id); + + // mojom::WindowTreeHost: + void SetSize(const gfx::Size& size) override; + void SetTitle(const mojo::String& title) override; + + private: + friend class test::DisplayTestApi; + + using WindowManagerDisplayRootMap = + std::map<UserId, std::unique_ptr<WindowManagerDisplayRoot>>; + + // Inits the necessary state once the display is ready. + void InitWindowManagerDisplayRootsIfNecessary(); + + // Creates the set of WindowManagerDisplayRoots from the + // WindowManagerWindowTreeFactorySet. + void CreateWindowManagerDisplayRootsFromFactories(); + + void CreateWindowManagerDisplayRootFromFactory( + WindowManagerWindowTreeFactory* factory); + + // PlatformDisplayDelegate: + ServerWindow* GetRootWindow() override; + void OnEvent(const ui::Event& event) override; + void OnNativeCaptureLost() override; + void OnDisplayClosed() override; + void OnViewportMetricsChanged(const ViewportMetrics& old_metrics, + const ViewportMetrics& new_metrics) override; + void OnCompositorFrameDrawn() override; + + // FocusControllerDelegate: + bool CanHaveActiveChildren(ServerWindow* window) const override; + + // FocusControllerObserver: + void OnActivationChanged(ServerWindow* old_active_window, + ServerWindow* new_active_window) override; + void OnFocusChanged(FocusControllerChangeSource change_source, + ServerWindow* old_focused_window, + ServerWindow* new_focused_window) override; + + // ServerWindowObserver: + void OnWindowDestroyed(ServerWindow* window) override; + + // UserIdTrackerObserver: + void OnUserIdRemoved(const UserId& id) override; + + // WindowManagerWindowTreeFactorySetObserver: + void OnWindowManagerWindowTreeFactoryReady( + WindowManagerWindowTreeFactory* factory) override; + + const uint32_t id_; + std::unique_ptr<DisplayBinding> binding_; + // Set once Init() has been called. + bool init_called_ = false; + WindowServer* const window_server_; + std::unique_ptr<ServerWindow> root_; + std::unique_ptr<PlatformDisplay> platform_display_; + std::unique_ptr<FocusController> focus_controller_; + + // The last cursor set. Used to track whether we need to change the cursor. + int32_t last_cursor_; + + ServerWindowTracker activation_parents_; + + // Set of windows with surfaces that need to be destroyed once the frame + // draws. + std::set<ServerWindow*> windows_needing_frame_destruction_; + + WindowManagerDisplayRootMap window_manager_display_root_map_; + + DISALLOW_COPY_AND_ASSIGN(Display); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_DISPLAY_H_ diff --git a/chromium/components/mus/ws/display_binding.cc b/chromium/components/mus/ws/display_binding.cc new file mode 100644 index 00000000000..519bef4a2f8 --- /dev/null +++ b/chromium/components/mus/ws/display_binding.cc @@ -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. + +#include "components/mus/ws/display_binding.h" + +#include "base/memory/ptr_util.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/window_manager_access_policy.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" +#include "services/shell/public/interfaces/connector.mojom.h" + +namespace mus { +namespace ws { + +DisplayBindingImpl::DisplayBindingImpl(mojom::WindowTreeHostRequest request, + Display* display, + const UserId& user_id, + mojom::WindowTreeClientPtr client, + WindowServer* window_server) + : window_server_(window_server), + user_id_(user_id), + binding_(display, std::move(request)), + client_(std::move(client)) {} + +DisplayBindingImpl::~DisplayBindingImpl() {} + +WindowTree* DisplayBindingImpl::CreateWindowTree(ServerWindow* root) { + const uint32_t embed_flags = 0; + WindowTree* tree = window_server_->EmbedAtWindow( + root, user_id_, std::move(client_), embed_flags, + base::WrapUnique(new WindowManagerAccessPolicy)); + tree->ConfigureWindowManager(); + return tree; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/display_binding.h b/chromium/components/mus/ws/display_binding.h new file mode 100644 index 00000000000..e1f46124870 --- /dev/null +++ b/chromium/components/mus/ws/display_binding.h @@ -0,0 +1,60 @@ +// 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 COMPONENTS_MUS_WS_DISPLAY_BINDING_H_ +#define COMPONENTS_MUS_WS_DISPLAY_BINDING_H_ + +#include <memory> + +#include "base/macros.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace ws { + +class ServerWindow; +class WindowServer; +class WindowTree; + +// DisplayBinding manages the binding between a Display and it's mojo clients. +// DisplayBinding is used when a Display is created via a +// WindowTreeHostFactory. +// +// DisplayBinding is owned by Display. +class DisplayBinding { + public: + virtual ~DisplayBinding() {} + + virtual WindowTree* CreateWindowTree(ServerWindow* root) = 0; +}; + +// Live implementation of DisplayBinding. +class DisplayBindingImpl : public DisplayBinding { + public: + DisplayBindingImpl(mojom::WindowTreeHostRequest request, + Display* display, + const UserId& user_id, + mojom::WindowTreeClientPtr client, + WindowServer* window_server); + ~DisplayBindingImpl() override; + + private: + // DisplayBinding: + WindowTree* CreateWindowTree(ServerWindow* root) override; + + WindowServer* window_server_; + const UserId user_id_; + mojo::Binding<mojom::WindowTreeHost> binding_; + mojom::WindowTreeClientPtr client_; + + DISALLOW_COPY_AND_ASSIGN(DisplayBindingImpl); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_DISPLAY_BINDING_H_ diff --git a/chromium/components/mus/ws/display_manager.cc b/chromium/components/mus/ws/display_manager.cc new file mode 100644 index 00000000000..d5554093ec1 --- /dev/null +++ b/chromium/components/mus/ws/display_manager.cc @@ -0,0 +1,167 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/display_manager.h" + +#include "base/memory/ptr_util.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_manager_delegate.h" +#include "components/mus/ws/event_dispatcher.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/user_display_manager.h" +#include "components/mus/ws/user_id_tracker.h" +#include "components/mus/ws/window_manager_state.h" + +namespace mus { +namespace ws { + +DisplayManager::DisplayManager(DisplayManagerDelegate* delegate, + UserIdTracker* user_id_tracker) + // |next_root_id_| is used as the lower bits, so that starting at 0 is + // fine. |next_display_id_| is used by itself, so we start at 1 to reserve + // 0 as invalid. + : delegate_(delegate), + user_id_tracker_(user_id_tracker), + next_root_id_(0), + next_display_id_(1) { + user_id_tracker_->AddObserver(this); +} + +DisplayManager::~DisplayManager() { + user_id_tracker_->RemoveObserver(this); + DestroyAllDisplays(); +} + +UserDisplayManager* DisplayManager::GetUserDisplayManager( + const UserId& user_id) { + if (!user_display_managers_.count(user_id)) { + user_display_managers_[user_id] = + base::WrapUnique(new UserDisplayManager(this, delegate_, user_id)); + } + return user_display_managers_[user_id].get(); +} + +void DisplayManager::AddDisplay(Display* display) { + DCHECK_EQ(0u, pending_displays_.count(display)); + pending_displays_.insert(display); +} + +void DisplayManager::DestroyDisplay(Display* display) { + if (pending_displays_.count(display)) { + pending_displays_.erase(display); + } else { + for (const auto& pair : user_display_managers_) + pair.second->OnWillDestroyDisplay(display); + + DCHECK(displays_.count(display)); + displays_.erase(display); + } + delete display; + + // If we have no more roots left, let the app know so it can terminate. + // TODO(sky): move to delegate/observer. + if (!displays_.size() && !pending_displays_.size()) + delegate_->OnNoMoreDisplays(); +} + +void DisplayManager::DestroyAllDisplays() { + while (!pending_displays_.empty()) + DestroyDisplay(*pending_displays_.begin()); + DCHECK(pending_displays_.empty()); + + while (!displays_.empty()) + DestroyDisplay(*displays_.begin()); + DCHECK(displays_.empty()); +} + +std::set<const Display*> DisplayManager::displays() const { + std::set<const Display*> ret_value(displays_.begin(), displays_.end()); + return ret_value; +} + +void DisplayManager::OnDisplayUpdate(Display* display) { + for (const auto& pair : user_display_managers_) + pair.second->OnDisplayUpdate(display); +} + +Display* DisplayManager::GetDisplayContaining(ServerWindow* window) { + return const_cast<Display*>( + static_cast<const DisplayManager*>(this)->GetDisplayContaining(window)); +} + +const Display* DisplayManager::GetDisplayContaining( + const ServerWindow* window) const { + while (window && window->parent()) + window = window->parent(); + for (Display* display : displays_) { + if (window == display->root_window()) + return display; + } + return nullptr; +} + +const WindowManagerDisplayRoot* DisplayManager::GetWindowManagerDisplayRoot( + const ServerWindow* window) const { + const ServerWindow* last = window; + while (window && window->parent()) { + last = window; + window = window->parent(); + } + for (Display* display : displays_) { + if (window == display->root_window()) + return display->GetWindowManagerDisplayRootWithRoot(last); + } + return nullptr; +} + +WindowManagerDisplayRoot* DisplayManager::GetWindowManagerDisplayRoot( + const ServerWindow* window) { + return const_cast<WindowManagerDisplayRoot*>( + const_cast<const DisplayManager*>(this)->GetWindowManagerDisplayRoot( + window)); +} + +WindowId DisplayManager::GetAndAdvanceNextRootId() { + // TODO(sky): handle wrapping! + const uint16_t id = next_root_id_++; + DCHECK_LT(id, next_root_id_); + return RootWindowId(id); +} + +uint32_t DisplayManager::GetAndAdvanceNextDisplayId() { + // TODO(sky): handle wrapping! + const uint32_t id = next_display_id_++; + DCHECK_LT(id, next_display_id_); + return id; +} + +void DisplayManager::OnDisplayAcceleratedWidgetAvailable(Display* display) { + DCHECK_NE(0u, pending_displays_.count(display)); + DCHECK_EQ(0u, displays_.count(display)); + const bool is_first_display = displays_.empty(); + displays_.insert(display); + pending_displays_.erase(display); + if (is_first_display) + delegate_->OnFirstDisplayReady(); +} + +void DisplayManager::OnActiveUserIdChanged(const UserId& previously_active_id, + const UserId& active_id) { + WindowManagerState* previous_window_manager_state = + delegate_->GetWindowManagerStateForUser(previously_active_id); + gfx::Point mouse_location_on_screen; + if (previous_window_manager_state) { + mouse_location_on_screen = previous_window_manager_state->event_dispatcher() + ->mouse_pointer_last_location(); + previous_window_manager_state->Deactivate(); + } + + WindowManagerState* current_window_manager_state = + delegate_->GetWindowManagerStateForUser(active_id); + if (current_window_manager_state) + current_window_manager_state->Activate(mouse_location_on_screen); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/display_manager.h b/chromium/components/mus/ws/display_manager.h new file mode 100644 index 00000000000..0c01f217e31 --- /dev/null +++ b/chromium/components/mus/ws/display_manager.h @@ -0,0 +1,102 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_DISPLAY_MANAGER_H_ +#define COMPONENTS_MUS_WS_DISPLAY_MANAGER_H_ + +#include <map> +#include <memory> +#include <set> + +#include "base/macros.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/user_id.h" +#include "components/mus/ws/user_id_tracker_observer.h" + +namespace mus { +namespace ws { + +class Display; +class DisplayManagerDelegate; +class ServerWindow; +class UserDisplayManager; +class UserIdTracker; +class WindowManagerDisplayRoot; + +// DisplayManager manages the set of Displays. DisplayManager distinguishes +// between displays that do yet have an accelerated widget (pending), vs +// those that do. +class DisplayManager : public UserIdTrackerObserver { + public: + DisplayManager(DisplayManagerDelegate* delegate, + UserIdTracker* user_id_tracker); + ~DisplayManager() override; + + // Returns the UserDisplayManager for |user_id|. DisplayManager owns the + // return value. + UserDisplayManager* GetUserDisplayManager(const UserId& user_id); + + // Adds/removes a Display. DisplayManager owns the Displays. + // TODO(sky): make add take a scoped_ptr. + void AddDisplay(Display* display); + void DestroyDisplay(Display* display); + void DestroyAllDisplays(); + const std::set<Display*>& displays() { return displays_; } + std::set<const Display*> displays() const; + + // Notifies when something about the Display changes. + void OnDisplayUpdate(Display* display); + + // Returns the Display that contains |window|, or null if |window| is not + // attached to a display. + Display* GetDisplayContaining(ServerWindow* window); + const Display* GetDisplayContaining(const ServerWindow* window) const; + + const WindowManagerDisplayRoot* GetWindowManagerDisplayRoot( + const ServerWindow* window) const; + // TODO(sky): constness here is wrong! fix! + WindowManagerDisplayRoot* GetWindowManagerDisplayRoot( + const ServerWindow* window); + + bool has_displays() const { return !displays_.empty(); } + bool has_active_or_pending_displays() const { + return !displays_.empty() || !pending_displays_.empty(); + } + + // Returns the id for the next root window (both for the root of a Display + // as well as the root of WindowManagers). + WindowId GetAndAdvanceNextRootId(); + + uint32_t GetAndAdvanceNextDisplayId(); + + // Called when the AcceleratedWidget is available for |display|. + void OnDisplayAcceleratedWidgetAvailable(Display* display); + + private: + // UserIdTrackerObserver: + void OnActiveUserIdChanged(const UserId& previously_active_id, + const UserId& active_id) override; + + DisplayManagerDelegate* delegate_; + UserIdTracker* user_id_tracker_; + + // Displays are initially added to |pending_displays_|. When the display is + // initialized it is moved to |displays_|. WindowServer owns the Displays. + std::set<Display*> pending_displays_; + std::set<Display*> displays_; + + std::map<UserId, std::unique_ptr<UserDisplayManager>> user_display_managers_; + + // ID to use for next root node. + ClientSpecificId next_root_id_; + + uint32_t next_display_id_; + + DISALLOW_COPY_AND_ASSIGN(DisplayManager); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_DISPLAY_MANAGER_H_ diff --git a/chromium/components/mus/ws/display_manager_delegate.h b/chromium/components/mus/ws/display_manager_delegate.h new file mode 100644 index 00000000000..9d59e2fba8b --- /dev/null +++ b/chromium/components/mus/ws/display_manager_delegate.h @@ -0,0 +1,38 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_DISPLAY_MANAGER_DELEGATE_H_ +#define COMPONENTS_MUS_WS_DISPLAY_MANAGER_DELEGATE_H_ + +#include "components/mus/public/interfaces/display.mojom.h" +#include "components/mus/ws/user_id.h" + +namespace mus { +namespace ws { + +class Display; +class WindowManagerState; + +class DisplayManagerDelegate { + public: + virtual void OnFirstDisplayReady() = 0; + virtual void OnNoMoreDisplays() = 0; + + // Gets the frame decorations for the specified user. Returns true if the + // decorations have been set, false otherwise. |values| may be null. + virtual bool GetFrameDecorationsForUser( + const UserId& user_id, + mojom::FrameDecorationValuesPtr* values) = 0; + + virtual WindowManagerState* GetWindowManagerStateForUser( + const UserId& user_id) = 0; + + protected: + virtual ~DisplayManagerDelegate() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_DISPLAY_MANAGER_DELEGATE_H_ diff --git a/chromium/components/mus/ws/display_unittest.cc b/chromium/components/mus/ws/display_unittest.cc new file mode 100644 index 00000000000..dfc24ef25a8 --- /dev/null +++ b/chromium/components/mus/ws/display_unittest.cc @@ -0,0 +1,308 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <string> + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "components/mus/common/types.h" +#include "components/mus/common/util.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/test_utils.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { +namespace ws { +namespace test { +namespace { + +const UserId kTestId1 = "2"; +const UserId kTestId2 = "21"; + +ClientWindowId ClientWindowIdForFirstRoot(WindowTree* tree) { + if (tree->roots().empty()) + return ClientWindowId(); + return ClientWindowIdForWindow(tree, *tree->roots().begin()); +} + +WindowManagerState* GetWindowManagerStateForUser(Display* display, + const UserId& user_id) { + WindowManagerDisplayRoot* display_root = + display->GetWindowManagerDisplayRootForUser(user_id); + return display_root ? display_root->window_manager_state() : nullptr; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +class DisplayTest : public testing::Test { + public: + DisplayTest() : cursor_id_(0), platform_display_factory_(&cursor_id_) {} + ~DisplayTest() override {} + + protected: + // testing::Test: + void SetUp() override { + PlatformDisplay::set_factory_for_testing(&platform_display_factory_); + window_server_.reset(new WindowServer(&window_server_delegate_, + scoped_refptr<SurfacesState>())); + window_server_delegate_.set_window_server(window_server_.get()); + window_server_->user_id_tracker()->AddUserId(kTestId1); + window_server_->user_id_tracker()->AddUserId(kTestId2); + } + + protected: + int32_t cursor_id_; + TestPlatformDisplayFactory platform_display_factory_; + TestWindowServerDelegate window_server_delegate_; + std::unique_ptr<WindowServer> window_server_; + base::MessageLoop message_loop_; + + private: + DISALLOW_COPY_AND_ASSIGN(DisplayTest); +}; + +TEST_F(DisplayTest, CallsCreateDefaultDisplays) { + const int kNumHostsToCreate = 2; + window_server_delegate_.set_num_displays_to_create(kNumHostsToCreate); + + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + // The first register should trigger creation of the default + // Displays. There should be kNumHostsToCreate Displays. + EXPECT_EQ(static_cast<size_t>(kNumHostsToCreate), + display_manager->displays().size()); + + // Each host should have a WindowManagerState for kTestId1. + for (Display* display : display_manager->displays()) { + EXPECT_EQ(1u, display->num_window_manger_states()); + EXPECT_TRUE(GetWindowManagerStateForUser(display, kTestId1)); + EXPECT_FALSE(GetWindowManagerStateForUser(display, kTestId2)); + } + + // Add another registry, should trigger creation of another wm. + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId2); + for (Display* display : display_manager->displays()) { + ASSERT_EQ(2u, display->num_window_manger_states()); + WindowManagerDisplayRoot* root1 = + display->GetWindowManagerDisplayRootForUser(kTestId1); + ASSERT_TRUE(root1); + WindowManagerDisplayRoot* root2 = + display->GetWindowManagerDisplayRootForUser(kTestId2); + ASSERT_TRUE(root2); + // Verify the two states have different roots. + EXPECT_NE(root1, root2); + EXPECT_NE(root1->root(), root2->root()); + } +} + +TEST_F(DisplayTest, Destruction) { + window_server_delegate_.set_num_displays_to_create(1); + + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + + // Add another registry, should trigger creation of another wm. + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId2); + ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + ASSERT_EQ(2u, display->num_window_manger_states()); + // There should be two trees, one for each windowmanager. + EXPECT_EQ(2u, window_server_->num_trees()); + + { + WindowManagerState* state = GetWindowManagerStateForUser(display, kTestId1); + // Destroy the tree associated with |state|. Should result in deleting + // |state|. + window_server_->DestroyTree(state->window_tree()); + ASSERT_EQ(1u, display->num_window_manger_states()); + EXPECT_FALSE(GetWindowManagerStateForUser(display, kTestId1)); + EXPECT_EQ(1u, display_manager->displays().size()); + EXPECT_EQ(1u, window_server_->num_trees()); + } + + EXPECT_FALSE(window_server_delegate_.got_on_no_more_displays()); + window_server_->display_manager()->DestroyDisplay(display); + // There is still one tree left. + EXPECT_EQ(1u, window_server_->num_trees()); + EXPECT_TRUE(window_server_delegate_.got_on_no_more_displays()); +} + +TEST_F(DisplayTest, EventStateResetOnUserSwitch) { + window_server_delegate_.set_num_displays_to_create(1); + + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId2); + + window_server_->user_id_tracker()->SetActiveUserId(kTestId1); + + DisplayManager* display_manager = window_server_->display_manager(); + ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + WindowManagerState* active_wms = + display->GetActiveWindowManagerDisplayRoot()->window_manager_state(); + ASSERT_TRUE(active_wms); + EXPECT_EQ(kTestId1, active_wms->user_id()); + + static_cast<PlatformDisplayDelegate*>(display)->OnEvent(ui::PointerEvent( + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), + gfx::Point(20, 25), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON))); + + EXPECT_TRUE(EventDispatcherTestApi(active_wms->event_dispatcher()) + .AreAnyPointersDown()); + EXPECT_EQ(gfx::Point(20, 25), + active_wms->event_dispatcher()->mouse_pointer_last_location()); + + // Switch the user. Should trigger resetting state in old event dispatcher + // and update state in new event dispatcher. + window_server_->user_id_tracker()->SetActiveUserId(kTestId2); + EXPECT_NE( + active_wms, + display->GetActiveWindowManagerDisplayRoot()->window_manager_state()); + EXPECT_FALSE(EventDispatcherTestApi(active_wms->event_dispatcher()) + .AreAnyPointersDown()); + active_wms = + display->GetActiveWindowManagerDisplayRoot()->window_manager_state(); + EXPECT_EQ(kTestId2, active_wms->user_id()); + EXPECT_EQ(gfx::Point(20, 25), + active_wms->event_dispatcher()->mouse_pointer_last_location()); + EXPECT_FALSE(EventDispatcherTestApi(active_wms->event_dispatcher()) + .AreAnyPointersDown()); +} + +// Verifies capture fails when wm is inactive and succeeds when wm is active. +TEST_F(DisplayTest, SetCaptureFromWindowManager) { + window_server_delegate_.set_num_displays_to_create(1); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId2); + window_server_->user_id_tracker()->SetActiveUserId(kTestId1); + DisplayManager* display_manager = window_server_->display_manager(); + ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + WindowManagerState* wms_for_id2 = + GetWindowManagerStateForUser(display, kTestId2); + ASSERT_TRUE(wms_for_id2); + EXPECT_FALSE(wms_for_id2->IsActive()); + + // Create a child of the root that we can set capture on. + WindowTree* tree = wms_for_id2->window_tree(); + ClientWindowId child_window_id; + ASSERT_TRUE(NewWindowInTree(tree, &child_window_id)); + + WindowTreeTestApi(tree).EnableCapture(); + + // SetCapture() should fail for user id2 as it is inactive. + EXPECT_FALSE(tree->SetCapture(child_window_id)); + + // Make the second user active and verify capture works. + window_server_->user_id_tracker()->SetActiveUserId(kTestId2); + EXPECT_TRUE(wms_for_id2->IsActive()); + EXPECT_TRUE(tree->SetCapture(child_window_id)); +} + +TEST_F(DisplayTest, FocusFailsForInactiveUser) { + window_server_delegate_.set_num_displays_to_create(1); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + TestWindowTreeClient* window_tree_client1 = + window_server_delegate_.last_client(); + ASSERT_TRUE(window_tree_client1); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId2); + window_server_->user_id_tracker()->SetActiveUserId(kTestId1); + DisplayManager* display_manager = window_server_->display_manager(); + ASSERT_EQ(1u, display_manager->displays().size()); + Display* display = *display_manager->displays().begin(); + WindowManagerState* wms_for_id2 = + GetWindowManagerStateForUser(display, kTestId2); + wms_for_id2->window_tree()->AddActivationParent( + ClientWindowIdForFirstRoot(wms_for_id2->window_tree())); + ASSERT_TRUE(wms_for_id2); + EXPECT_FALSE(wms_for_id2->IsActive()); + ClientWindowId child2_id; + NewWindowInTree(wms_for_id2->window_tree(), &child2_id); + + // Focus should fail for windows in inactive window managers. + EXPECT_FALSE(wms_for_id2->window_tree()->SetFocus(child2_id)); + + // Focus should succeed for the active window manager. + WindowManagerState* wms_for_id1 = + GetWindowManagerStateForUser(display, kTestId1); + ASSERT_TRUE(wms_for_id1); + wms_for_id1->window_tree()->AddActivationParent( + ClientWindowIdForFirstRoot(wms_for_id1->window_tree())); + ClientWindowId child1_id; + NewWindowInTree(wms_for_id1->window_tree(), &child1_id); + EXPECT_TRUE(wms_for_id1->IsActive()); + EXPECT_TRUE(wms_for_id1->window_tree()->SetFocus(child1_id)); +} + +// Verifies a single tree is used for multiple displays. +TEST_F(DisplayTest, MultipleDisplays) { + window_server_delegate_.set_num_displays_to_create(2); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kTestId1); + window_server_->user_id_tracker()->SetActiveUserId(kTestId1); + ASSERT_EQ(1u, window_server_delegate_.bindings()->size()); + TestWindowTreeBinding* window_tree_binding = + (*window_server_delegate_.bindings())[0]; + WindowTree* tree = window_tree_binding->tree(); + ASSERT_EQ(2u, tree->roots().size()); + std::set<const ServerWindow*> roots = tree->roots(); + auto it = roots.begin(); + ServerWindow* root1 = tree->GetWindow((*it)->id()); + ++it; + ServerWindow* root2 = tree->GetWindow((*it)->id()); + ASSERT_NE(root1, root2); + Display* display1 = tree->GetDisplay(root1); + WindowManagerState* display1_wms = + display1->GetWindowManagerDisplayRootForUser(kTestId1) + ->window_manager_state(); + Display* display2 = tree->GetDisplay(root2); + WindowManagerState* display2_wms = + display2->GetWindowManagerDisplayRootForUser(kTestId1) + ->window_manager_state(); + EXPECT_EQ(display1_wms->window_tree(), display2_wms->window_tree()); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/event_dispatcher.cc b/chromium/components/mus/ws/event_dispatcher.cc new file mode 100644 index 00000000000..23874c3abc4 --- /dev/null +++ b/chromium/components/mus/ws/event_dispatcher.cc @@ -0,0 +1,559 @@ +// 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 "components/mus/ws/event_dispatcher.h" + +#include <algorithm> + +#include "base/time/time.h" +#include "components/mus/ws/accelerator.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/event_dispatcher_delegate.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/window_coordinate_conversions.h" +#include "components/mus/ws/window_finder.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" + +namespace mus { +namespace ws { + +using Entry = std::pair<uint32_t, std::unique_ptr<Accelerator>>; + +namespace { + +bool IsOnlyOneMouseButtonDown(int flags) { + const uint32_t button_only_flags = + flags & (ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON | + ui::EF_RIGHT_MOUSE_BUTTON); + return button_only_flags == ui::EF_LEFT_MOUSE_BUTTON || + button_only_flags == ui::EF_MIDDLE_MOUSE_BUTTON || + button_only_flags == ui::EF_RIGHT_MOUSE_BUTTON; +} + +bool IsLocationInNonclientArea(const ServerWindow* target, + const gfx::Point& location) { + if (!target->parent()) + return false; + + gfx::Rect client_area(target->bounds().size()); + client_area.Inset(target->client_area()); + if (client_area.Contains(location)) + return false; + + for (const auto& rect : target->additional_client_areas()) { + if (rect.Contains(location)) + return false; + } + + return true; +} + +uint32_t PointerId(const ui::LocatedEvent& event) { + if (event.IsPointerEvent()) + return event.AsPointerEvent()->pointer_id(); + if (event.IsMouseWheelEvent()) + return ui::PointerEvent::kMousePointerId; + + NOTREACHED(); + return 0; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +EventDispatcher::EventDispatcher(EventDispatcherDelegate* delegate) + : delegate_(delegate), + capture_window_(nullptr), + capture_window_client_id_(kInvalidClientId), + modal_window_controller_(this), + mouse_button_down_(false), + mouse_cursor_source_window_(nullptr), + mouse_cursor_in_non_client_area_(false) {} + +EventDispatcher::~EventDispatcher() { + if (capture_window_) { + UnobserveWindow(capture_window_); + capture_window_ = nullptr; + capture_window_client_id_ = kInvalidClientId; + } + for (const auto& pair : pointer_targets_) { + if (pair.second.window) + UnobserveWindow(pair.second.window); + } + pointer_targets_.clear(); +} + +void EventDispatcher::Reset() { + if (capture_window_) { + CancelPointerEventsToTarget(capture_window_); + DCHECK(capture_window_ == nullptr); + } + + while (!pointer_targets_.empty()) + StopTrackingPointer(pointer_targets_.begin()->first); + + mouse_button_down_ = false; +} + +void EventDispatcher::SetMousePointerScreenLocation( + const gfx::Point& screen_location) { + DCHECK(pointer_targets_.empty()); + mouse_pointer_last_location_ = screen_location; + UpdateCursorProviderByLastKnownLocation(); + // Write our initial location back to our shared screen coordinate. This + // shouldn't cause problems because we already read the cursor before we + // process any events in views during window construction. + delegate_->OnMouseCursorLocationChanged(screen_location); +} + +bool EventDispatcher::GetCurrentMouseCursor(int32_t* cursor_out) { + if (!mouse_cursor_source_window_) + return false; + + *cursor_out = mouse_cursor_in_non_client_area_ + ? mouse_cursor_source_window_->non_client_cursor() + : mouse_cursor_source_window_->cursor(); + return true; +} + +bool EventDispatcher::SetCaptureWindow(ServerWindow* window, + ClientSpecificId client_id) { + if (!window) + client_id = kInvalidClientId; + + if (window == capture_window_ && client_id == capture_window_client_id_) + return true; + + // A window that is blocked by a modal window cannot gain capture. + if (window && modal_window_controller_.IsWindowBlocked(window)) + return false; + + if (capture_window_) { + // Stop observing old capture window. |pointer_targets_| are cleared on + // initial setting of a capture window. + delegate_->OnServerWindowCaptureLost(capture_window_); + UnobserveWindow(capture_window_); + } else { + // Cancel implicit capture to all other windows. + for (const auto& pair : pointer_targets_) { + ServerWindow* target = pair.second.window; + if (!target) + continue; + UnobserveWindow(target); + if (target == window) + continue; + + ui::EventType event_type = pair.second.is_mouse_event + ? ui::ET_POINTER_EXITED + : ui::ET_POINTER_CANCELLED; + ui::EventPointerType pointer_type = + pair.second.is_mouse_event ? ui::EventPointerType::POINTER_TYPE_MOUSE + : ui::EventPointerType::POINTER_TYPE_TOUCH; + // TODO(jonross): Track previous location in PointerTarget for sending + // cancels. + ui::PointerEvent event( + event_type, gfx::Point(), gfx::Point(), ui::EF_NONE, pair.first, + ui::PointerDetails(pointer_type), ui::EventTimeForNow()); + DispatchToPointerTarget(pair.second, event); + } + pointer_targets_.clear(); + } + + // Set the capture before changing native capture; otherwise, the callback + // from native platform might try to set the capture again. + bool had_capture_window = capture_window_ != nullptr; + capture_window_ = window; + capture_window_client_id_ = client_id; + + // Begin tracking the capture window if it is not yet being observed. + if (window) { + ObserveWindow(window); + // TODO(sky): this conditional is problematic for the case of capture moving + // to a different display. + if (!had_capture_window) + delegate_->SetNativeCapture(window); + } else { + delegate_->ReleaseNativeCapture(); + if (!mouse_button_down_) + UpdateCursorProviderByLastKnownLocation(); + } + + return true; +} + +void EventDispatcher::AddSystemModalWindow(ServerWindow* window) { + modal_window_controller_.AddSystemModalWindow(window); +} + +void EventDispatcher::ReleaseCaptureBlockedByModalWindow( + const ServerWindow* modal_window) { + if (!capture_window_) + return; + + if (modal_window_controller_.IsWindowBlockedBy(capture_window_, + modal_window)) { + SetCaptureWindow(nullptr, kInvalidClientId); + } +} + +void EventDispatcher::ReleaseCaptureBlockedByAnyModalWindow() { + if (!capture_window_) + return; + + if (modal_window_controller_.IsWindowBlocked(capture_window_)) + SetCaptureWindow(nullptr, kInvalidClientId); +} + +void EventDispatcher::UpdateNonClientAreaForCurrentWindow() { + if (mouse_cursor_source_window_) { + gfx::Point location = mouse_pointer_last_location_; + ServerWindow* target = FindDeepestVisibleWindowForEvents(&location); + if (target == mouse_cursor_source_window_) { + mouse_cursor_in_non_client_area_ = + mouse_cursor_source_window_ + ? IsLocationInNonclientArea(mouse_cursor_source_window_, location) + : false; + } + } +} + +void EventDispatcher::UpdateCursorProviderByLastKnownLocation() { + if (!mouse_button_down_) { + gfx::Point location = mouse_pointer_last_location_; + mouse_cursor_source_window_ = FindDeepestVisibleWindowForEvents(&location); + + mouse_cursor_in_non_client_area_ = + mouse_cursor_source_window_ + ? IsLocationInNonclientArea(mouse_cursor_source_window_, location) + : false; + } +} + +bool EventDispatcher::AddAccelerator(uint32_t id, + mojom::EventMatcherPtr event_matcher) { + std::unique_ptr<Accelerator> accelerator(new Accelerator(id, *event_matcher)); + // If an accelerator with the same id or matcher already exists, then abort. + for (const auto& pair : accelerators_) { + if (pair.first == id || accelerator->EqualEventMatcher(pair.second.get())) + return false; + } + accelerators_.insert(Entry(id, std::move(accelerator))); + return true; +} + +void EventDispatcher::RemoveAccelerator(uint32_t id) { + auto it = accelerators_.find(id); + // Clients may pass bogus ids. + if (it != accelerators_.end()) + accelerators_.erase(it); +} + +void EventDispatcher::ProcessEvent(const ui::Event& event) { + if (event.IsKeyEvent()) { + const ui::KeyEvent* key_event = event.AsKeyEvent(); + if (event.type() == ui::ET_KEY_PRESSED && !key_event->is_char()) { + Accelerator* pre_target = + FindAccelerator(*key_event, ui::mojom::AcceleratorPhase::PRE_TARGET); + if (pre_target) { + delegate_->OnAccelerator(pre_target->id(), event); + return; + } + } + ProcessKeyEvent(*key_event); + return; + } + + if (event.IsPointerEvent() || event.IsMouseWheelEvent()) { + ProcessLocatedEvent(*event.AsLocatedEvent()); + return; + } + + NOTREACHED(); +} + +void EventDispatcher::ProcessKeyEvent(const ui::KeyEvent& event) { + Accelerator* post_target = + FindAccelerator(event, ui::mojom::AcceleratorPhase::POST_TARGET); + ServerWindow* focused_window = + delegate_->GetFocusedWindowForEventDispatcher(); + if (focused_window) { + // Assume key events are for the client area. + const bool in_nonclient_area = false; + const ClientSpecificId client_id = + delegate_->GetEventTargetClientId(focused_window, in_nonclient_area); + delegate_->DispatchInputEventToWindow(focused_window, client_id, event, + post_target); + return; + } + delegate_->OnEventTargetNotFound(event); + if (post_target) + delegate_->OnAccelerator(post_target->id(), event); +} + +void EventDispatcher::ProcessLocatedEvent(const ui::LocatedEvent& event) { + DCHECK(event.IsPointerEvent() || event.IsMouseWheelEvent()); + const bool is_mouse_event = + event.IsMousePointerEvent() || event.IsMouseWheelEvent(); + + if (is_mouse_event) { + mouse_pointer_last_location_ = event.location(); + delegate_->OnMouseCursorLocationChanged(event.root_location()); + } + + // Release capture on pointer up. For mouse we only release if there are + // no buttons down. + const bool is_pointer_going_up = + (event.type() == ui::ET_POINTER_UP || + event.type() == ui::ET_POINTER_CANCELLED) && + (!is_mouse_event || IsOnlyOneMouseButtonDown(event.flags())); + + // Update mouse down state upon events which change it. + if (is_mouse_event) { + if (event.type() == ui::ET_POINTER_DOWN) + mouse_button_down_ = true; + else if (is_pointer_going_up) + mouse_button_down_ = false; + } + + if (capture_window_) { + mouse_cursor_source_window_ = capture_window_; + DispatchToClient(capture_window_, capture_window_client_id_, event); + return; + } + + const int32_t pointer_id = PointerId(event); + if (!IsTrackingPointer(pointer_id) || + !pointer_targets_[pointer_id].is_pointer_down) { + const bool any_pointers_down = AreAnyPointersDown(); + UpdateTargetForPointer(pointer_id, event); + if (is_mouse_event) + mouse_cursor_source_window_ = pointer_targets_[pointer_id].window; + + PointerTarget& pointer_target = pointer_targets_[pointer_id]; + if (pointer_target.is_pointer_down) { + if (is_mouse_event) + mouse_cursor_source_window_ = pointer_target.window; + if (!any_pointers_down) { + delegate_->SetFocusedWindowFromEventDispatcher(pointer_target.window); + delegate_->SetNativeCapture(pointer_target.window); + } + } + } + + // When we release the mouse button, we want the cursor to be sourced from + // the window under the mouse pointer, even though we're sending the button + // up event to the window that had implicit capture. We have to set this + // before we perform dispatch because the Delegate is going to read this + // information from us. + if (is_pointer_going_up && is_mouse_event) + UpdateCursorProviderByLastKnownLocation(); + + DispatchToPointerTarget(pointer_targets_[pointer_id], event); + + if (is_pointer_going_up) { + if (is_mouse_event) + pointer_targets_[pointer_id].is_pointer_down = false; + else + StopTrackingPointer(pointer_id); + if (!AreAnyPointersDown()) + delegate_->ReleaseNativeCapture(); + } +} + +void EventDispatcher::StartTrackingPointer( + int32_t pointer_id, + const PointerTarget& pointer_target) { + DCHECK(!IsTrackingPointer(pointer_id)); + ObserveWindow(pointer_target.window); + pointer_targets_[pointer_id] = pointer_target; +} + +void EventDispatcher::StopTrackingPointer(int32_t pointer_id) { + DCHECK(IsTrackingPointer(pointer_id)); + ServerWindow* window = pointer_targets_[pointer_id].window; + pointer_targets_.erase(pointer_id); + if (window) + UnobserveWindow(window); +} + +void EventDispatcher::UpdateTargetForPointer(int32_t pointer_id, + const ui::LocatedEvent& event) { + if (!IsTrackingPointer(pointer_id)) { + StartTrackingPointer(pointer_id, PointerTargetForEvent(event)); + return; + } + + const PointerTarget pointer_target = PointerTargetForEvent(event); + if (pointer_target.window == pointer_targets_[pointer_id].window && + pointer_target.in_nonclient_area == + pointer_targets_[pointer_id].in_nonclient_area) { + // The targets are the same, only set the down state to true if necessary. + // Down going to up is handled by ProcessLocatedEvent(). + if (pointer_target.is_pointer_down) + pointer_targets_[pointer_id].is_pointer_down = true; + return; + } + + // The targets are changing. Send an exit if appropriate. + if (event.IsMousePointerEvent()) { + ui::PointerEvent exit_event( + ui::ET_POINTER_EXITED, event.location(), event.root_location(), + event.flags(), ui::PointerEvent::kMousePointerId, + ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE), + event.time_stamp()); + DispatchToPointerTarget(pointer_targets_[pointer_id], exit_event); + } + + // Technically we're updating in place, but calling start then stop makes for + // simpler code. + StopTrackingPointer(pointer_id); + StartTrackingPointer(pointer_id, pointer_target); +} + +EventDispatcher::PointerTarget EventDispatcher::PointerTargetForEvent( + const ui::LocatedEvent& event) { + PointerTarget pointer_target; + gfx::Point location(event.location()); + ServerWindow* target_window = FindDeepestVisibleWindowForEvents(&location); + pointer_target.window = + modal_window_controller_.GetTargetForWindow(target_window); + pointer_target.is_mouse_event = event.IsMousePointerEvent(); + pointer_target.in_nonclient_area = + target_window != pointer_target.window || + IsLocationInNonclientArea(pointer_target.window, location); + pointer_target.is_pointer_down = event.type() == ui::ET_POINTER_DOWN; + return pointer_target; +} + +bool EventDispatcher::AreAnyPointersDown() const { + for (const auto& pair : pointer_targets_) { + if (pair.second.is_pointer_down) + return true; + } + return false; +} + +void EventDispatcher::DispatchToPointerTarget(const PointerTarget& target, + const ui::LocatedEvent& event) { + if (!target.window) { + delegate_->OnEventTargetNotFound(event); + return; + } + + if (target.is_mouse_event) + mouse_cursor_in_non_client_area_ = target.in_nonclient_area; + + DispatchToClient(target.window, delegate_->GetEventTargetClientId( + target.window, target.in_nonclient_area), + event); +} + +void EventDispatcher::DispatchToClient(ServerWindow* window, + ClientSpecificId client_id, + const ui::LocatedEvent& event) { + gfx::Point location(event.location()); + gfx::Transform transform(GetTransformToWindow(window)); + transform.TransformPoint(&location); + std::unique_ptr<ui::Event> clone = ui::Event::Clone(event); + clone->AsLocatedEvent()->set_location(location); + // TODO(jonross): add post-target accelerator support once accelerators + // support pointer events. + delegate_->DispatchInputEventToWindow(window, client_id, *clone, nullptr); +} + +void EventDispatcher::CancelPointerEventsToTarget(ServerWindow* window) { + if (capture_window_ == window) { + UnobserveWindow(window); + capture_window_ = nullptr; + capture_window_client_id_ = kInvalidClientId; + mouse_button_down_ = false; + // A window only cares to be informed that it lost capture if it explicitly + // requested capture. A window can lose capture if another window gains + // explicit capture. + delegate_->OnServerWindowCaptureLost(window); + delegate_->ReleaseNativeCapture(); + UpdateCursorProviderByLastKnownLocation(); + return; + } + + for (auto& pair : pointer_targets_) { + if (pair.second.window == window) { + UnobserveWindow(window); + pair.second.window = nullptr; + } + } +} + +void EventDispatcher::ObserveWindow(ServerWindow* window) { + auto res = observed_windows_.insert(std::make_pair(window, 0u)); + res.first->second++; + if (res.second) + window->AddObserver(this); +} + +void EventDispatcher::UnobserveWindow(ServerWindow* window) { + auto it = observed_windows_.find(window); + DCHECK(it != observed_windows_.end()); + DCHECK_LT(0u, it->second); + it->second--; + if (!it->second) { + window->RemoveObserver(this); + observed_windows_.erase(it); + } +} + +Accelerator* EventDispatcher::FindAccelerator( + const ui::KeyEvent& event, + const ui::mojom::AcceleratorPhase phase) { + for (const auto& pair : accelerators_) { + if (pair.second->MatchesEvent(event, phase)) { + return pair.second.get(); + } + } + return nullptr; +} + +ServerWindow* EventDispatcher::FindDeepestVisibleWindowForEvents( + gfx::Point* location) { + ServerWindow* root = delegate_->GetRootWindowContaining(*location); + if (!root) + return nullptr; + + return mus::ws::FindDeepestVisibleWindowForEvents(root, location); +} + +void EventDispatcher::OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) { + // TODO(sky): moving to a different root likely needs to transfer capture. + // TODO(sky): this isn't quite right, I think the logic should be (assuming + // moving in same root and still drawn): + // . if there is capture and window is still in the same root, continue + // sending to it. + // . if there isn't capture, then reevaluate each of the pointer targets + // sending exit as necessary. + // http://crbug.com/613646 . + if (!new_parent || !new_parent->IsDrawn() || + new_parent->GetRoot() != old_parent->GetRoot()) { + CancelPointerEventsToTarget(window); + } +} + +void EventDispatcher::OnWindowVisibilityChanged(ServerWindow* window) { + CancelPointerEventsToTarget(window); +} + +void EventDispatcher::OnWindowDestroyed(ServerWindow* window) { + CancelPointerEventsToTarget(window); + + if (mouse_cursor_source_window_ == window) + mouse_cursor_source_window_ = nullptr; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/event_dispatcher.h b/chromium/components/mus/ws/event_dispatcher.h new file mode 100644 index 00000000000..a6178073981 --- /dev/null +++ b/chromium/components/mus/ws/event_dispatcher.h @@ -0,0 +1,236 @@ +// 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 COMPONENTS_MUS_WS_EVENT_DISPATCHER_H_ +#define COMPONENTS_MUS_WS_EVENT_DISPATCHER_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <utility> + +#include "base/macros.h" +#include "components/mus/common/types.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "components/mus/ws/modal_window_controller.h" +#include "components/mus/ws/server_window_observer.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace ui { +class Event; +class KeyEvent; +class LocatedEvent; +} + +namespace mus { +namespace ws { + +class Accelerator; +class EventDispatcherDelegate; +class ServerWindow; + +namespace test { +class EventDispatcherTestApi; +} + +// Handles dispatching events to the right location as well as updating focus. +class EventDispatcher : public ServerWindowObserver { + public: + explicit EventDispatcher(EventDispatcherDelegate* delegate); + ~EventDispatcher() override; + + // Cancels capture and stops tracking any pointer events. This does not send + // any events to the delegate. + void Reset(); + + void SetMousePointerScreenLocation(const gfx::Point& screen_location); + const gfx::Point& mouse_pointer_last_location() const { + return mouse_pointer_last_location_; + } + + // If we still have the window of the last mouse move, returns true and sets + // the current cursor to use to |cursor_out|. + bool GetCurrentMouseCursor(int32_t* cursor_out); + + // |capture_window_| will receive all input. See window_tree.mojom for + // details. + ServerWindow* capture_window() { return capture_window_; } + const ServerWindow* capture_window() const { return capture_window_; } + // Setting capture can fail if the window is blocked by a modal window + // (indicated by returning |false|). + bool SetCaptureWindow(ServerWindow* capture_window, + ClientSpecificId client_id); + + // Id of the client that capture events are sent to. + ClientSpecificId capture_window_client_id() const { + return capture_window_client_id_; + } + + // Adds a system modal window. The window remains modal to system until it is + // destroyed. There can exist multiple system modal windows, in which case the + // one that is visible and added most recently or shown most recently would be + // the active one. + void AddSystemModalWindow(ServerWindow* window); + + // Checks if |modal_window| is a visible modal window that blocks current + // capture window and if that's the case, releases the capture. + void ReleaseCaptureBlockedByModalWindow(const ServerWindow* modal_window); + + // Checks if the current capture window is blocked by any visible modal window + // and if that's the case, releases the capture. + void ReleaseCaptureBlockedByAnyModalWindow(); + + // Retrieves the ServerWindow of the last mouse move. + ServerWindow* mouse_cursor_source_window() const { + return mouse_cursor_source_window_; + } + + // If the mouse cursor is still over |mouse_cursor_source_window_|, updates + // whether we are in the non-client area. Used when + // |mouse_cursor_source_window_| has changed its properties. + void UpdateNonClientAreaForCurrentWindow(); + + // Possibly updates the cursor. If we aren't in an implicit capture, we take + // the last known location of the mouse pointer, and look for the + // ServerWindow* under it. + void UpdateCursorProviderByLastKnownLocation(); + + // Adds an accelerator with the given id and event-matcher. If an accelerator + // already exists with the same id or the same matcher, then the accelerator + // is not added. Returns whether adding the accelerator was successful or not. + bool AddAccelerator(uint32_t id, mojom::EventMatcherPtr event_matcher); + void RemoveAccelerator(uint32_t id); + + // Processes the supplied event, informing the delegate as approriate. This + // may result in generating any number of events. + void ProcessEvent(const ui::Event& event); + + private: + friend class test::EventDispatcherTestApi; + + // Keeps track of state associated with an active pointer. + struct PointerTarget { + PointerTarget() + : window(nullptr), + is_mouse_event(false), + in_nonclient_area(false), + is_pointer_down(false) {} + + // NOTE: this is set to null if the window is destroyed before a + // corresponding release/cancel. + ServerWindow* window; + + bool is_mouse_event; + + // Did the pointer event start in the non-client area. + bool in_nonclient_area; + + bool is_pointer_down; + }; + + void ProcessKeyEvent(const ui::KeyEvent& event); + + bool IsTrackingPointer(int32_t pointer_id) const { + return pointer_targets_.count(pointer_id) > 0; + } + + // EventDispatcher provides the following logic for pointer events: + // . wheel events go to the current target of the associated pointer. If + // there is no target, they go to the deepest window. + // . move (not drag) events go to the deepest window. + // . when a pointer goes down all events until the corresponding up or + // cancel go to the deepest target. For mouse events the up only occurs + // when no buttons on the mouse are down. + // This also generates exit events as appropriate. For example, if the mouse + // moves between one window to another an exit is generated on the first. + void ProcessLocatedEvent(const ui::LocatedEvent& event); + + // Adds |pointer_target| to |pointer_targets_|. + void StartTrackingPointer(int32_t pointer_id, + const PointerTarget& pointer_target); + + // Removes a PointerTarget from |pointer_targets_|. + void StopTrackingPointer(int32_t pointer_id); + + // Starts tracking the pointer for |event|, or if already tracking the + // pointer sends the appropriate event to the delegate and updates the + // currently tracked PointerTarget appropriately. + void UpdateTargetForPointer(int32_t pointer_id, + const ui::LocatedEvent& event); + + // Returns a PointerTarget from the supplied event. + PointerTarget PointerTargetForEvent(const ui::LocatedEvent& event); + + // Returns true if any pointers are in the pressed/down state. + bool AreAnyPointersDown() const; + + // If |target->window| is valid, then passes the event to the delegate. + void DispatchToPointerTarget(const PointerTarget& target, + const ui::LocatedEvent& event); + + // Dispatch |event| to the delegate. + void DispatchToClient(ServerWindow* window, + ClientSpecificId client_id, + const ui::LocatedEvent& event); + + // Stops sending pointer events to |window|. This does not remove the entry + // for |window| from |pointer_targets_|, rather it nulls out the window. This + // way we continue to eat events until the up/cancel is received. + void CancelPointerEventsToTarget(ServerWindow* window); + + // Used to observe a window. Can be called multiple times on a window. To + // unobserve a window, UnobserveWindow() should be called the same number of + // times. + void ObserveWindow(ServerWindow* winodw); + void UnobserveWindow(ServerWindow* winodw); + + // Returns an Accelerator bound to the specified code/flags, and of the + // matching |phase|. Otherwise returns null. + Accelerator* FindAccelerator(const ui::KeyEvent& event, + const ui::mojom::AcceleratorPhase phase); + + ServerWindow* FindDeepestVisibleWindowForEvents(gfx::Point* location); + + // ServerWindowObserver: + void OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) override; + void OnWindowVisibilityChanged(ServerWindow* window) override; + void OnWindowDestroyed(ServerWindow* window) override; + + EventDispatcherDelegate* delegate_; + + ServerWindow* capture_window_; + ClientSpecificId capture_window_client_id_; + + ModalWindowController modal_window_controller_; + + bool mouse_button_down_; + ServerWindow* mouse_cursor_source_window_; + bool mouse_cursor_in_non_client_area_; + + // The on screen location of the mouse pointer. This can be outside the + // bounds of |mouse_cursor_source_window_|, which can capture the cursor. + gfx::Point mouse_pointer_last_location_; + + std::map<uint32_t, std::unique_ptr<Accelerator>> accelerators_; + + using PointerIdToTargetMap = std::map<int32_t, PointerTarget>; + // |pointer_targets_| contains the active pointers. For a mouse based pointer + // a PointerTarget is always active (and present in |pointer_targets_|). For + // touch based pointers the pointer is active while down and removed on + // cancel or up. + PointerIdToTargetMap pointer_targets_; + + // Keeps track of number of observe requests for each observed window. + std::map<const ServerWindow*, uint8_t> observed_windows_; + + DISALLOW_COPY_AND_ASSIGN(EventDispatcher); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_EVENT_DISPATCHER_H_ diff --git a/chromium/components/mus/ws/event_dispatcher_delegate.h b/chromium/components/mus/ws/event_dispatcher_delegate.h new file mode 100644 index 00000000000..1d31fb99d50 --- /dev/null +++ b/chromium/components/mus/ws/event_dispatcher_delegate.h @@ -0,0 +1,73 @@ +// 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 COMPONENTS_MUS_WS_EVENT_DISPATCHER_DELEGATE_H_ +#define COMPONENTS_MUS_WS_EVENT_DISPATCHER_DELEGATE_H_ + +#include <stdint.h> + +#include "components/mus/common/types.h" + +namespace gfx { +class Point; +} + +namespace ui { +class Event; +} + +namespace mus { +namespace ws { + +class Accelerator; +class ServerWindow; + +// Used by EventDispatcher for mocking in tests. +class EventDispatcherDelegate { + public: + virtual void OnAccelerator(uint32_t accelerator, const ui::Event& event) = 0; + + virtual void SetFocusedWindowFromEventDispatcher(ServerWindow* window) = 0; + virtual ServerWindow* GetFocusedWindowForEventDispatcher() = 0; + + // Called when capture should be set on the native display. |window| is the + // window capture is being set on. + virtual void SetNativeCapture(ServerWindow* window) = 0; + // Called when the native display is having capture released. There is no + // longer a ServerWindow holding capture. + virtual void ReleaseNativeCapture() = 0; + // Called when |window| has lost capture. The native display may still be + // holding capture. The delegate should not change native display capture. + // ReleaseNativeCapture() is invoked if appropriate. + virtual void OnServerWindowCaptureLost(ServerWindow* window) = 0; + + virtual void OnMouseCursorLocationChanged(const gfx::Point& point) = 0; + + // Dispatches an event to the specific client. + virtual void DispatchInputEventToWindow(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + Accelerator* accelerator) = 0; + + // Returns the id of the client to send events to. |in_nonclient_area| is + // true if the event occurred in the non-client area of the window. + virtual ClientSpecificId GetEventTargetClientId(const ServerWindow* window, + bool in_nonclient_area) = 0; + + // Returns the window to start searching from at the specified location, or + // null if there is a no window containing |location|. + virtual ServerWindow* GetRootWindowContaining(const gfx::Point& location) = 0; + + // Called when event dispatch could not find a target. OnAccelerator may still + // be called. + virtual void OnEventTargetNotFound(const ui::Event& event) = 0; + + protected: + virtual ~EventDispatcherDelegate() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_EVENT_DISPATCHER_DELEGATE_H_ diff --git a/chromium/components/mus/ws/event_dispatcher_unittest.cc b/chromium/components/mus/ws/event_dispatcher_unittest.cc new file mode 100644 index 00000000000..e47e961aff4 --- /dev/null +++ b/chromium/components/mus/ws/event_dispatcher_unittest.cc @@ -0,0 +1,1614 @@ +// 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 "components/mus/ws/event_dispatcher.h" + +#include <stddef.h> +#include <stdint.h> + +#include <queue> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "components/mus/common/event_matcher_util.h" +#include "components/mus/ws/accelerator.h" +#include "components/mus/ws/event_dispatcher_delegate.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "components/mus/ws/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" + +namespace mus { +namespace ws { +namespace test { +namespace { + +// Client ids used to indicate the client area and non-client area. +const ClientSpecificId kClientAreaId = 11; +const ClientSpecificId kNonclientAreaId = 111; + +// Identifies a generated event. +struct DispatchedEventDetails { + DispatchedEventDetails() + : window(nullptr), client_id(kInvalidClientId), accelerator(nullptr) {} + + bool IsNonclientArea() const { return client_id == kNonclientAreaId; } + bool IsClientArea() const { return client_id == kClientAreaId; } + + ServerWindow* window; + ClientSpecificId client_id; + std::unique_ptr<ui::Event> event; + Accelerator* accelerator; +}; + +class TestEventDispatcherDelegate : public EventDispatcherDelegate { + public: + // Delegate interface used by this class to release capture on event + // dispatcher. + class Delegate { + public: + virtual void ReleaseCapture() = 0; + }; + + explicit TestEventDispatcherDelegate(Delegate* delegate) + : delegate_(delegate), + focused_window_(nullptr), + lost_capture_window_(nullptr), + last_accelerator_(0) {} + ~TestEventDispatcherDelegate() override {} + + ui::Event* last_event_target_not_found() { + return last_event_target_not_found_.get(); + } + + uint32_t GetAndClearLastAccelerator() { + uint32_t return_value = last_accelerator_; + last_accelerator_ = 0; + return return_value; + } + + void set_root(ServerWindow* root) { root_ = root; } + + // Returns the last dispatched event, or null if there are no more. + std::unique_ptr<DispatchedEventDetails> + GetAndAdvanceDispatchedEventDetails() { + if (dispatched_event_queue_.empty()) + return nullptr; + + std::unique_ptr<DispatchedEventDetails> details = + std::move(dispatched_event_queue_.front()); + dispatched_event_queue_.pop(); + return details; + } + + ServerWindow* GetAndClearLastFocusedWindow() { + ServerWindow* result = focused_window_; + focused_window_ = nullptr; + return result; + } + + bool has_queued_events() const { return !dispatched_event_queue_.empty(); } + ServerWindow* lost_capture_window() { return lost_capture_window_; } + + // EventDispatcherDelegate: + void SetFocusedWindowFromEventDispatcher(ServerWindow* window) override { + focused_window_ = window; + } + + private: + // EventDispatcherDelegate: + void OnAccelerator(uint32_t accelerator, const ui::Event& event) override { + EXPECT_EQ(0u, last_accelerator_); + last_accelerator_ = accelerator; + } + ServerWindow* GetFocusedWindowForEventDispatcher() override { + return focused_window_; + } + void SetNativeCapture(ServerWindow* window) override {} + void ReleaseNativeCapture() override { + if (delegate_) + delegate_->ReleaseCapture(); + } + void OnServerWindowCaptureLost(ServerWindow* window) override { + lost_capture_window_ = window; + } + void OnMouseCursorLocationChanged(const gfx::Point& point) override {} + void DispatchInputEventToWindow(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + Accelerator* accelerator) override { + std::unique_ptr<DispatchedEventDetails> details(new DispatchedEventDetails); + details->window = target; + details->client_id = client_id; + details->event = ui::Event::Clone(event); + details->accelerator = accelerator; + dispatched_event_queue_.push(std::move(details)); + } + ClientSpecificId GetEventTargetClientId(const ServerWindow* window, + bool in_nonclient_area) override { + return in_nonclient_area ? kNonclientAreaId : kClientAreaId; + } + ServerWindow* GetRootWindowContaining(const gfx::Point& location) override { + return root_; + } + void OnEventTargetNotFound(const ui::Event& event) override { + last_event_target_not_found_ = ui::Event::Clone(event); + } + + Delegate* delegate_; + ServerWindow* focused_window_; + ServerWindow* lost_capture_window_; + uint32_t last_accelerator_; + std::queue<std::unique_ptr<DispatchedEventDetails>> dispatched_event_queue_; + ServerWindow* root_ = nullptr; + std::unique_ptr<ui::Event> last_event_target_not_found_; + + DISALLOW_COPY_AND_ASSIGN(TestEventDispatcherDelegate); +}; + +// Used by RunMouseEventTests(). Can identify up to two generated events. The +// first ServerWindow and two points identify the first event, the second +// ServerWindow and points identify the second event. If only one event is +// generated set the second window to null. +struct MouseEventTest { + ui::MouseEvent input_event; + ServerWindow* expected_target_window1; + gfx::Point expected_root_location1; + gfx::Point expected_location1; + ServerWindow* expected_target_window2; + gfx::Point expected_root_location2; + gfx::Point expected_location2; +}; + +// Verifies |details| matches the supplied ServerWindow and points. +void ExpectDispatchedEventDetailsMatches(const DispatchedEventDetails* details, + ServerWindow* target, + const gfx::Point& root_location, + const gfx::Point& location) { + if (!target) { + ASSERT_FALSE(details); + return; + } + + ASSERT_EQ(target, details->window); + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsLocatedEvent()); + ASSERT_TRUE(details->IsClientArea()); + ASSERT_EQ(root_location, details->event->AsLocatedEvent()->root_location()); + ASSERT_EQ(location, details->event->AsLocatedEvent()->location()); +} + +void RunMouseEventTests(EventDispatcher* dispatcher, + TestEventDispatcherDelegate* dispatcher_delegate, + MouseEventTest* tests, + size_t test_count) { + for (size_t i = 0; i < test_count; ++i) { + const MouseEventTest& test = tests[i]; + ASSERT_FALSE(dispatcher_delegate->has_queued_events()) + << " unexpected queued events before running " << i; + if (test.input_event.IsMouseWheelEvent()) + dispatcher->ProcessEvent(test.input_event); + else + dispatcher->ProcessEvent(ui::PointerEvent(test.input_event)); + + std::unique_ptr<DispatchedEventDetails> details = + dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_NO_FATAL_FAILURE(ExpectDispatchedEventDetailsMatches( + details.get(), test.expected_target_window1, + test.expected_root_location1, test.expected_location1)) + << " details don't match " << i; + details = dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_NO_FATAL_FAILURE(ExpectDispatchedEventDetailsMatches( + details.get(), test.expected_target_window2, + test.expected_root_location2, test.expected_location2)) + << " details2 don't match " << i; + ASSERT_FALSE(dispatcher_delegate->has_queued_events()) + << " unexpected queued events after running " << i; + } +} + +} // namespace + +// Test fixture for EventDispatcher with friend access to verify the internal +// state. Setup creates a TestServerWindowDelegate, a visible root ServerWindow, +// a TestEventDispatcher and the EventDispatcher for testing. +class EventDispatcherTest : public testing::Test, + public TestEventDispatcherDelegate::Delegate { + public: + EventDispatcherTest() {} + ~EventDispatcherTest() override {} + + ServerWindow* root_window() { return root_window_.get(); } + TestEventDispatcherDelegate* test_event_dispatcher_delegate() { + return test_event_dispatcher_delegate_.get(); + } + EventDispatcher* event_dispatcher() { return event_dispatcher_.get(); } + + bool AreAnyPointersDown() const; + // Deletes everything created during SetUp() + void ClearSetup(); + std::unique_ptr<ServerWindow> CreateChildWindowWithParent( + const WindowId& id, + ServerWindow* parent); + // Creates a window which is a child of |root_window_|. + std::unique_ptr<ServerWindow> CreateChildWindow(const WindowId& id); + bool IsMouseButtonDown() const; + bool IsWindowPointerTarget(const ServerWindow* window) const; + int NumberPointerTargetsForWindow(ServerWindow* window) const; + ServerWindow* GetActiveSystemModalWindow() const; + + protected: + // testing::Test: + void SetUp() override; + + private: + // TestEventDispatcherDelegate::Delegate: + void ReleaseCapture() override { + event_dispatcher_->SetCaptureWindow(nullptr, kInvalidClientId); + } + + std::unique_ptr<TestServerWindowDelegate> window_delegate_; + std::unique_ptr<ServerWindow> root_window_; + std::unique_ptr<TestEventDispatcherDelegate> test_event_dispatcher_delegate_; + std::unique_ptr<EventDispatcher> event_dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(EventDispatcherTest); +}; + +bool EventDispatcherTest::AreAnyPointersDown() const { + return EventDispatcherTestApi(event_dispatcher_.get()).AreAnyPointersDown(); +} + +void EventDispatcherTest::ClearSetup() { + window_delegate_.reset(); + root_window_.reset(); + test_event_dispatcher_delegate_.reset(); + event_dispatcher_.reset(); +} + +std::unique_ptr<ServerWindow> EventDispatcherTest::CreateChildWindowWithParent( + const WindowId& id, + ServerWindow* parent) { + std::unique_ptr<ServerWindow> child( + new ServerWindow(window_delegate_.get(), id)); + parent->Add(child.get()); + child->SetVisible(true); + EnableHitTest(child.get()); + return child; +} + +std::unique_ptr<ServerWindow> EventDispatcherTest::CreateChildWindow( + const WindowId& id) { + return CreateChildWindowWithParent(id, root_window_.get()); +} + +bool EventDispatcherTest::IsMouseButtonDown() const { + return EventDispatcherTestApi(event_dispatcher_.get()).is_mouse_button_down(); +} + +bool EventDispatcherTest::IsWindowPointerTarget( + const ServerWindow* window) const { + return EventDispatcherTestApi(event_dispatcher_.get()) + .IsWindowPointerTarget(window); +} + +int EventDispatcherTest::NumberPointerTargetsForWindow( + ServerWindow* window) const { + return EventDispatcherTestApi(event_dispatcher_.get()) + .NumberPointerTargetsForWindow(window); +} + +ServerWindow* EventDispatcherTest::GetActiveSystemModalWindow() const { + ModalWindowController* mwc = + EventDispatcherTestApi(event_dispatcher_.get()).modal_window_controller(); + return ModalWindowControllerTestApi(mwc).GetActiveSystemModalWindow(); +} + +void EventDispatcherTest::SetUp() { + testing::Test::SetUp(); + + window_delegate_.reset(new TestServerWindowDelegate()); + root_window_.reset(new ServerWindow(window_delegate_.get(), WindowId(1, 2))); + window_delegate_->set_root_window(root_window_.get()); + root_window_->SetVisible(true); + + test_event_dispatcher_delegate_.reset(new TestEventDispatcherDelegate(this)); + event_dispatcher_.reset( + new EventDispatcher(test_event_dispatcher_delegate_.get())); + test_event_dispatcher_delegate_->set_root(root_window_.get()); +} + +TEST_F(EventDispatcherTest, ProcessEvent) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + // Send event that is over child. + const ui::PointerEvent ui_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(ui_event); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(20, 25), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(10, 15), dispatched_event->location()); +} + +TEST_F(EventDispatcherTest, ProcessEventNoTarget) { + // Send event without a target. + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE); + event_dispatcher()->ProcessEvent(key); + + // Event wasn't dispatched to a target. + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(details); + + // Delegate was informed that there wasn't a target. + ui::Event* event_out = + test_event_dispatcher_delegate()->last_event_target_not_found(); + ASSERT_TRUE(event_out); + EXPECT_TRUE(event_out->IsKeyEvent()); + EXPECT_EQ(ui::VKEY_A, event_out->AsKeyEvent()->key_code()); +} + +TEST_F(EventDispatcherTest, AcceleratorBasic) { + ClearSetup(); + TestEventDispatcherDelegate event_dispatcher_delegate(nullptr); + EventDispatcher dispatcher(&event_dispatcher_delegate); + + uint32_t accelerator_1 = 1; + mojom::EventMatcherPtr matcher = mus::CreateKeyMatcher( + ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown); + EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_1, std::move(matcher))); + + uint32_t accelerator_2 = 2; + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::N, + ui::mojom::kEventFlagNone); + EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher))); + + // Attempting to add a new accelerator with the same id should fail. + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::T, + ui::mojom::kEventFlagNone); + EXPECT_FALSE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher))); + + // Adding the accelerator with the same id should succeed once the existing + // accelerator is removed. + dispatcher.RemoveAccelerator(accelerator_2); + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::T, + ui::mojom::kEventFlagNone); + EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher))); + + // Attempting to add an accelerator with the same matcher should fail. + uint32_t accelerator_3 = 3; + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::T, + ui::mojom::kEventFlagNone); + EXPECT_FALSE(dispatcher.AddAccelerator(accelerator_3, std::move(matcher))); + + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::T, + ui::mojom::kEventFlagControlDown); + EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_3, std::move(matcher))); +} + +TEST_F(EventDispatcherTest, EventMatching) { + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + mojom::EventMatcherPtr matcher = mus::CreateKeyMatcher( + ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown); + uint32_t accelerator_1 = 1; + dispatcher->AddAccelerator(accelerator_1, std::move(matcher)); + + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + dispatcher->ProcessEvent(key); + EXPECT_EQ(accelerator_1, + event_dispatcher_delegate->GetAndClearLastAccelerator()); + + // EF_NUM_LOCK_ON should be ignored since CreateKeyMatcher defaults to + // ignoring. + key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_W, + ui::EF_CONTROL_DOWN | ui::EF_NUM_LOCK_ON); + dispatcher->ProcessEvent(key); + EXPECT_EQ(accelerator_1, + event_dispatcher_delegate->GetAndClearLastAccelerator()); + + key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_NONE); + dispatcher->ProcessEvent(key); + EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator()); + + uint32_t accelerator_2 = 2; + matcher = mus::CreateKeyMatcher(ui::mojom::KeyboardCode::W, + ui::mojom::kEventFlagNone); + dispatcher->AddAccelerator(accelerator_2, std::move(matcher)); + dispatcher->ProcessEvent(key); + EXPECT_EQ(accelerator_2, + event_dispatcher_delegate->GetAndClearLastAccelerator()); + + dispatcher->RemoveAccelerator(accelerator_2); + dispatcher->ProcessEvent(key); + EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator()); +} + +// Tests that a post-target accelerator is not triggered by ProcessEvent. +TEST_F(EventDispatcherTest, PostTargetAccelerator) { + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + mojom::EventMatcherPtr matcher = mus::CreateKeyMatcher( + ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown); + matcher->accelerator_phase = ui::mojom::AcceleratorPhase::POST_TARGET; + uint32_t accelerator_1 = 1; + dispatcher->AddAccelerator(accelerator_1, std::move(matcher)); + + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + // The post-target accelerator should be fired if there is no focused window. + dispatcher->ProcessEvent(key); + EXPECT_EQ(accelerator_1, + event_dispatcher_delegate->GetAndClearLastAccelerator()); + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(details); + + // Set focused window for EventDispatcher dispatches key events. + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + event_dispatcher_delegate->SetFocusedWindowFromEventDispatcher(child.get()); + + // With a focused window the event should be dispatched. + dispatcher->ProcessEvent(key); + EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator()); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(details); + EXPECT_TRUE(details->accelerator); + + base::WeakPtr<Accelerator> accelerator_weak_ptr = + details->accelerator->GetWeakPtr(); + dispatcher->RemoveAccelerator(accelerator_1); + EXPECT_FALSE(accelerator_weak_ptr); + + // Post deletion there should be no accelerator + dispatcher->ProcessEvent(key); + EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator()); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(details); + EXPECT_FALSE(details->accelerator); +} + +TEST_F(EventDispatcherTest, Capture) { + ServerWindow* root = root_window(); + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + MouseEventTest tests[] = { + // Send a mouse down event over child. + {ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), + gfx::Point(20, 25), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(20, 25), gfx::Point(10, 15), nullptr, + gfx::Point(), gfx::Point()}, + + // Capture should be activated. Let's send a mouse move outside the bounds + // of the child. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr, + gfx::Point(), gfx::Point()}, + // Release the mouse and verify that the mouse up event goes to the child. + {ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr, + gfx::Point(), gfx::Point()}, + + // A mouse move at (50, 50) should now go to the root window. As the + // move crosses between |child| and |root| |child| gets an exit, and + // |root| the move. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), root, + gfx::Point(50, 50), gfx::Point(50, 50)}, + + }; + RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(), + tests, arraysize(tests)); +} + +TEST_F(EventDispatcherTest, CaptureMultipleMouseButtons) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + MouseEventTest tests[] = { + // Send a mouse down event over child with a left mouse button + {ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), + gfx::Point(20, 25), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(20, 25), gfx::Point(10, 15), nullptr, + gfx::Point(), gfx::Point()}, + + // Capture should be activated. Let's send a mouse move outside the bounds + // of the child and press the right mouse button too. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, 0), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr, + gfx::Point(), gfx::Point()}, + + // Release the left mouse button and verify that the mouse up event goes + // to the child. + {ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_RIGHT_MOUSE_BUTTON), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr, + gfx::Point(), gfx::Point()}, + + // A mouse move at (50, 50) should still go to the child. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, 0), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr, + gfx::Point(), gfx::Point()}, + + }; + RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(), + tests, arraysize(tests)); +} + +TEST_F(EventDispatcherTest, ClientAreaGoesToOwner) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + child->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>()); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Start move loop by sending mouse event over non-client area. + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(12, 12), gfx::Point(12, 12), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event); + + // Events should target child and be in the non-client area. + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + // Move the mouse 5,6 pixels and target is the same. + const ui::PointerEvent move_event( + ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(17, 18), gfx::Point(17, 18), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + dispatcher->ProcessEvent(move_event); + + // Still same target. + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + // Release the mouse. + const ui::PointerEvent release_event(ui::MouseEvent( + ui::ET_MOUSE_RELEASED, gfx::Point(17, 18), gfx::Point(17, 18), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(release_event); + + // The event should not have been dispatched to the delegate. + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + // Press in the client area and verify target/client area. The non-client area + // should get an exit first. + const ui::PointerEvent press_event2(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(21, 22), gfx::Point(21, 22), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event2); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type()); + + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + EXPECT_EQ(ui::ET_POINTER_DOWN, details->event->type()); +} + +TEST_F(EventDispatcherTest, AdditionalClientArea) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + std::vector<gfx::Rect> additional_client_areas; + additional_client_areas.push_back(gfx::Rect(18, 0, 2, 2)); + child->SetClientArea(gfx::Insets(5, 5, 5, 5), additional_client_areas); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + // Press in the additional client area, it should go to the child. + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(28, 11), gfx::Point(28, 11), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(press_event); + + // Events should target child and be in the client area. + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); +} + +TEST_F(EventDispatcherTest, HitTestMask) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + child->SetHitTestMask(gfx::Rect(2, 2, 16, 16)); + + // Move in the masked area. + const ui::PointerEvent move1(ui::MouseEvent( + ui::ET_MOUSE_MOVED, gfx::Point(11, 11), gfx::Point(11, 11), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + event_dispatcher()->ProcessEvent(move1); + + // Event went through the child window and hit the root. + std::unique_ptr<DispatchedEventDetails> details1 = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(root_window(), details1->window); + EXPECT_TRUE(details1->IsClientArea()); + + child->ClearHitTestMask(); + + // Move right in the same part of the window. + const ui::PointerEvent move2(ui::MouseEvent( + ui::ET_MOUSE_MOVED, gfx::Point(11, 12), gfx::Point(11, 12), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0)); + event_dispatcher()->ProcessEvent(move2); + + // Mouse exits the root. + std::unique_ptr<DispatchedEventDetails> details2 = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(ui::ET_POINTER_EXITED, details2->event->type()); + + // Mouse hits the child. + std::unique_ptr<DispatchedEventDetails> details3 = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child.get(), details3->window); + EXPECT_TRUE(details3->IsClientArea()); +} + +TEST_F(EventDispatcherTest, DontFocusOnSecondDown) { + std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child1->SetBounds(gfx::Rect(10, 10, 20, 20)); + child2->SetBounds(gfx::Rect(50, 51, 11, 12)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Press on child1. First press event should change focus. + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(12, 12), gfx::Point(12, 12), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event); + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child1.get(), details->window); + EXPECT_EQ(child1.get(), + event_dispatcher_delegate->GetAndClearLastFocusedWindow()); + + // Press (with a different pointer id) on child2. Event should go to child2, + // but focus should not change. + const ui::PointerEvent touch_event(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(53, 54), 2, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child2.get(), details->window); + EXPECT_EQ(nullptr, event_dispatcher_delegate->GetAndClearLastFocusedWindow()); +} + +TEST_F(EventDispatcherTest, TwoPointersActive) { + std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child1->SetBounds(gfx::Rect(10, 10, 20, 20)); + child2->SetBounds(gfx::Rect(50, 51, 11, 12)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Press on child1. + const ui::PointerEvent touch_event1(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event1); + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child1.get(), details->window); + + // Drag over child2, child1 should get the drag. + const ui::PointerEvent drag_event1(ui::TouchEvent( + ui::ET_TOUCH_MOVED, gfx::Point(53, 54), 1, base::TimeTicks())); + dispatcher->ProcessEvent(drag_event1); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child1.get(), details->window); + + // Press on child2 with a different touch id. + const ui::PointerEvent touch_event2(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(54, 55), 2, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event2); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child2.get(), details->window); + + // Drag over child1 with id 2, child2 should continue to get the drag. + const ui::PointerEvent drag_event2(ui::TouchEvent( + ui::ET_TOUCH_MOVED, gfx::Point(13, 14), 2, base::TimeTicks())); + dispatcher->ProcessEvent(drag_event2); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child2.get(), details->window); + + // Drag again with id 1, child1 should continue to get it. + dispatcher->ProcessEvent(drag_event1); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child1.get(), details->window); + + // Release touch id 1, and click on 2. 2 should get it. + const ui::PointerEvent touch_release(ui::TouchEvent( + ui::ET_TOUCH_RELEASED, gfx::Point(54, 55), 1, base::TimeTicks())); + dispatcher->ProcessEvent(touch_release); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child1.get(), details->window); + const ui::PointerEvent touch_event3(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(54, 55), 2, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event3); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(child2.get(), details->window); +} + +TEST_F(EventDispatcherTest, DestroyWindowWhileGettingEvents) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Press on child. + const ui::PointerEvent touch_event1(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event1); + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child.get(), details->window); + + // Delete child, and continue the drag. Event should not be dispatched. + child.reset(); + + const ui::PointerEvent drag_event1(ui::TouchEvent( + ui::ET_TOUCH_MOVED, gfx::Point(53, 54), 1, base::TimeTicks())); + dispatcher->ProcessEvent(drag_event1); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(nullptr, details.get()); +} + +TEST_F(EventDispatcherTest, MouseInExtendedHitTestRegion) { + ServerWindow* root = root_window(); + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Send event that is not over child. + const ui::PointerEvent ui_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(8, 9), gfx::Point(8, 9), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(ui_event); + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_EQ(root, details->window); + + // Release the mouse. + const ui::PointerEvent release_event(ui::MouseEvent( + ui::ET_MOUSE_RELEASED, gfx::Point(8, 9), gfx::Point(8, 9), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(release_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(root, details->window); + EXPECT_TRUE(details->IsClientArea()); + + // Change the extended hit test region and send event in extended hit test + // region. Should result in exit for root, followed by press for child. + child->set_extended_hit_test_region(gfx::Insets(5, 5, 5, 5)); + dispatcher->ProcessEvent(ui_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_EQ(root, details->window); + EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type()); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_TRUE(details->IsNonclientArea()); + ASSERT_EQ(child.get(), details->window); + EXPECT_EQ(ui::ET_POINTER_DOWN, details->event->type()); + ASSERT_TRUE(details->event.get()); + ASSERT_TRUE(details->event->IsPointerEvent()); + EXPECT_EQ(gfx::Point(-2, -1), details->event->AsPointerEvent()->location()); +} + +TEST_F(EventDispatcherTest, WheelWhileDown) { + std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child1->SetBounds(gfx::Rect(10, 10, 20, 20)); + child2->SetBounds(gfx::Rect(50, 51, 11, 12)); + + MouseEventTest tests[] = { + // Send a mouse down event over child1. + {ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), + gfx::Point(15, 15), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child1.get(), gfx::Point(15, 15), gfx::Point(5, 5), nullptr, + gfx::Point(), gfx::Point()}, + // Send mouse wheel over child2, should go to child1 as it has capture. + {ui::MouseWheelEvent(gfx::Vector2d(1, 0), gfx::Point(53, 54), + gfx::Point(53, 54), base::TimeTicks(), ui::EF_NONE, + ui::EF_NONE), + child1.get(), gfx::Point(53, 54), gfx::Point(43, 44), nullptr, + gfx::Point(), gfx::Point()}, + }; + RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(), + tests, arraysize(tests)); +} + +// Tests that when explicit capture has been set that all events go to the +// designated window, and that when capture is cleared, events find the +// appropriate target window. +TEST_F(EventDispatcherTest, SetExplicitCapture) { + ServerWindow* root = root_window(); + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + { + // Send all pointer events to the child. + dispatcher->SetCaptureWindow(child.get(), kClientAreaId); + + // The mouse press should go to the child even though its outside its + // bounds. + const ui::PointerEvent left_press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(left_press_event); + + // Events should target child. + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + EXPECT_TRUE(IsMouseButtonDown()); + + // The mouse down state should update while capture is set. + const ui::PointerEvent right_press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_RIGHT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(right_press_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(IsMouseButtonDown()); + + // One button released should not clear mouse down + const ui::PointerEvent left_release_event(ui::MouseEvent( + ui::ET_MOUSE_RELEASED, gfx::Point(5, 5), gfx::Point(5, 5), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(left_release_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(IsMouseButtonDown()); + + // Touch Event while mouse is down should not affect state. + const ui::PointerEvent touch_event(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(15, 15), 2, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(IsMouseButtonDown()); + + // Move event should not affect down + const ui::PointerEvent move_event( + ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(15, 5), gfx::Point(15, 5), + base::TimeTicks(), ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_RIGHT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(move_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_TRUE(IsMouseButtonDown()); + + // All mouse buttons up should clear mouse down. + const ui::PointerEvent right_release_event( + ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5), + gfx::Point(5, 5), base::TimeTicks(), + ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(right_release_event); + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(IsMouseButtonDown()); + } + + { + // Releasing capture and sending the same event will go to the root. + dispatcher->SetCaptureWindow(nullptr, kClientAreaId); + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event); + + // Events should target the root. + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + + ASSERT_TRUE(details); + ASSERT_EQ(root, details->window); + } +} + +// This test verifies that explicit capture overrides and resets implicit +// capture. +TEST_F(EventDispatcherTest, ExplicitCaptureOverridesImplicitCapture) { + ServerWindow* root = root_window(); + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + EventDispatcher* dispatcher = event_dispatcher(); + + // Run some implicit capture tests. + MouseEventTest tests[] = { + // Send a mouse down event over child with a left mouse button + {ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), + gfx::Point(20, 25), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON), + child.get(), gfx::Point(20, 25), gfx::Point(10, 15)}, + // Capture should be activated. Let's send a mouse move outside the bounds + // of the child and press the right mouse button too. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, 0), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40)}, + // Release the left mouse button and verify that the mouse up event goes + // to the child. + {ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, + ui::EF_RIGHT_MOUSE_BUTTON), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40)}, + // A mouse move at (50, 50) should still go to the child. + {ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50), + gfx::Point(50, 50), base::TimeTicks(), + ui::EF_LEFT_MOUSE_BUTTON, 0), + child.get(), gfx::Point(50, 50), gfx::Point(40, 40)}, + + }; + RunMouseEventTests(dispatcher, event_dispatcher_delegate, tests, + arraysize(tests)); + + // Add a second pointer target to the child. + { + const ui::PointerEvent touch_event(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event); + } + + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child.get(), details->window); + + // Verify that no window has explicit capture and hence we did indeed do + // implicit capture. + ASSERT_EQ(nullptr, dispatcher->capture_window()); + + // Give the root window explicit capture and verify input events over the + // child go to the root instead. + dispatcher->SetCaptureWindow(root, kNonclientAreaId); + + // The implicit target should receive a cancel event for each pointer target. + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_TRUE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child.get(), details->window); + EXPECT_EQ(ui::ET_POINTER_CANCELLED, details->event->type()); + + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + EXPECT_EQ(child.get(), details->window); + EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type()); + + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event); + + // Events should target the root. + details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(root, details->window); + ASSERT_TRUE(details->IsNonclientArea()); +} + +// Tests that setting capture does delete active pointer targets for the capture +// window. +TEST_F(EventDispatcherTest, CaptureUpdatesActivePointerTargets) { + ServerWindow* root = root_window(); + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + + EventDispatcher* dispatcher = event_dispatcher(); + { + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + dispatcher->ProcessEvent(press_event); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(root, details->window); + } + { + const ui::PointerEvent touch_event(ui::TouchEvent( + ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks())); + dispatcher->ProcessEvent(touch_event); + } + + ASSERT_TRUE(AreAnyPointersDown()); + ASSERT_TRUE(IsWindowPointerTarget(root)); + EXPECT_EQ(2, NumberPointerTargetsForWindow(root)); + + // Setting the capture should clear the implicit pointers for the specified + // window. + dispatcher->SetCaptureWindow(root, kNonclientAreaId); + EXPECT_FALSE(AreAnyPointersDown()); + EXPECT_FALSE(IsWindowPointerTarget(root)); +} + +// Tests that when explicit capture is changed, that the previous window with +// capture is no longer being observed. +TEST_F(EventDispatcherTest, UpdatingCaptureStopsObservingPreviousCapture) { + std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child1->SetBounds(gfx::Rect(10, 10, 20, 20)); + child2->SetBounds(gfx::Rect(50, 51, 11, 12)); + + EventDispatcher* dispatcher = event_dispatcher(); + ASSERT_FALSE(AreAnyPointersDown()); + ASSERT_FALSE(IsWindowPointerTarget(child1.get())); + ASSERT_FALSE(IsWindowPointerTarget(child2.get())); + dispatcher->SetCaptureWindow(child1.get(), kClientAreaId); + dispatcher->SetCaptureWindow(child2.get(), kClientAreaId); + EXPECT_EQ(child1.get(), + test_event_dispatcher_delegate()->lost_capture_window()); + + // If observing does not stop during the capture update this crashes. + child1->AddObserver(dispatcher); +} + +// Tests that destroying a window with explicit capture clears the capture +// state. +TEST_F(EventDispatcherTest, DestroyingCaptureWindowRemovesExplicitCapture) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + EventDispatcher* dispatcher = event_dispatcher(); + dispatcher->SetCaptureWindow(child.get(), kClientAreaId); + EXPECT_EQ(child.get(), dispatcher->capture_window()); + + ServerWindow* lost_capture_window = child.get(); + child.reset(); + EXPECT_EQ(nullptr, dispatcher->capture_window()); + EXPECT_EQ(lost_capture_window, + test_event_dispatcher_delegate()->lost_capture_window()); +} + +// Tests that when |client_id| is set for a window performing capture, that this +// preference is used regardless of whether an event targets the client region. +TEST_F(EventDispatcherTest, CaptureInNonClientAreaOverridesActualPoint) { + ServerWindow* root = root_window(); + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + + root->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>()); + EventDispatcher* dispatcher = event_dispatcher(); + dispatcher->SetCaptureWindow(root, kNonclientAreaId); + + TestEventDispatcherDelegate* event_dispatcher_delegate = + test_event_dispatcher_delegate(); + // Press in the client area, it should be marked as non client. + const ui::PointerEvent press_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(6, 6), gfx::Point(6, 6), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(press_event); + + // Events should target child and be in the client area. + std::unique_ptr<DispatchedEventDetails> details = + event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails(); + EXPECT_FALSE(event_dispatcher_delegate->has_queued_events()); + ASSERT_EQ(root, details->window); + EXPECT_TRUE(details->IsNonclientArea()); +} + +TEST_F(EventDispatcherTest, ProcessPointerEvents) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + { + const ui::PointerEvent pointer_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(pointer_event); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(20, 25), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(10, 15), dispatched_event->location()); + } + + { + const int touch_id = 3; + const ui::PointerEvent pointer_event( + ui::TouchEvent(ui::ET_TOUCH_RELEASED, gfx::Point(25, 20), touch_id, + base::TimeTicks())); + event_dispatcher()->ProcessEvent(pointer_event); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(25, 20), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(15, 10), dispatched_event->location()); + EXPECT_EQ(touch_id, dispatched_event->pointer_id()); + } +} + +TEST_F(EventDispatcherTest, ResetClearsPointerDown) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + child->SetBounds(gfx::Rect(10, 10, 20, 20)); + + // Send event that is over child. + const ui::PointerEvent ui_event(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(ui_event); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + ASSERT_EQ(child.get(), details->window); + + EXPECT_TRUE(AreAnyPointersDown()); + + event_dispatcher()->Reset(); + EXPECT_FALSE(test_event_dispatcher_delegate()->has_queued_events()); + EXPECT_FALSE(AreAnyPointersDown()); +} + +TEST_F(EventDispatcherTest, ResetClearsCapture) { + ServerWindow* root = root_window(); + root->SetBounds(gfx::Rect(0, 0, 100, 100)); + + root->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>()); + EventDispatcher* dispatcher = event_dispatcher(); + dispatcher->SetCaptureWindow(root, kNonclientAreaId); + + event_dispatcher()->Reset(); + EXPECT_FALSE(test_event_dispatcher_delegate()->has_queued_events()); + EXPECT_EQ(nullptr, event_dispatcher()->capture_window()); +} + +// Tests that events on a modal parent target the modal child. +TEST_F(EventDispatcherTest, ModalWindowEventOnModalParent) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w1|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(15, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(-35, 5), dispatched_event->location()); +} + +// Tests that events on a modal child target the modal child itself. +TEST_F(EventDispatcherTest, ModalWindowEventOnModalChild) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w2|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(55, 15), gfx::Point(55, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(55, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location()); +} + +// Tests that events on an unrelated window are not affected by the modal +// window. +TEST_F(EventDispatcherTest, ModalWindowEventOnUnrelatedWindow) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 6)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w3|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(75, 15), gfx::Point(75, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w3.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(75, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location()); +} + +// Tests that events events on a descendant of a modal parent target the modal +// child. +TEST_F(EventDispatcherTest, ModalWindowEventOnDescendantOfModalParent) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w11 = + CreateChildWindowWithParent(WindowId(1, 4), w1.get()); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w11|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(25, 25), gfx::Point(25, 25), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(25, 25), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(-25, 15), dispatched_event->location()); +} + +// Tests that events on a system modal window target the modal window itself. +TEST_F(EventDispatcherTest, ModalWindowEventOnSystemModal) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w1->SetModal(); + + // Send event that is over |w1|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w1.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(15, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location()); +} + +// Tests that events outside of system modal window target the modal window. +TEST_F(EventDispatcherTest, ModalWindowEventOutsideSystemModal) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w1->SetModal(); + event_dispatcher()->AddSystemModalWindow(w1.get()); + + // Send event that is over |w1|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(45, 15), gfx::Point(45, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w1.get(), details->window); + EXPECT_TRUE(details->IsNonclientArea()); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(45, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(35, 5), dispatched_event->location()); +} + +// Tests that setting capture to a descendant of a modal parent fails. +TEST_F(EventDispatcherTest, ModalWindowSetCaptureDescendantOfModalParent) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w11 = + CreateChildWindowWithParent(WindowId(1, 4), w1.get()); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + EXPECT_FALSE(event_dispatcher()->SetCaptureWindow(w11.get(), kClientAreaId)); + EXPECT_EQ(nullptr, event_dispatcher()->capture_window()); +} + +// Tests that setting capture to a window unrelated to a modal parent works. +TEST_F(EventDispatcherTest, ModalWindowSetCaptureUnrelatedWindow) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4)); + std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + EXPECT_TRUE(event_dispatcher()->SetCaptureWindow(w3.get(), kClientAreaId)); + EXPECT_EQ(w3.get(), event_dispatcher()->capture_window()); +} + +// Tests that setting capture fails when there is a system modal window. +TEST_F(EventDispatcherTest, ModalWindowSystemSetCapture) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + event_dispatcher()->AddSystemModalWindow(w2.get()); + + EXPECT_FALSE(event_dispatcher()->SetCaptureWindow(w1.get(), kClientAreaId)); + EXPECT_EQ(nullptr, event_dispatcher()->capture_window()); +} + +// Tests having multiple system modal windows. +TEST_F(EventDispatcherTest, ModalWindowMultipleSystemModals) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4)); + std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 5)); + + w2->SetVisible(false); + + // In the beginning, there should be no active system modal window. + EXPECT_EQ(nullptr, GetActiveSystemModalWindow()); + + // Add a visible system modal window. It should become the active one. + event_dispatcher()->AddSystemModalWindow(w1.get()); + EXPECT_EQ(w1.get(), GetActiveSystemModalWindow()); + + // Add an invisible system modal window. It should not change the active one. + event_dispatcher()->AddSystemModalWindow(w2.get()); + EXPECT_EQ(w1.get(), GetActiveSystemModalWindow()); + + // Add another visible system modal window. It should become the active one. + event_dispatcher()->AddSystemModalWindow(w3.get()); + EXPECT_EQ(w3.get(), GetActiveSystemModalWindow()); + + // Make an existing system modal window visible. It should become the active + // one. + w2->SetVisible(true); + EXPECT_EQ(w2.get(), GetActiveSystemModalWindow()); + + // Remove the active system modal window. Next one should become active. + w2.reset(); + EXPECT_EQ(w3.get(), GetActiveSystemModalWindow()); + + // Remove an inactive system modal window. It should not change the active + // one. + w1.reset(); + EXPECT_EQ(w3.get(), GetActiveSystemModalWindow()); + + // Remove the last remaining system modal window. There should be no active + // one anymore. + w3.reset(); + EXPECT_EQ(nullptr, GetActiveSystemModalWindow()); +} + +TEST_F(EventDispatcherTest, CaptureNotResetOnParentChange) { + std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + DisableHitTest(w1.get()); + std::unique_ptr<ServerWindow> w11 = + CreateChildWindowWithParent(WindowId(1, 4), w1.get()); + std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + DisableHitTest(w2.get()); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(0, 0, 100, 100)); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + w2->SetBounds(gfx::Rect(0, 0, 100, 100)); + + // Send event that is over |w11|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15), + base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + event_dispatcher()->SetCaptureWindow(w11.get(), kClientAreaId); + + std::unique_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w11.get(), details->window); + EXPECT_TRUE(details->IsClientArea()); + + // Move |w11| to |w2| and verify the mouse is still down, and |w11| has + // capture. + w2->Add(w11.get()); + EXPECT_TRUE(IsMouseButtonDown()); + EXPECT_EQ(w11.get(), + EventDispatcherTestApi(event_dispatcher()).capture_window()); +} + +TEST_F(EventDispatcherTest, ChangeCaptureFromClientToNonclient) { + std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); + event_dispatcher()->SetCaptureWindow(child.get(), kNonclientAreaId); + EXPECT_EQ(kNonclientAreaId, + event_dispatcher()->capture_window_client_id()); + EXPECT_EQ(nullptr, test_event_dispatcher_delegate()->lost_capture_window()); + event_dispatcher()->SetCaptureWindow(child.get(), kClientAreaId); + // Changing capture from client to non-client should notify the delegate. + // The delegate can decide if it really wants to forward the event or not. + EXPECT_EQ(child.get(), + test_event_dispatcher_delegate()->lost_capture_window()); + EXPECT_EQ(child.get(), event_dispatcher()->capture_window()); + EXPECT_EQ(kClientAreaId, event_dispatcher()->capture_window_client_id()); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/event_matcher.cc b/chromium/components/mus/ws/event_matcher.cc new file mode 100644 index 00000000000..da45554d2ea --- /dev/null +++ b/chromium/components/mus/ws/event_matcher.cc @@ -0,0 +1,113 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/event_matcher.h" + +namespace mus { +namespace ws { + +EventMatcher::EventMatcher(const mojom::EventMatcher& matcher) + : fields_to_match_(NONE), + event_type_(ui::ET_UNKNOWN), + event_flags_(ui::EF_NONE), + ignore_event_flags_(ui::EF_NONE), + keyboard_code_(ui::VKEY_UNKNOWN), + pointer_type_(ui::EventPointerType::POINTER_TYPE_UNKNOWN) { + if (matcher.type_matcher) { + fields_to_match_ |= TYPE; + switch (matcher.type_matcher->type) { + case ui::mojom::EventType::POINTER_DOWN: + event_type_ = ui::ET_POINTER_DOWN; + break; + case ui::mojom::EventType::POINTER_MOVE: + event_type_ = ui::ET_POINTER_MOVED; + break; + case ui::mojom::EventType::MOUSE_EXIT: + event_type_ = ui::ET_POINTER_EXITED; + break; + case ui::mojom::EventType::POINTER_UP: + event_type_ = ui::ET_POINTER_UP; + break; + case ui::mojom::EventType::POINTER_CANCEL: + event_type_ = ui::ET_POINTER_CANCELLED; + break; + case ui::mojom::EventType::KEY_PRESSED: + event_type_ = ui::ET_KEY_PRESSED; + break; + case ui::mojom::EventType::KEY_RELEASED: + event_type_ = ui::ET_KEY_RELEASED; + break; + default: + NOTREACHED(); + } + } + if (matcher.flags_matcher) { + fields_to_match_ |= FLAGS; + event_flags_ = matcher.flags_matcher->flags; + if (matcher.ignore_flags_matcher) + ignore_event_flags_ = matcher.ignore_flags_matcher->flags; + } + if (matcher.key_matcher) { + fields_to_match_ |= KEYBOARD_CODE; + keyboard_code_ = static_cast<uint16_t>(matcher.key_matcher->keyboard_code); + } + if (matcher.pointer_kind_matcher) { + fields_to_match_ |= POINTER_KIND; + switch (matcher.pointer_kind_matcher->pointer_kind) { + case ui::mojom::PointerKind::MOUSE: + pointer_type_ = ui::EventPointerType::POINTER_TYPE_MOUSE; + break; + case ui::mojom::PointerKind::TOUCH: + pointer_type_ = ui::EventPointerType::POINTER_TYPE_TOUCH; + break; + default: + NOTREACHED(); + } + } + if (matcher.pointer_location_matcher) { + fields_to_match_ |= POINTER_LOCATION; + pointer_region_ = matcher.pointer_location_matcher->region; + } +} + +EventMatcher::~EventMatcher() {} + +bool EventMatcher::MatchesEvent(const ui::Event& event) const { + if ((fields_to_match_ & TYPE) && event.type() != event_type_) + return false; + int flags = event.flags() & ~ignore_event_flags_; + if ((fields_to_match_ & FLAGS) && flags != event_flags_) + return false; + if (fields_to_match_ & KEYBOARD_CODE) { + if (!event.IsKeyEvent()) + return false; + if (keyboard_code_ != event.AsKeyEvent()->GetConflatedWindowsKeyCode()) + return false; + } + if (fields_to_match_ & POINTER_KIND) { + if (!event.IsPointerEvent() || + pointer_type_ != event.AsPointerEvent()->pointer_details().pointer_type) + return false; + } + if (fields_to_match_ & POINTER_LOCATION) { + // TODO(sad): The tricky part here is to make sure the same coord-space is + // used for the location-region and the event-location. + NOTIMPLEMENTED(); + return false; + } + return true; +} + +bool EventMatcher::Equals(const EventMatcher& other) const { + return fields_to_match_ == other.fields_to_match_ && + event_type_ == other.event_type_ && + event_flags_ == other.event_flags_ && + ignore_event_flags_ == other.ignore_event_flags_ && + keyboard_code_ == other.keyboard_code_ && + pointer_type_ == other.pointer_type_ && + pointer_region_ == other.pointer_region_; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/event_matcher.h b/chromium/components/mus/ws/event_matcher.h new file mode 100644 index 00000000000..ec9231b57a4 --- /dev/null +++ b/chromium/components/mus/ws/event_matcher.h @@ -0,0 +1,56 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_EVENT_MATCHER_H_ +#define COMPONENTS_MUS_WS_EVENT_MATCHER_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "ui/events/event.h" +#include "ui/events/mojo/event_constants.mojom.h" +#include "ui/events/mojo/keyboard_codes.mojom.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace mus { +namespace ws { + +// Wraps a mojom::EventMatcher and allows events to be tested against it. +class EventMatcher { + public: + explicit EventMatcher(const mojom::EventMatcher& matcher); + ~EventMatcher(); + + bool MatchesEvent(const ui::Event& event) const; + + bool Equals(const EventMatcher& other) const; + + private: + enum MatchFields { + NONE = 0, + TYPE = 1 << 0, + FLAGS = 1 << 1, + KEYBOARD_CODE = 1 << 2, + POINTER_KIND = 1 << 3, + POINTER_LOCATION = 1 << 4, + }; + + uint32_t fields_to_match_; + ui::EventType event_type_; + // Bitfields of kEventFlag* and kMouseEventFlag* values in + // input_event_constants.mojom. + int event_flags_; + int ignore_event_flags_; + uint16_t keyboard_code_; + ui::EventPointerType pointer_type_; + gfx::RectF pointer_region_; + + DISALLOW_COPY_AND_ASSIGN(EventMatcher); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_EVENT_MATCHER_H_ diff --git a/chromium/components/mus/ws/event_matcher_unittest.cc b/chromium/components/mus/ws/event_matcher_unittest.cc new file mode 100644 index 00000000000..04c011fb459 --- /dev/null +++ b/chromium/components/mus/ws/event_matcher_unittest.cc @@ -0,0 +1,71 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/event_matcher.h" + +#include "base/time/time.h" +#include "components/mus/public/interfaces/event_matcher.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/events/mojo/event_constants.mojom.h" +#include "ui/gfx/geometry/point.h" + +namespace mus { +namespace ws { + +// NOTE: Most of the matching functionality is exercised by tests of Accelerator +// handling in the EventDispatcher and WindowTree tests. These are just basic +// sanity checks. + +using EventTesterTest = testing::Test; + +TEST_F(EventTesterTest, MatchesEventByType) { + mojom::EventMatcherPtr matcher = mojom::EventMatcher::New(); + matcher->type_matcher = mojom::EventTypeMatcher::New(); + matcher->type_matcher->type = ui::mojom::EventType::POINTER_DOWN; + EventMatcher pointer_down_matcher(*matcher); + + ui::PointerEvent pointer_down( + ui::TouchEvent(ui::ET_TOUCH_PRESSED, gfx::Point(), 1, base::TimeTicks())); + EXPECT_TRUE(pointer_down_matcher.MatchesEvent(pointer_down)); + + ui::PointerEvent pointer_up(ui::TouchEvent( + ui::ET_TOUCH_RELEASED, gfx::Point(), 1, base::TimeTicks())); + EXPECT_FALSE(pointer_down_matcher.MatchesEvent(pointer_up)); +} + +TEST_F(EventTesterTest, MatchesEventByKeyCode) { + mojom::EventMatcherPtr matcher(mojom::EventMatcher::New()); + matcher->type_matcher = mojom::EventTypeMatcher::New(); + matcher->type_matcher->type = ui::mojom::EventType::KEY_PRESSED; + matcher->key_matcher = mojom::KeyEventMatcher::New(); + matcher->key_matcher->keyboard_code = ui::mojom::KeyboardCode::Z; + EventMatcher z_matcher(*matcher); + + ui::KeyEvent z_key(ui::ET_KEY_PRESSED, ui::VKEY_Z, ui::EF_NONE); + EXPECT_TRUE(z_matcher.MatchesEvent(z_key)); + + ui::KeyEvent y_key(ui::ET_KEY_PRESSED, ui::VKEY_Y, ui::EF_NONE); + EXPECT_FALSE(z_matcher.MatchesEvent(y_key)); +} + +TEST_F(EventTesterTest, MatchesEventByKeyFlags) { + mojom::EventMatcherPtr matcher(mojom::EventMatcher::New()); + matcher->type_matcher = mojom::EventTypeMatcher::New(); + matcher->type_matcher->type = ui::mojom::EventType::KEY_PRESSED; + matcher->flags_matcher = mojom::EventFlagsMatcher::New(); + matcher->flags_matcher->flags = ui::mojom::kEventFlagControlDown; + matcher->key_matcher = mojom::KeyEventMatcher::New(); + matcher->key_matcher->keyboard_code = ui::mojom::KeyboardCode::N; + EventMatcher control_n_matcher(*matcher); + + ui::KeyEvent control_n(ui::ET_KEY_PRESSED, ui::VKEY_N, ui::EF_CONTROL_DOWN); + EXPECT_TRUE(control_n_matcher.MatchesEvent(control_n)); + + ui::KeyEvent shift_n(ui::ET_KEY_PRESSED, ui::VKEY_N, ui::EF_SHIFT_DOWN); + EXPECT_FALSE(control_n_matcher.MatchesEvent(shift_n)); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/focus_controller.cc b/chromium/components/mus/ws/focus_controller.cc new file mode 100644 index 00000000000..5728acbe154 --- /dev/null +++ b/chromium/components/mus/ws/focus_controller.cc @@ -0,0 +1,311 @@ +// 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 "components/mus/ws/focus_controller.h" + +#include "base/macros.h" +#include "components/mus/public/interfaces/window_manager.mojom.h" +#include "components/mus/ws/focus_controller_delegate.h" +#include "components/mus/ws/focus_controller_observer.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_drawn_tracker.h" + +namespace mus { +namespace ws { + +namespace { + +ServerWindow* GetDeepestLastDescendant(ServerWindow* window) { + while (!window->children().empty()) + window = window->children().back(); + return window; +} + +// This can be used to iterate over each node in a rooted tree for the purpose +// of shifting focus/activation. +class WindowTreeIterator { + public: + explicit WindowTreeIterator(ServerWindow* root) : root_(root) {} + ~WindowTreeIterator() {} + + ServerWindow* GetNext(ServerWindow* window) const { + if (window == root_ || window == nullptr) + return GetDeepestLastDescendant(root_); + + // Return the next sibling. + ServerWindow* parent = window->parent(); + if (parent) { + const ServerWindow::Windows& siblings = parent->children(); + ServerWindow::Windows::const_reverse_iterator iter = + std::find(siblings.rbegin(), siblings.rend(), window); + DCHECK(iter != siblings.rend()); + ++iter; + if (iter != siblings.rend()) + return GetDeepestLastDescendant(*iter); + } + + // All children and siblings have been explored. Next is the parent. + return parent; + } + + private: + ServerWindow* root_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeIterator); +}; + +} // namespace + +FocusController::FocusController(FocusControllerDelegate* delegate, + ServerWindow* root) + : delegate_(delegate), + root_(root), + focused_window_(nullptr), + active_window_(nullptr), + activation_reason_(ActivationChangeReason::UNKNONW) { + DCHECK(delegate_); + DCHECK(root_); +} + +FocusController::~FocusController() { +} + +bool FocusController::SetFocusedWindow(ServerWindow* window) { + if (GetFocusedWindow() == window) + return true; + + return SetFocusedWindowImpl(FocusControllerChangeSource::EXPLICIT, window); +} + +ServerWindow* FocusController::GetFocusedWindow() { + return focused_window_; +} + +void FocusController::ActivateNextWindow() { + WindowTreeIterator iter(root_); + ServerWindow* activate = active_window_; + while (true) { + activate = iter.GetNext(activate); + if (activation_reason_ == ActivationChangeReason::CYCLE) { + if (activate == active_window_) { + // We have cycled over all the activatable windows. Remove the oldest + // window that was cycled. + if (!cycle_windows_->windows().empty()) { + cycle_windows_->Remove(cycle_windows_->windows().front()); + continue; + } + } else if (cycle_windows_->Contains(activate)) { + // We are cycling between activated windows, and this window has already + // been through the cycle. So skip over it. + continue; + } + } + if (activate == active_window_ || CanBeActivated(activate)) + break; + } + SetActiveWindow(activate, ActivationChangeReason::CYCLE); + + if (active_window_) { + // Do not shift focus if the focused window already lives in the active + // window. + if (focused_window_ && active_window_->Contains(focused_window_)) + return; + // Focus the first focusable window in the tree. + WindowTreeIterator iter(active_window_); + ServerWindow* focus = nullptr; + do { + focus = iter.GetNext(focus); + } while (focus != active_window_ && !CanBeFocused(focus)); + SetFocusedWindow(focus); + } else { + SetFocusedWindow(nullptr); + } +} + +void FocusController::AddObserver(FocusControllerObserver* observer) { + observers_.AddObserver(observer); +} + +void FocusController::RemoveObserver(FocusControllerObserver* observer) { + observers_.RemoveObserver(observer); +} + +void FocusController::SetActiveWindow(ServerWindow* window, + ActivationChangeReason reason) { + DCHECK(!window || CanBeActivated(window)); + if (active_window_ == window) + return; + if (reason != ActivationChangeReason::CYCLE) { + cycle_windows_.reset(); + } else if (activation_reason_ != ActivationChangeReason::CYCLE) { + DCHECK(!cycle_windows_); + cycle_windows_.reset(new ServerWindowTracker()); + if (active_window_) + cycle_windows_->Add(active_window_); + } + + ServerWindow* old_active = active_window_; + active_window_ = window; + activation_reason_ = reason; + FOR_EACH_OBSERVER(FocusControllerObserver, observers_, + OnActivationChanged(old_active, active_window_)); + if (active_window_ && activation_reason_ == ActivationChangeReason::CYCLE) + cycle_windows_->Add(active_window_); +} + +bool FocusController::CanBeFocused(ServerWindow* window) const { + // All ancestors of |window| must be drawn, and be focusable. + for (ServerWindow* w = window; w; w = w->parent()) { + if (!w->IsDrawn()) + return false; + if (!w->can_focus()) + return false; + } + + // |window| must be a descendent of an activatable window. + return GetActivatableAncestorOf(window) != nullptr; +} + +bool FocusController::CanBeActivated(ServerWindow* window) const { + DCHECK(window); + // A detached window cannot be activated. + if (!root_->Contains(window)) + return false; + + // The parent window must be allowed to have active children. + if (!delegate_->CanHaveActiveChildren(window->parent())) + return false; + + if (!window->can_focus()) + return false; + + // The window must be drawn, or if it's not drawn, it must be minimized. + if (!window->IsDrawn()) { + bool is_minimized = false; + const ServerWindow::Properties& props = window->properties(); + if (props.count(mojom::WindowManager::kShowState_Property)) { + is_minimized = + props.find(mojom::WindowManager::kShowState_Property)->second[0] == + static_cast<int>(mus::mojom::ShowState::MINIMIZED); + } + if (!is_minimized) + return false; + } + + // TODO(sad): If there's a transient modal window, then this cannot be + // activated. + return true; +} + +ServerWindow* FocusController::GetActivatableAncestorOf( + ServerWindow* window) const { + for (ServerWindow* w = window; w; w = w->parent()) { + if (CanBeActivated(w)) + return w; + } + return nullptr; +} + +bool FocusController::SetFocusedWindowImpl( + FocusControllerChangeSource change_source, + ServerWindow* window) { + if (window && !CanBeFocused(window)) + return false; + + ServerWindow* old_focused = GetFocusedWindow(); + + DCHECK(!window || window->IsDrawn()); + + // Activate the closest activatable ancestor window. + // TODO(sad): The window to activate doesn't necessarily have to be a direct + // ancestor (e.g. could be a transient parent). + SetActiveWindow(GetActivatableAncestorOf(window), + ActivationChangeReason::FOCUS); + + FOR_EACH_OBSERVER(FocusControllerObserver, observers_, + OnFocusChanged(change_source, old_focused, window)); + + focused_window_ = window; + // We can currently use only a single ServerWindowDrawnTracker since focused + // window is expected to be a direct descendant of the active window. + if (focused_window_ && active_window_) { + DCHECK(active_window_->Contains(focused_window_)); + } + ServerWindow* track_window = focused_window_; + if (!track_window) + track_window = active_window_; + if (track_window) + drawn_tracker_.reset(new ServerWindowDrawnTracker(track_window, this)); + else + drawn_tracker_.reset(); + return true; +} + +void FocusController::OnDrawnStateWillChange(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) { + DCHECK(!is_drawn); + DCHECK_NE(ancestor, window); + DCHECK(root_->Contains(window)); + drawn_tracker_.reset(); + + // Find the window that triggered this state-change notification. + ServerWindow* trigger = window; + while (trigger->parent() != ancestor) + trigger = trigger->parent(); + DCHECK(trigger); + auto will_be_hidden = [trigger](ServerWindow* w) { + return trigger->Contains(w); + }; + + // If |window| is |active_window_|, then activate the next activatable window + // that does not belong to the subtree which is getting hidden. + if (window == active_window_) { + WindowTreeIterator iter(root_); + ServerWindow* activate = active_window_; + do { + activate = iter.GetNext(activate); + } while (activate != active_window_ && + (will_be_hidden(activate) || !CanBeActivated(activate))); + if (activate == window) + activate = nullptr; + SetActiveWindow(activate, ActivationChangeReason::DRAWN_STATE_CHANGED); + + // Now make sure focus is in the active window. + ServerWindow* focus = nullptr; + if (active_window_) { + WindowTreeIterator iter(active_window_); + focus = nullptr; + do { + focus = iter.GetNext(focus); + } while (focus != active_window_ && + (will_be_hidden(focus) || !CanBeFocused(focus))); + DCHECK(focus && CanBeFocused(focus)); + } + SetFocusedWindowImpl(FocusControllerChangeSource::DRAWN_STATE_CHANGED, + focus); + return; + } + + // Move focus to the next focusable window in |active_window_|. + DCHECK_EQ(focused_window_, window); + WindowTreeIterator iter(active_window_); + ServerWindow* focus = focused_window_; + do { + focus = iter.GetNext(focus); + } while (focus != focused_window_ && + (will_be_hidden(focus) || !CanBeFocused(focus))); + if (focus == window) + focus = nullptr; + SetFocusedWindowImpl(FocusControllerChangeSource::DRAWN_STATE_CHANGED, focus); +} + +void FocusController::OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) { + // DCHECK(false); TODO(sadrul): +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/focus_controller.h b/chromium/components/mus/ws/focus_controller.h new file mode 100644 index 00000000000..96a08609ad0 --- /dev/null +++ b/chromium/components/mus/ws/focus_controller.h @@ -0,0 +1,104 @@ +// 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 COMPONENTS_MUS_WS_FOCUS_CONTROLLER_H_ +#define COMPONENTS_MUS_WS_FOCUS_CONTROLLER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/ws/server_window_drawn_tracker_observer.h" +#include "components/mus/ws/server_window_tracker.h" + +namespace mus { + +namespace ws { + +class FocusControllerDelegate; +class FocusControllerObserver; +class ServerWindow; +class ServerWindowDrawnTracker; +struct WindowId; + +// Describes the source of the change. +enum class FocusControllerChangeSource { + EXPLICIT, + DRAWN_STATE_CHANGED, +}; + +// Tracks the focused window. Focus is moved to another window when the drawn +// state of the focused window changes. +class FocusController : public ServerWindowDrawnTrackerObserver { + public: + FocusController(FocusControllerDelegate* delegate, ServerWindow* root); + ~FocusController() override; + + // Sets the focused window. Does nothing if |window| is currently focused. + // This does not notify the delegate. See ServerWindow::SetFocusedWindow() + // for details on return value. + bool SetFocusedWindow(ServerWindow* window); + ServerWindow* GetFocusedWindow(); + + // Moves activation to the next activatable window. + void ActivateNextWindow(); + + void AddObserver(FocusControllerObserver* observer); + void RemoveObserver(FocusControllerObserver* observer); + + private: + enum class ActivationChangeReason { + UNKNONW, + CYCLE, // Activation changed because of ActivateNextWindow(). + FOCUS, // Focus change required a different window to be activated. + DRAWN_STATE_CHANGED, // Active window was hidden or destroyed. + }; + void SetActiveWindow(ServerWindow* window, ActivationChangeReason reason); + + // Returns whether |window| can be focused or activated. + bool CanBeFocused(ServerWindow* window) const; + bool CanBeActivated(ServerWindow* window) const; + + // Returns the closest activatable ancestor of |window|. Returns nullptr if + // there is no such ancestor. + ServerWindow* GetActivatableAncestorOf(ServerWindow* window) const; + + // Implementation of SetFocusedWindow(). + bool SetFocusedWindowImpl(FocusControllerChangeSource change_source, + ServerWindow* window); + + // ServerWindowDrawnTrackerObserver: + void OnDrawnStateWillChange(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) override; + void OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) override; + + FocusControllerDelegate* delegate_; + + ServerWindow* root_; + ServerWindow* focused_window_; + ServerWindow* active_window_; + // Tracks what caused |active_window_| to be activated. + ActivationChangeReason activation_reason_; + + // Keeps track of the list of windows that have already been visited during a + // window cycle. This is only active when |activation_reason_| is set to + // CYCLE. + std::unique_ptr<ServerWindowTracker> cycle_windows_; + + base::ObserverList<FocusControllerObserver> observers_; + + // Keeps track of the visibility of the focused and active window. + std::unique_ptr<ServerWindowDrawnTracker> drawn_tracker_; + + DISALLOW_COPY_AND_ASSIGN(FocusController); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_FOCUS_CONTROLLER_H_ diff --git a/chromium/components/mus/ws/focus_controller_delegate.h b/chromium/components/mus/ws/focus_controller_delegate.h new file mode 100644 index 00000000000..6eca41cabbb --- /dev/null +++ b/chromium/components/mus/ws/focus_controller_delegate.h @@ -0,0 +1,26 @@ +// 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 COMPONENTS_MUS_WS_FOCUS_CONTROLLER_DELEGATE_H_ +#define COMPONENTS_MUS_WS_FOCUS_CONTROLLER_DELEGATE_H_ + +namespace mus { + +namespace ws { + +class ServerWindow; + +class FocusControllerDelegate { + public: + virtual bool CanHaveActiveChildren(ServerWindow* window) const = 0; + + protected: + ~FocusControllerDelegate() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_FOCUS_CONTROLLER_DELEGATE_H_ diff --git a/chromium/components/mus/ws/focus_controller_observer.h b/chromium/components/mus/ws/focus_controller_observer.h new file mode 100644 index 00000000000..8edbae18c35 --- /dev/null +++ b/chromium/components/mus/ws/focus_controller_observer.h @@ -0,0 +1,29 @@ +// 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 COMPONENTS_MUS_WS_FOCUS_CONTROLLER_OBSERVER_H_ +#define COMPONENTS_MUS_WS_FOCUS_CONTROLLER_OBSERVER_H_ + +namespace mus { +namespace ws { + +enum class FocusControllerChangeSource; +class ServerWindow; + +class FocusControllerObserver { + public: + virtual void OnActivationChanged(ServerWindow* old_active_window, + ServerWindow* new_active_window) = 0; + virtual void OnFocusChanged(FocusControllerChangeSource change_source, + ServerWindow* old_focused_window, + ServerWindow* new_focused_window) = 0; + + protected: + ~FocusControllerObserver() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_FOCUS_CONTROLLER_OBSERVER_H_ diff --git a/chromium/components/mus/ws/focus_controller_unittest.cc b/chromium/components/mus/ws/focus_controller_unittest.cc new file mode 100644 index 00000000000..563de06e875 --- /dev/null +++ b/chromium/components/mus/ws/focus_controller_unittest.cc @@ -0,0 +1,312 @@ +// 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 "components/mus/ws/focus_controller.h" + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "components/mus/ws/focus_controller_delegate.h" +#include "components/mus/ws/focus_controller_observer.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mus { + +namespace ws { +namespace { + +const char kDisallowActiveChildren[] = "disallow-active-children"; + +class TestFocusControllerObserver : public FocusControllerObserver, + public FocusControllerDelegate { + public: + TestFocusControllerObserver() + : ignore_explicit_(true), + focus_change_count_(0u), + old_focused_window_(nullptr), + new_focused_window_(nullptr), + old_active_window_(nullptr), + new_active_window_(nullptr) {} + + void ClearAll() { + focus_change_count_ = 0u; + old_focused_window_ = nullptr; + new_focused_window_ = nullptr; + old_active_window_ = nullptr; + new_active_window_ = nullptr; + } + size_t focus_change_count() const { return focus_change_count_; } + ServerWindow* old_focused_window() { return old_focused_window_; } + ServerWindow* new_focused_window() { return new_focused_window_; } + + ServerWindow* old_active_window() { return old_active_window_; } + ServerWindow* new_active_window() { return new_active_window_; } + + void set_ignore_explicit(bool ignore) { ignore_explicit_ = ignore; } + + private: + // FocusControllerDelegate: + bool CanHaveActiveChildren(ServerWindow* window) const override { + return !window || window->properties().count(kDisallowActiveChildren) == 0; + } + // FocusControllerObserver: + void OnActivationChanged(ServerWindow* old_active_window, + ServerWindow* new_active_window) override { + old_active_window_ = old_active_window; + new_active_window_ = new_active_window; + } + void OnFocusChanged(FocusControllerChangeSource source, + ServerWindow* old_focused_window, + ServerWindow* new_focused_window) override { + if (ignore_explicit_ && source == FocusControllerChangeSource::EXPLICIT) + return; + + focus_change_count_++; + old_focused_window_ = old_focused_window; + new_focused_window_ = new_focused_window; + } + + bool ignore_explicit_; + size_t focus_change_count_; + ServerWindow* old_focused_window_; + ServerWindow* new_focused_window_; + ServerWindow* old_active_window_; + ServerWindow* new_active_window_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusControllerObserver); +}; + +} // namespace + +TEST(FocusControllerTest, Basic) { + TestServerWindowDelegate server_window_delegate; + ServerWindow root(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&root); + root.SetVisible(true); + ServerWindow child(&server_window_delegate, WindowId()); + child.SetVisible(true); + root.Add(&child); + ServerWindow child_child(&server_window_delegate, WindowId()); + child_child.SetVisible(true); + child.Add(&child_child); + + TestFocusControllerObserver focus_observer; + FocusController focus_controller(&focus_observer, &root); + focus_controller.AddObserver(&focus_observer); + + focus_controller.SetFocusedWindow(&child_child); + EXPECT_EQ(0u, focus_observer.focus_change_count()); + + // Remove the ancestor of the focused window, focus should go to the |root|. + root.Remove(&child); + EXPECT_EQ(1u, focus_observer.focus_change_count()); + EXPECT_EQ(&root, focus_observer.new_focused_window()); + EXPECT_EQ(&child_child, focus_observer.old_focused_window()); + focus_observer.ClearAll(); + + // Make the focused window invisible. Focus is lost in this case (as no one + // to give focus to). + root.SetVisible(false); + EXPECT_EQ(1u, focus_observer.focus_change_count()); + EXPECT_EQ(nullptr, focus_observer.new_focused_window()); + EXPECT_EQ(&root, focus_observer.old_focused_window()); + focus_observer.ClearAll(); + + // Go back to initial state and focus |child_child|. + root.SetVisible(true); + root.Add(&child); + focus_controller.SetFocusedWindow(&child_child); + EXPECT_EQ(0u, focus_observer.focus_change_count()); + + // Hide the focused window, focus should go to parent. + child_child.SetVisible(false); + EXPECT_EQ(1u, focus_observer.focus_change_count()); + EXPECT_EQ(&child, focus_observer.new_focused_window()); + EXPECT_EQ(&child_child, focus_observer.old_focused_window()); + focus_observer.ClearAll(); + + child_child.SetVisible(true); + focus_controller.SetFocusedWindow(&child_child); + EXPECT_EQ(0u, focus_observer.focus_change_count()); + + // Hide the parent of the focused window. + child.SetVisible(false); + EXPECT_EQ(1u, focus_observer.focus_change_count()); + EXPECT_EQ(&root, focus_observer.new_focused_window()); + EXPECT_EQ(&child_child, focus_observer.old_focused_window()); + focus_observer.ClearAll(); + focus_controller.RemoveObserver(&focus_observer); +} + +// Tests that focus shifts correctly if the focused window is destroyed. +TEST(FocusControllerTest, FocusShiftsOnDestroy) { + TestServerWindowDelegate server_window_delegate; + ServerWindow parent(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&parent); + parent.SetVisible(true); + ServerWindow child_first(&server_window_delegate, WindowId()); + child_first.SetVisible(true); + parent.Add(&child_first); + std::unique_ptr<ServerWindow> child_second( + new ServerWindow(&server_window_delegate, WindowId())); + child_second->SetVisible(true); + parent.Add(child_second.get()); + std::vector<uint8_t> dummy; + // Allow only |parent| to be activated. + parent.SetProperty(kDisallowActiveChildren, &dummy); + + TestFocusControllerObserver focus_observer; + focus_observer.set_ignore_explicit(false); + FocusController focus_controller(&focus_observer, &parent); + focus_controller.AddObserver(&focus_observer); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(nullptr, focus_observer.old_active_window()); + EXPECT_EQ(&parent, focus_observer.new_active_window()); + EXPECT_EQ(nullptr, focus_observer.old_focused_window()); + EXPECT_EQ(child_second.get(), focus_observer.new_focused_window()); + focus_observer.ClearAll(); + + // Destroying |child_second| should move focus to |child_first|. + child_second.reset(); + EXPECT_NE(nullptr, focus_observer.old_focused_window()); + EXPECT_EQ(&child_first, focus_observer.new_focused_window()); + + focus_controller.RemoveObserver(&focus_observer); +} + +TEST(FocusControllerTest, ActivationSkipsOverHiddenWindow) { + TestServerWindowDelegate server_window_delegate; + ServerWindow parent(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&parent); + parent.SetVisible(true); + + ServerWindow child_first(&server_window_delegate, WindowId()); + ServerWindow child_second(&server_window_delegate, WindowId()); + ServerWindow child_third(&server_window_delegate, WindowId()); + + parent.Add(&child_first); + parent.Add(&child_second); + parent.Add(&child_third); + + child_first.SetVisible(true); + child_second.SetVisible(false); + child_third.SetVisible(true); + + TestFocusControllerObserver focus_observer; + focus_observer.set_ignore_explicit(false); + FocusController focus_controller(&focus_observer, &parent); + focus_controller.AddObserver(&focus_observer); + + // Since |child_second| is invisible, activation should cycle from + // |child_third|, to |child_first|, to |parent|, back to |child_third|. + focus_controller.ActivateNextWindow(); + EXPECT_EQ(nullptr, focus_observer.old_active_window()); + EXPECT_EQ(&child_third, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(&child_third, focus_observer.old_active_window()); + EXPECT_EQ(&child_first, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(&child_first, focus_observer.old_active_window()); + EXPECT_EQ(&parent, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(&parent, focus_observer.old_active_window()); + EXPECT_EQ(&child_third, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + // Once |child_second| is made visible, activation should go from + // |child_third| to |child_second|. + child_second.SetVisible(true); + focus_controller.ActivateNextWindow(); + EXPECT_EQ(&child_third, focus_observer.old_active_window()); + EXPECT_EQ(&child_second, focus_observer.new_active_window()); +} + +TEST(FocusControllerTest, ActivationSkipsOverHiddenContainers) { + TestServerWindowDelegate server_window_delegate; + ServerWindow parent(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&parent); + parent.SetVisible(true); + + ServerWindow child1(&server_window_delegate, WindowId()); + ServerWindow child2(&server_window_delegate, WindowId()); + + parent.Add(&child1); + parent.Add(&child2); + + child1.SetVisible(true); + child2.SetVisible(true); + + ServerWindow child11(&server_window_delegate, WindowId()); + ServerWindow child12(&server_window_delegate, WindowId()); + ServerWindow child21(&server_window_delegate, WindowId()); + ServerWindow child22(&server_window_delegate, WindowId()); + + child1.Add(&child11); + child1.Add(&child12); + child2.Add(&child21); + child2.Add(&child22); + + child11.SetVisible(true); + child12.SetVisible(true); + child21.SetVisible(true); + child22.SetVisible(true); + + TestFocusControllerObserver focus_observer; + FocusController focus_controller(&focus_observer, &parent); + focus_controller.AddObserver(&focus_observer); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(nullptr, focus_observer.old_active_window()); + EXPECT_EQ(&child22, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + child2.SetVisible(false); + EXPECT_EQ(&child22, focus_observer.old_active_window()); + EXPECT_EQ(&child12, focus_observer.new_active_window()); +} + +TEST(FocusControllerTest, NonFocusableWindowNotActivated) { + TestServerWindowDelegate server_window_delegate; + ServerWindow parent(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&parent); + parent.SetVisible(true); + + ServerWindow child_first(&server_window_delegate, WindowId()); + ServerWindow child_second(&server_window_delegate, WindowId()); + parent.Add(&child_first); + parent.Add(&child_second); + child_first.SetVisible(true); + child_second.SetVisible(true); + + TestFocusControllerObserver focus_observer; + focus_observer.set_ignore_explicit(false); + FocusController focus_controller(&focus_observer, &parent); + focus_controller.AddObserver(&focus_observer); + + child_first.set_can_focus(false); + + // |child_second| is activated first, but then activation skips over + // |child_first| and goes to |parent| instead. + focus_controller.ActivateNextWindow(); + EXPECT_EQ(nullptr, focus_observer.old_active_window()); + EXPECT_EQ(&child_second, focus_observer.new_active_window()); + focus_observer.ClearAll(); + + focus_controller.ActivateNextWindow(); + EXPECT_EQ(&child_second, focus_observer.old_active_window()); + EXPECT_EQ(&parent, focus_observer.new_active_window()); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/ids.h b/chromium/components/mus/ws/ids.h new file mode 100644 index 00000000000..3cdf4b29b87 --- /dev/null +++ b/chromium/components/mus/ws/ids.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_IDS_H_ +#define COMPONENTS_MUS_WS_IDS_H_ + +#include <stdint.h> + +#include <tuple> + +#include "base/containers/hash_tables.h" +#include "base/hash.h" +#include "components/mus/common/types.h" +#include "components/mus/common/util.h" + +namespace mus { +namespace ws { + +// A client id used to indicate no client. That is, no WindowTree ever gets this +// id. +const ClientSpecificId kInvalidClientId = 0; + +// Every window has a unique id associated with it (WindowId). The id is a +// combination of the id assigned to the client (the high order bits) and +// a unique id for the window. Each client (WindowTree) refers to the window +// by an id assigned by the client (ClientWindowId). To facilitate this +// WindowTree maintains a mapping between WindowId and ClientWindowId. +// +// This model works when the client initiates creation of windows, which is +// the typical use case. Embed roots and the WindowManager are special, they +// get access to windows created by other clients. These clients see the +// id assigned on the server. Such clients have to take care that they only +// create windows using their client id. To do otherwise could result in +// multiple windows having the same ClientWindowId. WindowTree enforces +// that embed roots use the client id in creating the window id to avoid +// possible conflicts. +struct WindowId { + WindowId(ClientSpecificId client_id, ClientSpecificId window_id) + : client_id(client_id), window_id(window_id) {} + WindowId() : client_id(0), window_id(0) {} + + bool operator==(const WindowId& other) const { + return other.client_id == client_id && other.window_id == window_id; + } + + bool operator!=(const WindowId& other) const { return !(*this == other); } + + bool operator<(const WindowId& other) const { + return std::tie(client_id, window_id) < + std::tie(other.client_id, other.window_id); + } + + ClientSpecificId client_id; + ClientSpecificId window_id; +}; + +// Used for ids assigned by the client. +struct ClientWindowId { + explicit ClientWindowId(Id id) : id(id) {} + ClientWindowId() : id(0u) {} + + bool operator==(const ClientWindowId& other) const { return other.id == id; } + + bool operator!=(const ClientWindowId& other) const { + return !(*this == other); + } + + bool operator<(const ClientWindowId& other) const { return id < other.id; } + + Id id; +}; + +inline WindowId WindowIdFromTransportId(Id id) { + return WindowId(HiWord(id), LoWord(id)); +} +inline Id WindowIdToTransportId(const WindowId& id) { + return (id.client_id << 16) | id.window_id; +} + +// Returns a WindowId that is reserved to indicate no window. That is, no window +// will ever be created with this id. +inline WindowId InvalidWindowId() { + return WindowId(kInvalidClientId, 0); +} + +// Returns a root window id with a given index offset. +inline WindowId RootWindowId(uint16_t index) { + return WindowId(kInvalidClientId, 2 + index); +} + +} // namespace ws +} // namespace mus + +namespace BASE_HASH_NAMESPACE { + +template <> +struct hash<mus::ws::ClientWindowId> { + size_t operator()(const mus::ws::ClientWindowId& id) const { + return hash<mus::Id>()(id.id); + } +}; + +template <> +struct hash<mus::ws::WindowId> { + size_t operator()(const mus::ws::WindowId& id) const { + return hash<mus::Id>()(WindowIdToTransportId(id)); + } +}; + +} // namespace BASE_HASH_NAMESPACE + +#endif // COMPONENTS_MUS_WS_IDS_H_ diff --git a/chromium/components/mus/ws/modal_window_controller.cc b/chromium/components/mus/ws/modal_window_controller.cc new file mode 100644 index 00000000000..89ec02e3720 --- /dev/null +++ b/chromium/components/mus/ws/modal_window_controller.cc @@ -0,0 +1,121 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/modal_window_controller.h" + +#include "base/stl_util.h" +#include "components/mus/ws/event_dispatcher.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_drawn_tracker.h" + +namespace mus { +namespace ws { +namespace { + +const ServerWindow* GetModalChildForWindowAncestor(const ServerWindow* window) { + for (const ServerWindow* ancestor = window; ancestor; + ancestor = ancestor->parent()) { + for (const auto& transient_child : ancestor->transient_children()) { + if (transient_child->is_modal() && transient_child->IsDrawn()) + return transient_child; + } + } + return nullptr; +} + +const ServerWindow* GetWindowModalTargetForWindow(const ServerWindow* window) { + const ServerWindow* modal_window = GetModalChildForWindowAncestor(window); + if (!modal_window) + return window; + return GetWindowModalTargetForWindow(modal_window); +} + +} // namespace + +ModalWindowController::ModalWindowController(EventDispatcher* event_dispatcher) + : event_dispatcher_(event_dispatcher) {} + +ModalWindowController::~ModalWindowController() { + for (auto it = system_modal_windows_.begin(); + it != system_modal_windows_.end(); it++) { + (*it)->RemoveObserver(this); + } +} + +void ModalWindowController::AddSystemModalWindow(ServerWindow* window) { + DCHECK(window); + DCHECK(!ContainsValue(system_modal_windows_, window)); + + window->SetModal(); + system_modal_windows_.push_back(window); + window_drawn_trackers_.insert(make_pair( + window, base::WrapUnique(new ServerWindowDrawnTracker(window, this)))); + window->AddObserver(this); + + event_dispatcher_->ReleaseCaptureBlockedByModalWindow(window); +} + +bool ModalWindowController::IsWindowBlockedBy( + const ServerWindow* window, + const ServerWindow* modal_window) const { + DCHECK(window); + DCHECK(modal_window); + if (!modal_window->is_modal() || !modal_window->IsDrawn()) + return false; + + if (modal_window->transient_parent() && + !modal_window->transient_parent()->Contains(window)) { + return false; + } + + return true; +} + +bool ModalWindowController::IsWindowBlocked(const ServerWindow* window) const { + DCHECK(window); + return GetActiveSystemModalWindow() || GetModalChildForWindowAncestor(window); +} + +const ServerWindow* ModalWindowController::GetTargetForWindow( + const ServerWindow* window) const { + ServerWindow* system_modal_window = GetActiveSystemModalWindow(); + return system_modal_window ? system_modal_window + : GetWindowModalTargetForWindow(window); +} + +ServerWindow* ModalWindowController::GetActiveSystemModalWindow() const { + for (auto it = system_modal_windows_.rbegin(); + it != system_modal_windows_.rend(); it++) { + ServerWindow* modal = *it; + if (modal->IsDrawn()) + return modal; + } + return nullptr; +} + +void ModalWindowController::OnWindowDestroyed(ServerWindow* window) { + window->RemoveObserver(this); + auto it = std::find(system_modal_windows_.begin(), + system_modal_windows_.end(), window); + DCHECK(it != system_modal_windows_.end()); + system_modal_windows_.erase(it); + window_drawn_trackers_.erase(window); +} + +void ModalWindowController::OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) { + if (!is_drawn) + return; + + // Move the most recently shown window to the end of the list. + auto it = std::find(system_modal_windows_.begin(), + system_modal_windows_.end(), window); + DCHECK(it != system_modal_windows_.end()); + system_modal_windows_.splice(system_modal_windows_.end(), + system_modal_windows_, it); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/modal_window_controller.h b/chromium/components/mus/ws/modal_window_controller.h new file mode 100644 index 00000000000..112fce771e6 --- /dev/null +++ b/chromium/components/mus/ws/modal_window_controller.h @@ -0,0 +1,87 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_MODAL_WINDOW_CONTROLLER_H_ +#define COMPONENTS_MUS_WS_MODAL_WINDOW_CONTROLLER_H_ + +#include <list> +#include <map> + +#include "components/mus/ws/server_window_drawn_tracker_observer.h" +#include "components/mus/ws/server_window_observer.h" + +namespace mus { +namespace ws { + +class EventDispatcher; +class ServerWindow; +class ServerWindowDrawnTracker; + +namespace test { +class ModalWindowControllerTestApi; +} + +// Used to keeps track of system modal windows and check whether windows are +// blocked by modal windows or not to do appropriate retargetting of events. +class ModalWindowController : public ServerWindowObserver, + public ServerWindowDrawnTrackerObserver { + public: + explicit ModalWindowController(EventDispatcher* event_dispatcher); + ~ModalWindowController() override; + + // Adds a system modal window. The window remains modal to system until it is + // destroyed. There can exist multiple system modal windows, in which case the + // one that is visible and added most recently or shown most recently would be + // the active one. + void AddSystemModalWindow(ServerWindow* window); + + // Checks whether |modal_window| is a visible modal window that blocks + // |window|. + bool IsWindowBlockedBy(const ServerWindow* window, + const ServerWindow* modal_window) const; + + // Checks whether |window| is blocked by any visible modal window. + bool IsWindowBlocked(const ServerWindow* window) const; + + // Returns the window that events targeted to |window| should be retargeted + // to; according to modal windows. If any modal window is blocking |window| + // (either system modal or window modal), returns the topmost one; otherwise, + // returns |window| itself. + const ServerWindow* GetTargetForWindow(const ServerWindow* window) const; + ServerWindow* GetTargetForWindow(ServerWindow* window) const { + return const_cast<ServerWindow*>( + GetTargetForWindow(static_cast<const ServerWindow*>(window))); + } + + private: + friend class test::ModalWindowControllerTestApi; + + // Returns the system modal window that is visible and added/shown most + // recently, if any. + ServerWindow* GetActiveSystemModalWindow() const; + + // ServerWindowObserver: + void OnWindowDestroyed(ServerWindow* window) override; + + // ServerWindowDrawnTrackerObserver: + void OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) override; + + EventDispatcher* event_dispatcher_; + + // List of system modal windows in order they are added/shown. + std::list<ServerWindow*> system_modal_windows_; + + // Drawn trackers for system modal windows. + std::map<ServerWindow*, std::unique_ptr<ServerWindowDrawnTracker>> + window_drawn_trackers_; + + DISALLOW_COPY_AND_ASSIGN(ModalWindowController); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_MODAL_WINDOW_CONTROLLER_H_ diff --git a/chromium/components/mus/ws/mus_ws_unittests_app_manifest.json b/chromium/components/mus/ws/mus_ws_unittests_app_manifest.json new file mode 100644 index 00000000000..5e3872e21ac --- /dev/null +++ b/chromium/components/mus/ws/mus_ws_unittests_app_manifest.json @@ -0,0 +1,16 @@ +{ + "manifest_version": 1, + "name": "mojo:mus_ws_unittests_app", + "display_name": "Mus Window Server Unittests", + "capabilities": { + "required": { + "*": { "classes": [ "app" ] }, + "mojo:mus_ws_unittests_app": { + "interfaces": [ "mus::mojom::WindowTreeClient" ] + }, + "mojo:mus": { + "interfaces": [ "mus::mojom::WindowTreeHostFactory" ] + } + } + } +} diff --git a/chromium/components/mus/ws/operation.cc b/chromium/components/mus/ws/operation.cc new file mode 100644 index 00000000000..6c04bc34c01 --- /dev/null +++ b/chromium/components/mus/ws/operation.cc @@ -0,0 +1,30 @@ +// 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 "components/mus/ws/operation.h" + +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" + +namespace mus { +namespace ws { + +Operation::Operation(WindowTree* tree, + WindowServer* window_server, + OperationType operation_type) + : window_server_(window_server), + source_tree_id_(tree->id()), + operation_type_(operation_type) { + DCHECK(operation_type != OperationType::NONE); + // Tell the window server about the operation currently in flight. The window + // server uses this information to suppress certain calls out to clients. + window_server_->PrepareForOperation(this); +} + +Operation::~Operation() { + window_server_->FinishOperation(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/operation.h b/chromium/components/mus/ws/operation.h new file mode 100644 index 00000000000..15d0034fddc --- /dev/null +++ b/chromium/components/mus/ws/operation.h @@ -0,0 +1,77 @@ +// 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 COMPONENTS_MUS_WS_OPERATION_H_ +#define COMPONENTS_MUS_WS_OPERATION_H_ + +#include <set> + +#include "base/macros.h" +#include "components/mus/common/types.h" + +namespace mus { +namespace ws { + +class WindowServer; +class WindowTree; + +enum class OperationType { + NONE, + ADD_TRANSIENT_WINDOW, + ADD_WINDOW, + DELETE_WINDOW, + EMBED, + RELEASE_CAPTURE, + REMOVE_TRANSIENT_WINDOW_FROM_PARENT, + REMOVE_WINDOW_FROM_PARENT, + REORDER_WINDOW, + SET_CAPTURE, + SET_FOCUS, + SET_WINDOW_BOUNDS, + SET_WINDOW_OPACITY, + SET_WINDOW_PREDEFINED_CURSOR, + SET_WINDOW_PROPERTY, + SET_WINDOW_VISIBILITY, +}; + +// This class tracks the currently pending client-initiated operation. +// This is typically used to suppress superfluous notifications generated +// by suboperations in the window server. +class Operation { + public: + Operation(WindowTree* tree, + WindowServer* window_server, + OperationType operation_type); + ~Operation(); + + ClientSpecificId source_tree_id() const { return source_tree_id_; } + + const OperationType& type() const { return operation_type_; } + + // Marks the tree with the specified id as having been sent a message + // during the course of |this| operation. + void MarkTreeAsMessaged(ClientSpecificId tree_id) { + message_ids_.insert(tree_id); + } + + // Returns true if MarkTreeAsMessaged(tree_id) was invoked. + bool DidMessageTree(ClientSpecificId tree_id) const { + return message_ids_.count(tree_id) > 0; + } + + private: + WindowServer* const window_server_; + const ClientSpecificId source_tree_id_; + const OperationType operation_type_; + + // See description of MarkTreeAsMessaged/DidMessageTree. + std::set<ClientSpecificId> message_ids_; + + DISALLOW_COPY_AND_ASSIGN(Operation); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_OPERATION_H_ diff --git a/chromium/components/mus/ws/platform_display.cc b/chromium/components/mus/ws/platform_display.cc new file mode 100644 index 00000000000..84ca1fde8ba --- /dev/null +++ b/chromium/components/mus/ws/platform_display.cc @@ -0,0 +1,414 @@ +// 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 "components/mus/ws/platform_display.h" + +#include "base/numerics/safe_conversions.h" +#include "build/build_config.h" +#include "cc/ipc/quads.mojom.h" +#include "cc/output/compositor_frame.h" +#include "cc/output/copy_output_request.h" +#include "cc/output/delegated_frame_data.h" +#include "cc/quads/render_pass.h" +#include "cc/quads/shared_quad_state.h" +#include "cc/quads/surface_draw_quad.h" +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/public/interfaces/gpu.mojom.h" +#include "components/mus/surfaces/display_compositor.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface.h" +#include "components/mus/ws/server_window_surface_manager.h" +#include "components/mus/ws/window_coordinate_conversions.h" +#include "services/shell/public/cpp/connection.h" +#include "services/shell/public/cpp/connector.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/base/cursor/cursor_loader.h" +#include "ui/display/display.h" +#include "ui/events/event.h" +#include "ui/events/event_utils.h" +#include "ui/platform_window/platform_ime_controller.h" +#include "ui/platform_window/platform_window.h" + +#if defined(OS_WIN) +#include "ui/platform_window/win/win_window.h" +#elif defined(USE_X11) +#include "ui/platform_window/x11/x11_window.h" +#elif defined(OS_ANDROID) +#include "ui/platform_window/android/platform_window_android.h" +#elif defined(USE_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#endif + +namespace mus { + +namespace ws { +namespace { + +// DrawWindowTree recursively visits ServerWindows, creating a SurfaceDrawQuad +// for each that lacks one. +void DrawWindowTree(cc::RenderPass* pass, + ServerWindow* window, + const gfx::Vector2d& parent_to_root_origin_offset, + float opacity) { + if (!window->visible()) + return; + + ServerWindowSurface* default_surface = + window->surface_manager() ? window->surface_manager()->GetDefaultSurface() + : nullptr; + + const gfx::Rect absolute_bounds = + window->bounds() + parent_to_root_origin_offset; + std::vector<ServerWindow*> children(window->GetChildren()); + // TODO(rjkroege, fsamuel): Make sure we're handling alpha correctly. + const float combined_opacity = opacity * window->opacity(); + for (auto it = children.rbegin(); it != children.rend(); ++it) { + DrawWindowTree(pass, *it, absolute_bounds.OffsetFromOrigin(), + combined_opacity); + } + + if (!window->surface_manager() || !window->surface_manager()->ShouldDraw()) + return; + + ServerWindowSurface* underlay_surface = + window->surface_manager()->GetUnderlaySurface(); + if (!default_surface && !underlay_surface) + return; + + if (default_surface) { + gfx::Transform quad_to_target_transform; + quad_to_target_transform.Translate(absolute_bounds.x(), + absolute_bounds.y()); + + cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState(); + + const gfx::Rect bounds_at_origin(window->bounds().size()); + // TODO(fsamuel): These clipping and visible rects are incorrect. They need + // to be populated from CompositorFrame structs. + sqs->SetAll(quad_to_target_transform, + bounds_at_origin.size() /* layer_bounds */, + bounds_at_origin /* visible_layer_bounds */, + bounds_at_origin /* clip_rect */, false /* is_clipped */, + window->opacity(), SkXfermode::kSrcOver_Mode, + 0 /* sorting-context_id */); + auto quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>(); + quad->SetAll(sqs, bounds_at_origin /* rect */, + gfx::Rect() /* opaque_rect */, + bounds_at_origin /* visible_rect */, true /* needs_blending*/, + default_surface->id()); + } + if (underlay_surface) { + const gfx::Rect underlay_absolute_bounds = + absolute_bounds - window->underlay_offset(); + gfx::Transform quad_to_target_transform; + quad_to_target_transform.Translate(underlay_absolute_bounds.x(), + underlay_absolute_bounds.y()); + cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState(); + const gfx::Rect bounds_at_origin( + underlay_surface->last_submitted_frame_size()); + sqs->SetAll(quad_to_target_transform, + bounds_at_origin.size() /* layer_bounds */, + bounds_at_origin /* visible_layer_bounds */, + bounds_at_origin /* clip_rect */, false /* is_clipped */, + window->opacity(), SkXfermode::kSrcOver_Mode, + 0 /* sorting-context_id */); + + auto quad = pass->CreateAndAppendDrawQuad<cc::SurfaceDrawQuad>(); + quad->SetAll(sqs, bounds_at_origin /* rect */, + gfx::Rect() /* opaque_rect */, + bounds_at_origin /* visible_rect */, true /* needs_blending*/, + underlay_surface->id()); + } +} + +} // namespace + +// static +PlatformDisplayFactory* PlatformDisplay::factory_ = nullptr; + +// static +PlatformDisplay* PlatformDisplay::Create( + const PlatformDisplayInitParams& init_params) { + if (factory_) + return factory_->CreatePlatformDisplay(); + + return new DefaultPlatformDisplay(init_params); +} + +DefaultPlatformDisplay::DefaultPlatformDisplay( + const PlatformDisplayInitParams& init_params) + : display_id_(init_params.display_id), + gpu_state_(init_params.gpu_state), + surfaces_state_(init_params.surfaces_state), + delegate_(nullptr), + draw_timer_(false, false), + frame_pending_(false), +#if !defined(OS_ANDROID) + cursor_loader_(ui::CursorLoader::Create()), +#endif + weak_factory_(this) { + metrics_.size_in_pixels = init_params.display_bounds.size(); + // TODO(rjkroege): Preserve the display_id when Ozone platform can use it. +} + +void DefaultPlatformDisplay::Init(PlatformDisplayDelegate* delegate) { + delegate_ = delegate; + + gfx::Rect bounds(metrics_.size_in_pixels); +#if defined(OS_WIN) + platform_window_.reset(new ui::WinWindow(this, bounds)); +#elif defined(USE_X11) + platform_window_.reset(new ui::X11Window(this)); +#elif defined(OS_ANDROID) + platform_window_.reset(new ui::PlatformWindowAndroid(this)); +#elif defined(USE_OZONE) + platform_window_ = + ui::OzonePlatform::GetInstance()->CreatePlatformWindow(this, bounds); +#else + NOTREACHED() << "Unsupported platform"; +#endif + platform_window_->SetBounds(bounds); + platform_window_->Show(); +} + +DefaultPlatformDisplay::~DefaultPlatformDisplay() { + // Don't notify the delegate from the destructor. + delegate_ = nullptr; + + // Invalidate WeakPtrs now to avoid callbacks back into the + // DefaultPlatformDisplay during destruction of |display_compositor_|. + weak_factory_.InvalidateWeakPtrs(); + display_compositor_.reset(); + // Destroy the PlatformWindow early on as it may call us back during + // destruction and we want to be in a known state. But destroy the surface + // first because it can still be using the platform window. + platform_window_.reset(); +} + +void DefaultPlatformDisplay::SchedulePaint(const ServerWindow* window, + const gfx::Rect& bounds) { + DCHECK(window); + if (!window->IsDrawn()) + return; + const gfx::Rect root_relative_rect = + ConvertRectBetweenWindows(window, delegate_->GetRootWindow(), bounds); + if (root_relative_rect.IsEmpty()) + return; + dirty_rect_.Union(root_relative_rect); + WantToDraw(); +} + +void DefaultPlatformDisplay::SetViewportSize(const gfx::Size& size) { + platform_window_->SetBounds(gfx::Rect(size)); +} + +void DefaultPlatformDisplay::SetTitle(const base::string16& title) { + platform_window_->SetTitle(title); +} + +void DefaultPlatformDisplay::SetCapture() { + platform_window_->SetCapture(); +} + +void DefaultPlatformDisplay::ReleaseCapture() { + platform_window_->ReleaseCapture(); +} + +void DefaultPlatformDisplay::SetCursorById(int32_t cursor_id) { +#if !defined(OS_ANDROID) + // TODO(erg): This still isn't sufficient, and will only use native cursors + // that chrome would use, not custom image cursors. For that, we should + // delegate to the window manager to load images from resource packs. + // + // We probably also need to deal with different DPIs. + ui::Cursor cursor(cursor_id); + cursor_loader_->SetPlatformCursor(&cursor); + platform_window_->SetCursor(cursor.platform()); +#endif +} + +float DefaultPlatformDisplay::GetDeviceScaleFactor() { + return metrics_.device_scale_factor; +} + +mojom::Rotation DefaultPlatformDisplay::GetRotation() { + // TODO(sky): implement me. + return mojom::Rotation::VALUE_0; +} + +void DefaultPlatformDisplay::UpdateTextInputState( + const ui::TextInputState& state) { + ui::PlatformImeController* ime = platform_window_->GetPlatformImeController(); + if (ime) + ime->UpdateTextInputState(state); +} + +void DefaultPlatformDisplay::SetImeVisibility(bool visible) { + ui::PlatformImeController* ime = platform_window_->GetPlatformImeController(); + if (ime) + ime->SetImeVisibility(visible); +} + +void DefaultPlatformDisplay::Draw() { + if (!delegate_->GetRootWindow()->visible()) + return; + + // TODO(fsamuel): We should add a trace for generating a top level frame. + cc::CompositorFrame frame(GenerateCompositorFrame()); + frame_pending_ = true; + if (display_compositor_) { + display_compositor_->SubmitCompositorFrame( + std::move(frame), base::Bind(&DefaultPlatformDisplay::DidDraw, + weak_factory_.GetWeakPtr())); + } + dirty_rect_ = gfx::Rect(); +} + +void DefaultPlatformDisplay::DidDraw(cc::SurfaceDrawStatus status) { + frame_pending_ = false; + delegate_->OnCompositorFrameDrawn(); + if (!dirty_rect_.IsEmpty()) + WantToDraw(); +} + +bool DefaultPlatformDisplay::IsFramePending() const { + return frame_pending_; +} + +int64_t DefaultPlatformDisplay::GetDisplayId() const { + return display_id_; +} + +void DefaultPlatformDisplay::WantToDraw() { + if (draw_timer_.IsRunning() || frame_pending_) + return; + + // TODO(rjkroege): Use vblank to kick off Draw. + draw_timer_.Start( + FROM_HERE, base::TimeDelta(), + base::Bind(&DefaultPlatformDisplay::Draw, weak_factory_.GetWeakPtr())); +} + +void DefaultPlatformDisplay::UpdateMetrics(const gfx::Size& size, + float device_scale_factor) { + if (display::Display::HasForceDeviceScaleFactor()) + device_scale_factor = display::Display::GetForcedDeviceScaleFactor(); + if (metrics_.size_in_pixels == size && + metrics_.device_scale_factor == device_scale_factor) + return; + + ViewportMetrics old_metrics = metrics_; + metrics_.size_in_pixels = size; + metrics_.device_scale_factor = device_scale_factor; + delegate_->OnViewportMetricsChanged(old_metrics, metrics_); +} + +cc::CompositorFrame DefaultPlatformDisplay::GenerateCompositorFrame() { + std::unique_ptr<cc::RenderPass> render_pass = cc::RenderPass::Create(); + render_pass->damage_rect = dirty_rect_; + render_pass->output_rect = gfx::Rect(metrics_.size_in_pixels); + + DrawWindowTree(render_pass.get(), delegate_->GetRootWindow(), gfx::Vector2d(), + 1.0f); + + std::unique_ptr<cc::DelegatedFrameData> frame_data( + new cc::DelegatedFrameData); + frame_data->render_pass_list.push_back(std::move(render_pass)); + + cc::CompositorFrame frame; + frame.delegated_frame_data = std::move(frame_data); + return frame; +} + +void DefaultPlatformDisplay::OnBoundsChanged(const gfx::Rect& new_bounds) { + UpdateMetrics(new_bounds.size(), metrics_.device_scale_factor); +} + +void DefaultPlatformDisplay::OnDamageRect(const gfx::Rect& damaged_region) { + dirty_rect_.Union(damaged_region); + WantToDraw(); +} + +void DefaultPlatformDisplay::DispatchEvent(ui::Event* event) { + if (event->IsScrollEvent()) { + // TODO(moshayedi): crbug.com/602859. Dispatch scroll events as + // they are once we have proper support for scroll events. + delegate_->OnEvent(ui::MouseWheelEvent(*event->AsScrollEvent())); + } else if (event->IsMouseEvent() && !event->IsMouseWheelEvent()) { + delegate_->OnEvent(ui::PointerEvent(*event->AsMouseEvent())); + } else if (event->IsTouchEvent()) { + delegate_->OnEvent(ui::PointerEvent(*event->AsTouchEvent())); + } else { + delegate_->OnEvent(*event); + } + +#if defined(USE_X11) || defined(USE_OZONE) + // 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 = event->AsKeyEvent(); + 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()); + delegate_->OnEvent(char_event); + } +#endif +} + +void DefaultPlatformDisplay::OnCloseRequest() { + platform_window_->Close(); +} + +void DefaultPlatformDisplay::OnClosed() { + if (delegate_) + delegate_->OnDisplayClosed(); +} + +void DefaultPlatformDisplay::OnWindowStateChanged( + ui::PlatformWindowState new_state) {} + +void DefaultPlatformDisplay::OnLostCapture() { + delegate_->OnNativeCaptureLost(); +} + +void DefaultPlatformDisplay::OnAcceleratedWidgetAvailable( + gfx::AcceleratedWidget widget, + float device_scale_factor) { + if (widget != gfx::kNullAcceleratedWidget) { + display_compositor_.reset( + new DisplayCompositor(base::ThreadTaskRunnerHandle::Get(), widget, + gpu_state_, surfaces_state_)); + } + UpdateMetrics(metrics_.size_in_pixels, device_scale_factor); +} + +void DefaultPlatformDisplay::OnAcceleratedWidgetDestroyed() { + NOTREACHED(); +} + +void DefaultPlatformDisplay::OnActivationChanged(bool active) {} + +void DefaultPlatformDisplay::RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request) { + if (display_compositor_) + display_compositor_->RequestCopyOfOutput(std::move(output_request)); +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/platform_display.h b/chromium/components/mus/ws/platform_display.h new file mode 100644 index 00000000000..e16a46c1936 --- /dev/null +++ b/chromium/components/mus/ws/platform_display.h @@ -0,0 +1,195 @@ +// 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 COMPONENTS_MUS_WS_PLATFORM_DISPLAY_H_ +#define COMPONENTS_MUS_WS_PLATFORM_DISPLAY_H_ + +#include <stdint.h> + +#include <map> +#include <memory> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "base/timer/timer.h" +#include "build/build_config.h" +#include "cc/surfaces/surface.h" +#include "components/mus/public/interfaces/window_manager.mojom.h" +#include "components/mus/public/interfaces/window_manager_constants.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/platform_display_delegate.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/platform_window/platform_window_delegate.h" + +namespace cc { +class CompositorFrame; +class CopyOutputRequest; +class SurfaceIdAllocator; +class SurfaceManager; +} // namespace cc + +namespace gles2 { +class GpuState; +} // namespace gles2 + +namespace shell { +class Connector; +} // namespace shell + +namespace ui { +class CursorLoader; +class PlatformWindow; +struct TextInputState; +} // namespace ui + +namespace mus { + +class GpuState; +class SurfacesState; +class DisplayCompositor; + +namespace ws { + +class EventDispatcher; +class PlatformDisplayFactory; +class ServerWindow; + +struct ViewportMetrics { + gfx::Size size_in_pixels; + float device_scale_factor = 0.f; +}; + +// PlatformDisplay is used to connect the root ServerWindow to a display. +class PlatformDisplay { + public: + virtual ~PlatformDisplay() {} + + static PlatformDisplay* Create(const PlatformDisplayInitParams& init_params); + + virtual void Init(PlatformDisplayDelegate* delegate) = 0; + + // Schedules a paint for the specified region in the coordinates of |window|. + virtual void SchedulePaint(const ServerWindow* window, + const gfx::Rect& bounds) = 0; + + virtual void SetViewportSize(const gfx::Size& size) = 0; + + virtual void SetTitle(const base::string16& title) = 0; + + virtual void SetCapture() = 0; + + virtual void ReleaseCapture() = 0; + + virtual void SetCursorById(int32_t cursor) = 0; + + virtual mojom::Rotation GetRotation() = 0; + + virtual float GetDeviceScaleFactor() = 0; + + virtual void UpdateTextInputState(const ui::TextInputState& state) = 0; + virtual void SetImeVisibility(bool visible) = 0; + + // Returns true if a compositor frame has been submitted but not drawn yet. + virtual bool IsFramePending() const = 0; + + virtual void RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request) = 0; + + virtual int64_t GetDisplayId() const = 0; + + // Overrides factory for testing. Default (NULL) value indicates regular + // (non-test) environment. + static void set_factory_for_testing(PlatformDisplayFactory* factory) { + PlatformDisplay::factory_ = factory; + } + + private: + // Static factory instance (always NULL for non-test). + static PlatformDisplayFactory* factory_; +}; + +// PlatformDisplay implementation that connects to the services necessary to +// actually display. +class DefaultPlatformDisplay : public PlatformDisplay, + public ui::PlatformWindowDelegate { + public: + explicit DefaultPlatformDisplay(const PlatformDisplayInitParams& init_params); + ~DefaultPlatformDisplay() override; + + // PlatformDisplay: + void Init(PlatformDisplayDelegate* delegate) override; + void SchedulePaint(const ServerWindow* window, + const gfx::Rect& bounds) override; + void SetViewportSize(const gfx::Size& size) override; + void SetTitle(const base::string16& title) override; + void SetCapture() override; + void ReleaseCapture() override; + void SetCursorById(int32_t cursor) override; + float GetDeviceScaleFactor() override; + mojom::Rotation GetRotation() override; + void UpdateTextInputState(const ui::TextInputState& state) override; + void SetImeVisibility(bool visible) override; + bool IsFramePending() const override; + void RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request) override; + int64_t GetDisplayId() const override; + + private: + void WantToDraw(); + + // This method initiates a top level redraw of the display. + // TODO(fsamuel): This should use vblank as a signal rather than a timer + // http://crbug.com/533042 + void Draw(); + + // This is called after cc::Display has completed generating a new frame + // for the display. TODO(fsamuel): Idle time processing should happen here + // if there is budget for it. + void DidDraw(cc::SurfaceDrawStatus status); + void UpdateMetrics(const gfx::Size& size, float device_scale_factor); + cc::CompositorFrame GenerateCompositorFrame(); + + // ui::PlatformWindowDelegate: + void OnBoundsChanged(const gfx::Rect& new_bounds) override; + void OnDamageRect(const gfx::Rect& damaged_region) override; + void DispatchEvent(ui::Event* event) override; + void OnCloseRequest() override; + void OnClosed() override; + void OnWindowStateChanged(ui::PlatformWindowState new_state) override; + void OnLostCapture() override; + void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget, + float device_scale_factor) override; + void OnAcceleratedWidgetDestroyed() override; + void OnActivationChanged(bool active) override; + + int64_t display_id_; + + scoped_refptr<GpuState> gpu_state_; + scoped_refptr<SurfacesState> surfaces_state_; + PlatformDisplayDelegate* delegate_; + + ViewportMetrics metrics_; + gfx::Rect dirty_rect_; + base::Timer draw_timer_; + bool frame_pending_; + + std::unique_ptr<DisplayCompositor> display_compositor_; + std::unique_ptr<ui::PlatformWindow> platform_window_; + +#if !defined(OS_ANDROID) + std::unique_ptr<ui::CursorLoader> cursor_loader_; +#endif + + base::WeakPtrFactory<DefaultPlatformDisplay> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DefaultPlatformDisplay); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_DISPLAY_H_ diff --git a/chromium/components/mus/ws/platform_display_delegate.h b/chromium/components/mus/ws/platform_display_delegate.h new file mode 100644 index 00000000000..95a9c761dc4 --- /dev/null +++ b/chromium/components/mus/ws/platform_display_delegate.h @@ -0,0 +1,53 @@ +// 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 COMPONENTS_MUS_WS_PLATFORM_DISPLAY_DELEGATE_H_ +#define COMPONENTS_MUS_WS_PLATFORM_DISPLAY_DELEGATE_H_ + +#include "components/mus/ws/ids.h" + +namespace ui { +class Event; +} + +namespace mus { + +namespace ws { + +class ServerWindow; +struct ViewportMetrics; + +// PlatformDisplayDelegate is implemented by an object that manages the +// lifetime of a PlatformDisplay, forwards events to the appropriate windows, +/// and responses to changes in viewport size. +class PlatformDisplayDelegate { + public: + // Returns the root window of this display. + virtual ServerWindow* GetRootWindow() = 0; + + // Called when the window managed by the PlatformDisplay is closed. + virtual void OnDisplayClosed() = 0; + + // Called when an event arrives. + virtual void OnEvent(const ui::Event& event) = 0; + + // Called when the Display loses capture. + virtual void OnNativeCaptureLost() = 0; + + // Signals that the metrics of this display's viewport has changed. + virtual void OnViewportMetricsChanged(const ViewportMetrics& old_metrics, + const ViewportMetrics& new_metrics) = 0; + + // Called when a compositor frame is finished drawing. + virtual void OnCompositorFrameDrawn() = 0; + + protected: + virtual ~PlatformDisplayDelegate() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_DISPLAY_DELEGATE_H_ diff --git a/chromium/components/mus/ws/platform_display_factory.h b/chromium/components/mus/ws/platform_display_factory.h new file mode 100644 index 00000000000..1cad7dd8331 --- /dev/null +++ b/chromium/components/mus/ws/platform_display_factory.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 COMPONENTS_MUS_WS_PLATFORM_DISPLAY_FACTORY_H_ +#define COMPONENTS_MUS_WS_PLATFORM_DISPLAY_FACTORY_H_ + +namespace mus { +namespace ws { + +class PlatformDisplay; + +// Abstract factory for PlatformDisplays. Used by tests to construct test +// PlatformDisplays. +class PlatformDisplayFactory { + public: + virtual PlatformDisplay* CreatePlatformDisplay() = 0; +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_DISPLAY_FACTORY_H_ diff --git a/chromium/components/mus/ws/platform_display_init_params.cc b/chromium/components/mus/ws/platform_display_init_params.cc new file mode 100644 index 00000000000..394b5711637 --- /dev/null +++ b/chromium/components/mus/ws/platform_display_init_params.cc @@ -0,0 +1,21 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/platform_display_init_params.h" + +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/surfaces/surfaces_state.h" + +namespace mus { +namespace ws { + +PlatformDisplayInitParams::PlatformDisplayInitParams() + : display_bounds(gfx::Rect(0, 0, 1024, 768)), display_id(1) {} + +PlatformDisplayInitParams::PlatformDisplayInitParams( + const PlatformDisplayInitParams& other) = default; +PlatformDisplayInitParams::~PlatformDisplayInitParams() {} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/platform_display_init_params.h b/chromium/components/mus/ws/platform_display_init_params.h new file mode 100644 index 00000000000..e596834f49b --- /dev/null +++ b/chromium/components/mus/ws/platform_display_init_params.h @@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_PLATFORM_DISPLAY_INIT_PARAMS_H_ +#define COMPONENTS_MUS_WS_PLATFORM_DISPLAY_INIT_PARAMS_H_ + +#include <stdint.h> + +#include "base/memory/ref_counted.h" +#include "ui/gfx/geometry/rect.h" + +namespace shell { +class Connector; +} + +namespace mus { + +class GpuState; +class SurfacesState; + +namespace ws { + +struct PlatformDisplayInitParams { + PlatformDisplayInitParams(); + PlatformDisplayInitParams(const PlatformDisplayInitParams& other); + ~PlatformDisplayInitParams(); + + scoped_refptr<GpuState> gpu_state; + scoped_refptr<SurfacesState> surfaces_state; + + gfx::Rect display_bounds; + int64_t display_id; +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_DISPLAY_INIT_PARAMS_H_ diff --git a/chromium/components/mus/ws/platform_screen.h b/chromium/components/mus/ws/platform_screen.h new file mode 100644 index 00000000000..3215c28c257 --- /dev/null +++ b/chromium/components/mus/ws/platform_screen.h @@ -0,0 +1,43 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_PLATFORM_SCREEN_H_ +#define COMPONENTS_MUS_WS_PLATFORM_SCREEN_H_ + +#include <stdint.h> + +#include "base/callback.h" + +namespace gfx { +class Rect; +} + +namespace mus { +namespace ws { + +// PlatformScreen provides the necessary functionality to configure all +// attached physical displays. +class PlatformScreen { + public: + virtual ~PlatformScreen() {} + + // Creates a PlatformScreen instance. + static std::unique_ptr<PlatformScreen> Create(); + + // Initializes platform specific screen resources. + virtual void Init() = 0; + + using ConfiguredDisplayCallback = + base::Callback<void(int64_t, const gfx::Rect&)>; + + // ConfigurePhysicalDisplay() configures a single physical display and returns + // its id and bounds for it via |callback|. + virtual void ConfigurePhysicalDisplay( + const ConfiguredDisplayCallback& callback) = 0; +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_SCREEN_H_ diff --git a/chromium/components/mus/ws/platform_screen_impl.cc b/chromium/components/mus/ws/platform_screen_impl.cc new file mode 100644 index 00000000000..e5ec99f660f --- /dev/null +++ b/chromium/components/mus/ws/platform_screen_impl.cc @@ -0,0 +1,43 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/platform_screen_impl.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { + +namespace ws { +namespace { + +void FixedSizeScreenConfiguration( + const PlatformScreen::ConfiguredDisplayCallback& callback) { + callback.Run(1, gfx::Rect(1024, 768)); +} + +} // namespace + +// static +std::unique_ptr<PlatformScreen> PlatformScreen::Create() { + return base::WrapUnique(new PlatformScreenImpl); +} + +PlatformScreenImpl::PlatformScreenImpl() {} + +PlatformScreenImpl::~PlatformScreenImpl() {} + +void PlatformScreenImpl::Init() {} + +void PlatformScreenImpl::ConfigurePhysicalDisplay( + const PlatformScreen::ConfiguredDisplayCallback& callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FixedSizeScreenConfiguration, callback)); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/platform_screen_impl.h b/chromium/components/mus/ws/platform_screen_impl.h new file mode 100644 index 00000000000..166bff771b4 --- /dev/null +++ b/chromium/components/mus/ws/platform_screen_impl.h @@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_H_ +#define COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_H_ + +#include <stdint.h> + +#include "base/callback.h" +#include "components/mus/ws/platform_screen.h" + +namespace gfx { +class Rect; +} + +namespace mus { +namespace ws { + +// PlatformScreenImpl provides the necessary functionality to configure all +// attached physical displays on non-ozone platforms. +class PlatformScreenImpl : public PlatformScreen { + public: + PlatformScreenImpl(); + ~PlatformScreenImpl() override; + + private: + // PlatformScreen. + void Init() override; + void ConfigurePhysicalDisplay( + const PlatformScreen::ConfiguredDisplayCallback& callback) override; + + DISALLOW_COPY_AND_ASSIGN(PlatformScreenImpl); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_H_ diff --git a/chromium/components/mus/ws/platform_screen_impl_ozone.cc b/chromium/components/mus/ws/platform_screen_impl_ozone.cc new file mode 100644 index 00000000000..91f2ba249da --- /dev/null +++ b/chromium/components/mus/ws/platform_screen_impl_ozone.cc @@ -0,0 +1,126 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/platform_screen_impl_ozone.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/ptr_util.h" +#include "base/sys_info.h" +#include "base/threading/thread_task_runner_handle.h" +#include "ui/display/types/display_snapshot.h" +#include "ui/display/types/native_display_delegate.h" +#include "ui/ozone/public/ozone_platform.h" + +namespace mus { + +namespace ws { +namespace { + +// TODO(rjkroege): Remove this code once ozone oxygen has the same +// display creation semantics as ozone drm. +// Some ozone platforms do not configure physical displays and so do not +// callback into this class via the implementation of NativeDisplayObserver. +// FixedSizeScreenConfiguration() short-circuits the implementation of display +// configuration in this case by calling the |callback| provided to +// ConfigurePhysicalDisplay() with a hard-coded |id| and |bounds|. +void FixedSizeScreenConfiguration( + const PlatformScreen::ConfiguredDisplayCallback& callback) { + callback.Run(1, gfx::Rect(1024, 768)); +} + +void GetDisplaysFinished(const std::vector<ui::DisplaySnapshot*>& displays) { + // We don't really care about list of displays, we just want the snapshots + // held by DrmDisplayManager to be updated. This only only happens when we + // call NativeDisplayDelegate::GetDisplays(). Although, this would be a good + // place to have PlatformScreen cache the snapshots if need be. +} + +} // namespace + +// static +std::unique_ptr<PlatformScreen> PlatformScreen::Create() { + return base::WrapUnique(new PlatformScreenImplOzone); +} + +PlatformScreenImplOzone::PlatformScreenImplOzone() : weak_ptr_factory_(this) {} + +PlatformScreenImplOzone::~PlatformScreenImplOzone() {} + +void PlatformScreenImplOzone::Init() { + native_display_delegate_ = + ui::OzonePlatform::GetInstance()->CreateNativeDisplayDelegate(); + native_display_delegate_->AddObserver(this); + native_display_delegate_->Initialize(); +} + +void PlatformScreenImplOzone::ConfigurePhysicalDisplay( + const PlatformScreen::ConfiguredDisplayCallback& callback) { +#if defined(OS_CHROMEOS) + if (base::SysInfo::IsRunningOnChromeOS()) { + // Kick off the configuration of the physical displays comprising the + // |PlatformScreenImplOzone| + + DCHECK(native_display_delegate_) << "DefaultDisplayManager::" + "OnConfigurationChanged requires a " + "native_display_delegate_ to work."; + + native_display_delegate_->GetDisplays( + base::Bind(&PlatformScreenImplOzone::OnDisplaysAquired, + weak_ptr_factory_.GetWeakPtr(), callback)); + + return; + } +#endif // defined(OS_CHROMEOS) + // PostTask()ed to maximize control flow similarity with the ChromeOS case. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&FixedSizeScreenConfiguration, callback)); +} + +void PlatformScreenImplOzone::OnConfigurationChanged() {} + +// The display subsystem calls |OnDisplaysAquired| to deliver |displays| +// describing the attached displays. +void PlatformScreenImplOzone::OnDisplaysAquired( + const ConfiguredDisplayCallback& callback, + const std::vector<ui::DisplaySnapshot*>& displays) { + DCHECK(native_display_delegate_) << "DefaultDisplayManager::" + "OnConfigurationChanged requires a " + "native_display_delegate_ to work."; + CHECK(displays.size() == 1) << "Mus only supports one 1 display\n"; + gfx::Point origin; + for (auto display : displays) { + if (!display->native_mode()) { + LOG(ERROR) << "Display " << display->display_id() + << " doesn't have a native mode"; + continue; + } + // Setup each native display. This places a task on the DRM thread's + // runqueue that configures the window size correctly before the call to + // Configure. + native_display_delegate_->Configure( + *display, display->native_mode(), origin, + base::Bind(&PlatformScreenImplOzone::OnDisplayConfigured, + weak_ptr_factory_.GetWeakPtr(), callback, + display->display_id(), + gfx::Rect(origin, display->native_mode()->size()))); + origin.Offset(display->native_mode()->size().width(), 0); + } +} + +void PlatformScreenImplOzone::OnDisplayConfigured( + const ConfiguredDisplayCallback& callback, + int64_t id, + const gfx::Rect& bounds, + bool success) { + if (success) { + native_display_delegate_->GetDisplays(base::Bind(&GetDisplaysFinished)); + callback.Run(id, bounds); + } else { + LOG(FATAL) << "Failed to configure display at " << bounds.ToString(); + } +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/platform_screen_impl_ozone.h b/chromium/components/mus/ws/platform_screen_impl_ozone.h new file mode 100644 index 00000000000..55949e4b45c --- /dev/null +++ b/chromium/components/mus/ws/platform_screen_impl_ozone.h @@ -0,0 +1,72 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_OZONE_H_ +#define COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_OZONE_H_ + +#include <stdint.h> + +#include <vector> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "components/mus/ws/platform_screen.h" +#include "ui/display/types/native_display_observer.h" +#include "ui/gfx/geometry/rect.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class NativeDisplayDelegate; +class DisplaySnapshot; +} + +namespace mus { +namespace ws { + +// PlatformScreenImplOzone provides the necessary functionality to configure all +// attached physical displays on the ozone platform. +class PlatformScreenImplOzone : public PlatformScreen, + public ui::NativeDisplayObserver { + public: + PlatformScreenImplOzone(); + ~PlatformScreenImplOzone() override; + + private: + // PlatformScreen + void Init() override; // Must not be called until after the ozone platform is + // initialized. + void ConfigurePhysicalDisplay( + const ConfiguredDisplayCallback& callback) override; + + // TODO(rjkroege): NativeDisplayObserver is misnamed as it tracks changes in + // the physical "Screen". Consider renaming it to NativeScreenObserver. + // ui::NativeDisplayObserver: + void OnConfigurationChanged() override; + + // Display management callback. + void OnDisplaysAquired(const ConfiguredDisplayCallback& callback, + const std::vector<ui::DisplaySnapshot*>& displays); + + // The display subsystem calls |OnDisplayConfigured| for each display that has + // been successfully configured. This in turn calls |callback_| with the + // identity and bounds of each physical display. + void OnDisplayConfigured(const ConfiguredDisplayCallback& callback, + int64_t id, + const gfx::Rect& bounds, + bool success); + + std::unique_ptr<ui::NativeDisplayDelegate> native_display_delegate_; + + base::WeakPtrFactory<PlatformScreenImplOzone> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformScreenImplOzone); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_PLATFORM_SCREEN_IMPL_OZONE_H_ diff --git a/chromium/components/mus/ws/scheduled_animation_group.cc b/chromium/components/mus/ws/scheduled_animation_group.cc new file mode 100644 index 00000000000..d76b7895597 --- /dev/null +++ b/chromium/components/mus/ws/scheduled_animation_group.cc @@ -0,0 +1,356 @@ +// 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 "components/mus/ws/scheduled_animation_group.h" + +#include <set> + +#include "components/mus/ws/server_window.h" + +using mus::mojom::AnimationProperty; + +namespace mus { +namespace ws { +namespace { + +using Sequences = std::vector<ScheduledAnimationSequence>; + +// Gets the value of |property| from |window| into |value|. +void GetValueFromWindow(const ServerWindow* window, + AnimationProperty property, + ScheduledAnimationValue* value) { + switch (property) { + case AnimationProperty::NONE: + NOTREACHED(); + break; + case AnimationProperty::OPACITY: + value->float_value = window->opacity(); + break; + case AnimationProperty::TRANSFORM: + value->transform = window->transform(); + break; + } +} + +// Sets the value of |property| from |value| into |window|. +void SetWindowPropertyFromValue(ServerWindow* window, + AnimationProperty property, + const ScheduledAnimationValue& value) { + switch (property) { + case AnimationProperty::NONE: + break; + case AnimationProperty::OPACITY: + window->SetOpacity(value.float_value); + break; + case AnimationProperty::TRANSFORM: + window->SetTransform(value.transform); + break; + } +} + +// Sets the value of |property| into |window| between two points. +void SetWindowPropertyFromValueBetween(ServerWindow* window, + 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 AnimationProperty::NONE: + break; + case AnimationProperty::OPACITY: + window->SetOpacity(gfx::Tween::FloatValueBetween( + tween_value, start.float_value, target.float_value)); + break; + case AnimationProperty::TRANSFORM: + window->SetTransform(gfx::Tween::TransformValueBetween( + tween_value, start.transform, target.transform)); + break; + } +} + +// TODO(mfomitchev): Use struct traits for this? +gfx::Tween::Type AnimationTypeToTweenType(mojom::AnimationTweenType type) { + switch (type) { + case mojom::AnimationTweenType::LINEAR: + return gfx::Tween::LINEAR; + case mojom::AnimationTweenType::EASE_IN: + return gfx::Tween::EASE_IN; + case mojom::AnimationTweenType::EASE_OUT: + return gfx::Tween::EASE_OUT; + case mojom::AnimationTweenType::EASE_IN_OUT: + return gfx::Tween::EASE_IN_OUT; + } + return gfx::Tween::LINEAR; +} + +void ConvertToScheduledValue(const mojom::AnimationValue& transport_value, + ScheduledAnimationValue* value) { + value->float_value = transport_value.float_value; + value->transform = transport_value.transform; +} + +void ConvertToScheduledElement(const mojom::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 != AnimationProperty::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 mojom::AnimationValue& value) { + bool result; + switch (property) { + case AnimationProperty::NONE: + NOTREACHED(); + return false; + case AnimationProperty::OPACITY: + result = value.float_value >= 0.f && value.float_value <= 1.f; + DVLOG_IF(1, !result) << "Invalid AnimationValue for opacity: " + << value.float_value; + return result; + case AnimationProperty::TRANSFORM: + return true; + } + return false; +} + +bool IsAnimationElementValid(const mojom::AnimationElement& element) { + if (element.property == AnimationProperty::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 mojom::AnimationSequence& sequence) { + if (sequence.elements.size() == 0) { + DVLOG(1) << "Empty or null AnimationSequence - invalid."; + return false; + } + + for (size_t i = 0; i < sequence.elements.size(); ++i) { + if (!IsAnimationElementValid(*sequence.elements[i])) + return false; + } + return true; +} + +bool IsAnimationGroupValid(const mojom::AnimationGroup& transport_group) { + if (transport_group.sequences.size() == 0) { + DVLOG(1) << "Empty or null AnimationGroup - invalid; window_id=" + << transport_group.window_id; + 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 |window| and placed into |element|. +void GetStartValueFromWindowIfNecessary(const ServerWindow* window, + ScheduledAnimationElement* element) { + if (element->property != AnimationProperty::NONE && + !element->is_start_valid) { + GetValueFromWindow(window, 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); + } +} + +// Set |window|'s specified |property| to the value resulting from running all +// the |sequences|. +void SetPropertyToTargetProperty(ServerWindow* window, + mojom::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; + std::unique_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()) + SetWindowPropertyFromValue(window, property, *value); +} + +void ConvertSequenceToScheduled( + const mojom::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(ServerWindow* window, + ScheduledAnimationSequence* sequence, + base::TimeTicks now) { + ScheduledAnimationElement* element = + &(sequence->elements[sequence->current_index]); + while (element->start_time + element->duration < now) { + SetWindowPropertyFromValue(window, element->property, + element->target_value); + if (++sequence->current_index == sequence->elements.size()) { + if (!sequence->run_until_stopped && --sequence->cycle_count == 0) { + return false; + } + + sequence->current_index = 0; + } + sequence->elements[sequence->current_index].start_time = + element->start_time + element->duration; + element = &(sequence->elements[sequence->current_index]); + GetStartValueFromWindowIfNecessary(window, 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() : float_value(0) {} +ScheduledAnimationValue::~ScheduledAnimationValue() {} + +ScheduledAnimationElement::ScheduledAnimationElement() + : property(AnimationProperty::OPACITY), + tween_type(gfx::Tween::EASE_IN), + is_start_valid(false) {} +ScheduledAnimationElement::ScheduledAnimationElement( + const ScheduledAnimationElement& other) = default; +ScheduledAnimationElement::~ScheduledAnimationElement() {} + +ScheduledAnimationSequence::ScheduledAnimationSequence() + : run_until_stopped(false), cycle_count(0), current_index(0u) {} +ScheduledAnimationSequence::ScheduledAnimationSequence( + const ScheduledAnimationSequence& other) = default; +ScheduledAnimationSequence::~ScheduledAnimationSequence() {} + +ScheduledAnimationGroup::~ScheduledAnimationGroup() {} + +// static +std::unique_ptr<ScheduledAnimationGroup> ScheduledAnimationGroup::Create( + ServerWindow* window, + base::TimeTicks now, + uint32_t id, + const mojom::AnimationGroup& transport_group) { + if (!IsAnimationGroupValid(transport_group)) + return nullptr; + + std::unique_ptr<ScheduledAnimationGroup> group( + new ScheduledAnimationGroup(window, id, now)); + group->sequences_.resize(transport_group.sequences.size()); + for (size_t i = 0; i < transport_group.sequences.size(); ++i) { + const mojom::AnimationSequence& transport_sequence( + *(transport_group.sequences[i])); + DCHECK_NE(0u, transport_sequence.elements.size()); + ConvertSequenceToScheduled(transport_sequence, now, &group->sequences_[i]); + } + return group; +} + +void ScheduledAnimationGroup::ObtainStartValues() { + for (ScheduledAnimationSequence& sequence : sequences_) + GetStartValueFromWindowIfNecessary(window_, &(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 != AnimationProperty::NONE) { + SetPropertyToTargetProperty(window_, property, sequences_); + } + } +} + +bool ScheduledAnimationGroup::Tick(base::TimeTicks time) { + for (Sequences::iterator i = sequences_.begin(); i != sequences_.end();) { + if (!AdvanceSequence(window_, &(*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(); + SetWindowPropertyFromValueBetween( + window_, active_element.property, percent, active_element.tween_type, + active_element.start_value, active_element.target_value); + ++i; + } + return sequences_.empty(); +} + +ScheduledAnimationGroup::ScheduledAnimationGroup(ServerWindow* window, + uint32_t id, + base::TimeTicks time_scheduled) + : window_(window), id_(id), time_scheduled_(time_scheduled) {} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/scheduled_animation_group.h b/chromium/components/mus/ws/scheduled_animation_group.h new file mode 100644 index 00000000000..2c19bcd2e09 --- /dev/null +++ b/chromium/components/mus/ws/scheduled_animation_group.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_SCHEDULED_ANIMATION_GROUP_H_ +#define COMPONENTS_MUS_WS_SCHEDULED_ANIMATION_GROUP_H_ + +#include <memory> +#include <vector> + +#include "base/time/time.h" +#include "components/mus/public/interfaces/animations.mojom.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/transform.h" + +namespace mus { +namespace ws { + +class ServerWindow; + +struct ScheduledAnimationValue { + ScheduledAnimationValue(); + ~ScheduledAnimationValue(); + + float float_value; + gfx::Transform transform; +}; + +struct ScheduledAnimationElement { + ScheduledAnimationElement(); + // Needed because we call resize() vector of elements. + ScheduledAnimationElement(const ScheduledAnimationElement& other); + ~ScheduledAnimationElement(); + + mojom::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(); + // Needed because we call resize() and erase() on vector of sequences. + ScheduledAnimationSequence(const ScheduledAnimationSequence& other); + ~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 mojom::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 std::unique_ptr<ScheduledAnimationGroup> Create( + ServerWindow* window, + base::TimeTicks now, + uint32_t id, + const mojom::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); + + ServerWindow* window() { return window_; } + + private: + ScheduledAnimationGroup(ServerWindow* window, + uint32_t id, + base::TimeTicks time_scheduled); + + ServerWindow* window_; + const uint32_t id_; + base::TimeTicks time_scheduled_; + std::vector<ScheduledAnimationSequence> sequences_; + + DISALLOW_COPY_AND_ASSIGN(ScheduledAnimationGroup); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SCHEDULED_ANIMATION_GROUP_H_ diff --git a/chromium/components/mus/ws/scheduled_animation_group_unittest.cc b/chromium/components/mus/ws/scheduled_animation_group_unittest.cc new file mode 100644 index 00000000000..27c2f76c9b7 --- /dev/null +++ b/chromium/components/mus/ws/scheduled_animation_group_unittest.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 "components/mus/ws/scheduled_animation_group.h" + +#include "components/mus/public/interfaces/animations.mojom.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +using mus::mojom::AnimationProperty; +using mus::mojom::AnimationTweenType; +using mus::mojom::AnimationGroup; +using mus::mojom::AnimationSequence; +using mus::mojom::AnimationElement; +using mus::mojom::AnimationValue; + +namespace mus { +namespace ws { +namespace { + +bool IsAnimationGroupValid(const AnimationGroup& transport_group) { + TestServerWindowDelegate window_delegate; + ServerWindow window(&window_delegate, WindowId()); + std::unique_ptr<ScheduledAnimationGroup> group( + ScheduledAnimationGroup::Create(&window, 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 = AnimationProperty::OPACITY; + element.tween_type = AnimationTweenType::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)); + + // All transforms are valid. + element.property = AnimationProperty::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 = AnimationProperty::OPACITY; + element2.tween_type = AnimationTweenType::LINEAR; + + // Element with no target_value is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element2.property = AnimationProperty::NONE; + EXPECT_TRUE(IsAnimationGroupValid(group)); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/server_window.cc b/chromium/components/mus/ws/server_window.cc new file mode 100644 index 00000000000..f6a9304a03b --- /dev/null +++ b/chromium/components/mus/ws/server_window.cc @@ -0,0 +1,470 @@ +// 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 "components/mus/ws/server_window.h" + +#include <inttypes.h> +#include <stddef.h> + +#include "base/strings/stringprintf.h" +#include "components/mus/common/transient_window_utils.h" +#include "components/mus/public/interfaces/window_manager.mojom.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/server_window_observer.h" +#include "components/mus/ws/server_window_surface_manager.h" + +namespace mus { + +namespace ws { + +ServerWindow::ServerWindow(ServerWindowDelegate* delegate, const WindowId& id) + : ServerWindow(delegate, id, Properties()) {} + +ServerWindow::ServerWindow(ServerWindowDelegate* delegate, + const WindowId& id, + const Properties& properties) + : delegate_(delegate), + id_(id), + parent_(nullptr), + stacking_target_(nullptr), + transient_parent_(nullptr), + is_modal_(false), + visible_(false), + cursor_id_(mojom::Cursor::CURSOR_NULL), + non_client_cursor_id_(mojom::Cursor::CURSOR_NULL), + opacity_(1), + can_focus_(true), + properties_(properties), + // Don't notify newly added observers during notification. This causes + // problems for code that adds an observer as part of an observer + // notification (such as ServerWindowDrawTracker). + observers_( + base::ObserverList<ServerWindowObserver>::NOTIFY_EXISTING_ONLY) { + DCHECK(delegate); // Must provide a delegate. +} + +ServerWindow::~ServerWindow() { + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, OnWindowDestroying(this)); + + if (transient_parent_) + transient_parent_->RemoveTransientWindow(this); + + // Destroy transient children, only after we've removed ourselves from our + // parent, as destroying an active transient child may otherwise attempt to + // refocus us. + Windows transient_children(transient_children_); + STLDeleteElements(&transient_children); + DCHECK(transient_children_.empty()); + + while (!children_.empty()) + children_.front()->parent()->Remove(children_.front()); + + if (parent_) + parent_->Remove(this); + + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, OnWindowDestroyed(this)); +} + +void ServerWindow::AddObserver(ServerWindowObserver* observer) { + observers_.AddObserver(observer); +} + +void ServerWindow::RemoveObserver(ServerWindowObserver* observer) { + DCHECK(observers_.HasObserver(observer)); + observers_.RemoveObserver(observer); +} + +void ServerWindow::CreateSurface(mojom::SurfaceType surface_type, + mojo::InterfaceRequest<mojom::Surface> request, + mojom::SurfaceClientPtr client) { + GetOrCreateSurfaceManager()->CreateSurface(surface_type, std::move(request), + std::move(client)); +} + +void ServerWindow::Add(ServerWindow* 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. + child->Reorder(children_.back(), mojom::OrderDirection::ABOVE); + return; + } + + ServerWindow* old_parent = child->parent(); + FOR_EACH_OBSERVER(ServerWindowObserver, child->observers_, + OnWillChangeWindowHierarchy(child, this, old_parent)); + + if (child->parent()) + child->parent()->RemoveImpl(child); + + child->parent_ = this; + children_.push_back(child); + + // Stack the child properly if it is a transient child of a sibling. + if (child->transient_parent_ && child->transient_parent_->parent() == this) + RestackTransientDescendants(child->transient_parent_, &GetStackingTarget, + &ReorderImpl); + + FOR_EACH_OBSERVER(ServerWindowObserver, child->observers_, + OnWindowHierarchyChanged(child, this, old_parent)); +} + +void ServerWindow::Remove(ServerWindow* child) { + // We assume validation checks happened else where. + DCHECK(child); + DCHECK(child != this); + DCHECK(child->parent() == this); + + FOR_EACH_OBSERVER(ServerWindowObserver, child->observers_, + OnWillChangeWindowHierarchy(child, nullptr, this)); + RemoveImpl(child); + + // Stack the child properly if it is a transient child of a sibling. + if (child->transient_parent_ && child->transient_parent_->parent() == this) + RestackTransientDescendants(child->transient_parent_, &GetStackingTarget, + &ReorderImpl); + + FOR_EACH_OBSERVER(ServerWindowObserver, child->observers_, + OnWindowHierarchyChanged(child, nullptr, this)); +} + +void ServerWindow::Reorder(ServerWindow* relative, + mojom::OrderDirection direction) { + ReorderImpl(this, relative, direction); +} + +void ServerWindow::StackChildAtBottom(ServerWindow* child) { + // There's nothing to do if the child is already at the bottom. + if (children_.size() <= 1 || child == children_.front()) + return; + child->Reorder(children_.front(), mojom::OrderDirection::BELOW); +} + +void ServerWindow::StackChildAtTop(ServerWindow* child) { + // There's nothing to do if the child is already at the top. + if (children_.size() <= 1 || child == children_.back()) + return; + child->Reorder(children_.back(), mojom::OrderDirection::ABOVE); +} + +void ServerWindow::SetBounds(const gfx::Rect& bounds) { + if (bounds_ == bounds) + return; + + // TODO(fsamuel): figure out how will this work with CompositorFrames. + + const gfx::Rect old_bounds = bounds_; + bounds_ = bounds; + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWindowBoundsChanged(this, old_bounds, bounds)); +} + +void ServerWindow::SetClientArea( + const gfx::Insets& insets, + const std::vector<gfx::Rect>& additional_client_areas) { + if (client_area_ == insets && + additional_client_areas == additional_client_areas_) { + return; + } + + additional_client_areas_ = additional_client_areas; + client_area_ = insets; + FOR_EACH_OBSERVER( + ServerWindowObserver, observers_, + OnWindowClientAreaChanged(this, insets, additional_client_areas)); +} + +void ServerWindow::SetHitTestMask(const gfx::Rect& mask) { + hit_test_mask_.reset(new gfx::Rect(mask)); +} + +void ServerWindow::ClearHitTestMask() { + hit_test_mask_.reset(); +} + +const ServerWindow* ServerWindow::GetRoot() const { + return delegate_->GetRootWindow(this); +} + +std::vector<const ServerWindow*> ServerWindow::GetChildren() const { + std::vector<const ServerWindow*> children; + children.reserve(children_.size()); + for (size_t i = 0; i < children_.size(); ++i) + children.push_back(children_[i]); + return children; +} + +std::vector<ServerWindow*> ServerWindow::GetChildren() { + // TODO(sky): rename to children() and fix return type. + return children_; +} + +ServerWindow* ServerWindow::GetChildWindow(const WindowId& window_id) { + if (id_ == window_id) + return this; + + for (ServerWindow* child : children_) { + ServerWindow* window = child->GetChildWindow(window_id); + if (window) + return window; + } + + return nullptr; +} + +bool ServerWindow::AddTransientWindow(ServerWindow* child) { + // A system modal window cannot become a transient child. + if (child->is_modal() && !child->transient_parent()) + return false; + + if (child->transient_parent()) + child->transient_parent()->RemoveTransientWindow(child); + + DCHECK(std::find(transient_children_.begin(), transient_children_.end(), + child) == transient_children_.end()); + transient_children_.push_back(child); + child->transient_parent_ = this; + + // Restack |child| properly above its transient parent, if they share the same + // parent. + if (child->parent() == parent()) + RestackTransientDescendants(this, &GetStackingTarget, &ReorderImpl); + + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnTransientWindowAdded(this, child)); + return true; +} + +void ServerWindow::RemoveTransientWindow(ServerWindow* child) { + Windows::iterator i = + std::find(transient_children_.begin(), transient_children_.end(), child); + DCHECK(i != transient_children_.end()); + transient_children_.erase(i); + DCHECK_EQ(this, child->transient_parent()); + child->transient_parent_ = nullptr; + + // If |child| and its former transient parent share the same parent, |child| + // should be restacked properly so it is not among transient children of its + // former parent, anymore. + if (parent() == child->parent()) + RestackTransientDescendants(this, &GetStackingTarget, &ReorderImpl); + + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnTransientWindowRemoved(this, child)); +} + +void ServerWindow::SetModal() { + is_modal_ = true; +} + +bool ServerWindow::Contains(const ServerWindow* window) const { + for (const ServerWindow* parent = window; parent; parent = parent->parent_) { + if (parent == this) + return true; + } + return false; +} + +void ServerWindow::SetVisible(bool value) { + if (visible_ == value) + return; + + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWillChangeWindowVisibility(this)); + visible_ = value; + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWindowVisibilityChanged(this)); +} + +void ServerWindow::SetOpacity(float value) { + if (value == opacity_) + return; + float old_opacity = opacity_; + opacity_ = value; + delegate_->OnScheduleWindowPaint(this); + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWindowOpacityChanged(this, old_opacity, opacity_)); +} + +void ServerWindow::SetPredefinedCursor(mus::mojom::Cursor value) { + if (value == cursor_id_) + return; + cursor_id_ = value; + FOR_EACH_OBSERVER( + ServerWindowObserver, observers_, + OnWindowPredefinedCursorChanged(this, static_cast<int32_t>(value))); +} + +void ServerWindow::SetNonClientCursor(mus::mojom::Cursor value) { + if (value == non_client_cursor_id_) + return; + non_client_cursor_id_ = value; + FOR_EACH_OBSERVER( + ServerWindowObserver, observers_, + OnWindowNonClientCursorChanged(this, static_cast<int32_t>(value))); +} + +void ServerWindow::SetTransform(const gfx::Transform& transform) { + if (transform_ == transform) + return; + + transform_ = transform; + delegate_->OnScheduleWindowPaint(this); +} + +void ServerWindow::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 nullptr, so + // there's + // no change. + return; + } + + if (value) { + properties_[name] = *value; + } else if (it != properties_.end()) { + properties_.erase(it); + } + + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWindowSharedPropertyChanged(this, name, value)); +} + +std::string ServerWindow::GetName() const { + auto it = properties_.find(mojom::WindowManager::kName_Property); + if (it == properties_.end()) + return std::string(); + return std::string(it->second.begin(), it->second.end()); +} + +void ServerWindow::SetTextInputState(const ui::TextInputState& state) { + const bool changed = !(text_input_state_ == state); + if (changed) { + text_input_state_ = state; + // keyboard even if the state is not changed. So we have to notify + // |observers_|. + FOR_EACH_OBSERVER(ServerWindowObserver, observers_, + OnWindowTextInputStateChanged(this, state)); + } +} + +bool ServerWindow::IsDrawn() const { + const ServerWindow* root = delegate_->GetRootWindow(this); + if (!root || !root->visible()) + return false; + const ServerWindow* window = this; + while (window && window != root && window->visible()) + window = window->parent(); + return root == window; +} + +void ServerWindow::DestroySurfacesScheduledForDestruction() { + if (!surface_manager_) + return; + ServerWindowSurface* surface = surface_manager_->GetDefaultSurface(); + if (surface) + surface->DestroySurfacesScheduledForDestruction(); + + surface = surface_manager_->GetUnderlaySurface(); + if (surface) + surface->DestroySurfacesScheduledForDestruction(); +} + +ServerWindowSurfaceManager* ServerWindow::GetOrCreateSurfaceManager() { + if (!surface_manager_.get()) + surface_manager_.reset(new ServerWindowSurfaceManager(this)); + return surface_manager_.get(); +} + +void ServerWindow::SetUnderlayOffset(const gfx::Vector2d& offset) { + if (offset == underlay_offset_) + return; + + underlay_offset_ = offset; + delegate_->OnScheduleWindowPaint(this); +} + +#if !defined(NDEBUG) +std::string ServerWindow::GetDebugWindowHierarchy() const { + std::string result; + BuildDebugInfo(std::string(), &result); + return result; +} + +void ServerWindow::BuildDebugInfo(const std::string& depth, + std::string* result) const { + std::string name = GetName(); + *result += base::StringPrintf( + "%sid=%d,%d visible=%s bounds=%d,%d %dx%d %s\n", depth.c_str(), + static_cast<int>(id_.client_id), static_cast<int>(id_.window_id), + visible_ ? "true" : "false", bounds_.x(), bounds_.y(), bounds_.width(), + bounds_.height(), !name.empty() ? name.c_str() : "(no name)"); + for (const ServerWindow* child : children_) + child->BuildDebugInfo(depth + " ", result); +} +#endif + +void ServerWindow::RemoveImpl(ServerWindow* window) { + window->parent_ = nullptr; + children_.erase(std::find(children_.begin(), children_.end(), window)); +} + +void ServerWindow::OnStackingChanged() { + if (stacking_target_) { + Windows::const_iterator window_i = std::find( + parent()->children().begin(), parent()->children().end(), this); + DCHECK(window_i != parent()->children().end()); + if (window_i != parent()->children().begin() && + (*(window_i - 1) == stacking_target_)) { + return; + } + } + RestackTransientDescendants(this, &GetStackingTarget, &ReorderImpl); +} + +// static +void ServerWindow::ReorderImpl(ServerWindow* window, + ServerWindow* relative, + mojom::OrderDirection direction) { + DCHECK(relative); + DCHECK_NE(window, relative); + DCHECK_EQ(window->parent(), relative->parent()); + + if (!AdjustStackingForTransientWindows(&window, &relative, &direction, + window->stacking_target_)) + return; + + window->parent_->children_.erase(std::find(window->parent_->children_.begin(), + window->parent_->children_.end(), + window)); + Windows::iterator i = std::find(window->parent_->children_.begin(), + window->parent_->children_.end(), relative); + if (direction == mojom::OrderDirection::ABOVE) { + DCHECK(i != window->parent_->children_.end()); + window->parent_->children_.insert(++i, window); + } else if (direction == mojom::OrderDirection::BELOW) { + DCHECK(i != window->parent_->children_.end()); + window->parent_->children_.insert(i, window); + } + FOR_EACH_OBSERVER(ServerWindowObserver, window->observers_, + OnWindowReordered(window, relative, direction)); + window->OnStackingChanged(); +} + +// static +ServerWindow** ServerWindow::GetStackingTarget(ServerWindow* window) { + return &window->stacking_target_; +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/server_window.h b/chromium/components/mus/ws/server_window.h new file mode 100644 index 00000000000..75f4f081406 --- /dev/null +++ b/chromium/components/mus/ws/server_window.h @@ -0,0 +1,249 @@ +// 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 COMPONENTS_MUS_WS_SERVER_WINDOW_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_H_ + +#include <stdint.h> + +#include <map> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/public/interfaces/surface.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/server_window_surface.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/transform.h" +#include "ui/platform_window/text_input_state.h" + +namespace mus { +namespace ws { + +class ServerWindowDelegate; +class ServerWindowObserver; +class ServerWindowSurfaceManager; + +// Server side representation of a window. 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 +// window is a child and not already in position. +// +// ServerWindows do not own their children. If you delete a window that has +// children the children are implicitly removed. Similarly if a window has a +// parent and the window is deleted the deleted window is implicitly removed +// from the parent. +class ServerWindow { + public: + using Properties = std::map<std::string, std::vector<uint8_t>>; + using Windows = std::vector<ServerWindow*>; + + ServerWindow(ServerWindowDelegate* delegate, const WindowId& id); + ServerWindow(ServerWindowDelegate* delegate, + const WindowId& id, + const Properties& properties); + ~ServerWindow(); + + void AddObserver(ServerWindowObserver* observer); + void RemoveObserver(ServerWindowObserver* observer); + + // Creates a new surface of the specified type, replacing the existing. + void CreateSurface(mojom::SurfaceType surface_type, + mojo::InterfaceRequest<mojom::Surface> request, + mojom::SurfaceClientPtr client); + + const WindowId& id() const { return id_; } + + void Add(ServerWindow* child); + void Remove(ServerWindow* child); + void Reorder(ServerWindow* relative, mojom::OrderDirection diretion); + void StackChildAtBottom(ServerWindow* child); + void StackChildAtTop(ServerWindow* child); + + const gfx::Rect& bounds() const { return bounds_; } + // Sets the bounds. If the size changes this implicitly resets the client + // area to fill the whole bounds. + void SetBounds(const gfx::Rect& bounds); + + const std::vector<gfx::Rect>& additional_client_areas() const { + return additional_client_areas_; + } + const gfx::Insets& client_area() const { return client_area_; } + void SetClientArea(const gfx::Insets& insets, + const std::vector<gfx::Rect>& additional_client_areas); + + const gfx::Rect* hit_test_mask() const { return hit_test_mask_.get(); } + void SetHitTestMask(const gfx::Rect& mask); + void ClearHitTestMask(); + + int32_t cursor() const { return static_cast<int32_t>(cursor_id_); } + int32_t non_client_cursor() const { + return static_cast<int32_t>(non_client_cursor_id_); + } + + const ServerWindow* parent() const { return parent_; } + ServerWindow* parent() { return parent_; } + + const ServerWindow* GetRoot() const; + ServerWindow* GetRoot() { + return const_cast<ServerWindow*>( + const_cast<const ServerWindow*>(this)->GetRoot()); + } + + std::vector<const ServerWindow*> GetChildren() const; + std::vector<ServerWindow*> GetChildren(); + const Windows& children() const { return children_; } + + // Returns the ServerWindow object with the provided |id| if it lies in a + // subtree of |this|. + ServerWindow* GetChildWindow(const WindowId& id); + + // Transient window management. + // Adding transient child fails if the child window is modal to system. + bool AddTransientWindow(ServerWindow* child); + void RemoveTransientWindow(ServerWindow* child); + + ServerWindow* transient_parent() { return transient_parent_; } + const ServerWindow* transient_parent() const { return transient_parent_; } + + const Windows& transient_children() const { return transient_children_; } + + bool is_modal() const { return is_modal_; } + void SetModal(); + + // Returns true if this contains |window| or is |window|. + bool Contains(const ServerWindow* window) const; + + // Returns the visibility requested by this window. IsDrawn() returns whether + // the window is actually visible on screen. + bool visible() const { return visible_; } + void SetVisible(bool value); + + float opacity() const { return opacity_; } + void SetOpacity(float value); + + void SetPredefinedCursor(mus::mojom::Cursor cursor_id); + void SetNonClientCursor(mus::mojom::Cursor cursor_id); + + 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); + + std::string GetName() const; + + void SetTextInputState(const ui::TextInputState& state); + const ui::TextInputState& text_input_state() const { + return text_input_state_; + } + + void set_can_focus(bool can_focus) { can_focus_ = can_focus; } + bool can_focus() const { return can_focus_; } + + // Returns true if this window is attached to a root and all ancestors are + // visible. + bool IsDrawn() const; + + // Called when its appropriate to destroy surfaces scheduled for destruction. + void DestroySurfacesScheduledForDestruction(); + + const gfx::Insets& extended_hit_test_region() const { + return extended_hit_test_region_; + } + void set_extended_hit_test_region(const gfx::Insets& insets) { + extended_hit_test_region_ = insets; + } + + ServerWindowSurfaceManager* GetOrCreateSurfaceManager(); + ServerWindowSurfaceManager* surface_manager() { + return surface_manager_.get(); + } + const ServerWindowSurfaceManager* surface_manager() const { + return surface_manager_.get(); + } + + // Offset of the underlay from the the window bounds (used for shadows). + const gfx::Vector2d& underlay_offset() const { return underlay_offset_; } + void SetUnderlayOffset(const gfx::Vector2d& offset); + + ServerWindowDelegate* delegate() { return delegate_; } + +#if !defined(NDEBUG) + std::string GetDebugWindowHierarchy() const; + void BuildDebugInfo(const std::string& depth, std::string* result) const; +#endif + + private: + // Implementation of removing a window. Doesn't send any notification. + void RemoveImpl(ServerWindow* window); + + // Called when this window's stacking order among its siblings is changed. + void OnStackingChanged(); + + static void ReorderImpl(ServerWindow* window, + ServerWindow* relative, + mojom::OrderDirection diretion); + + // Returns a pointer to the stacking target that can be used by + // RestackTransientDescendants. + static ServerWindow** GetStackingTarget(ServerWindow* window); + + ServerWindowDelegate* delegate_; + const WindowId id_; + ServerWindow* parent_; + Windows children_; + + // Transient window management. + // If non-null we're actively restacking transient as the result of a + // transient ancestor changing. + ServerWindow* stacking_target_; + ServerWindow* transient_parent_; + Windows transient_children_; + + bool is_modal_; + bool visible_; + gfx::Rect bounds_; + gfx::Insets client_area_; + std::vector<gfx::Rect> additional_client_areas_; + std::unique_ptr<ServerWindowSurfaceManager> surface_manager_; + mojom::Cursor cursor_id_; + mojom::Cursor non_client_cursor_id_; + float opacity_; + bool can_focus_; + gfx::Transform transform_; + ui::TextInputState text_input_state_; + + Properties properties_; + + gfx::Vector2d underlay_offset_; + + // The hit test for windows extends outside the bounds of the window by this + // amount. + gfx::Insets extended_hit_test_region_; + + // Mouse events outside the hit test mask don't hit the window. An empty mask + // means all events miss the window. If null there is no mask. + std::unique_ptr<gfx::Rect> hit_test_mask_; + + base::ObserverList<ServerWindowObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(ServerWindow); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_H_ diff --git a/chromium/components/mus/ws/server_window_delegate.h b/chromium/components/mus/ws/server_window_delegate.h new file mode 100644 index 00000000000..24d6bbd0daa --- /dev/null +++ b/chromium/components/mus/ws/server_window_delegate.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 COMPONENTS_MUS_WS_SERVER_WINDOW_DELEGATE_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_DELEGATE_H_ + +#include <memory> + +#include "components/mus/public/interfaces/mus_constants.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" + +namespace mus { + +class SurfacesState; + +namespace ws { + +struct ClientWindowId; +class ServerWindow; +struct WindowId; + +class ServerWindowDelegate { + public: + virtual SurfacesState* GetSurfacesState() = 0; + + virtual void OnScheduleWindowPaint(ServerWindow* window) = 0; + + // Returns the root of the window tree to which this |window| is attached. + // Returns null if this window is not attached up through to a root window. + virtual const ServerWindow* GetRootWindow( + const ServerWindow* window) const = 0; + + // Schedules a callback to DestroySurfacesScheduledForDestruction() at the + // appropriate time, which may be synchronously. + virtual void ScheduleSurfaceDestruction(ServerWindow* window) = 0; + + protected: + virtual ~ServerWindowDelegate() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_DELEGATE_H_ diff --git a/chromium/components/mus/ws/server_window_drawn_tracker.cc b/chromium/components/mus/ws/server_window_drawn_tracker.cc new file mode 100644 index 00000000000..cdbca8ccddf --- /dev/null +++ b/chromium/components/mus/ws/server_window_drawn_tracker.cc @@ -0,0 +1,136 @@ +// 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 "components/mus/ws/server_window_drawn_tracker.h" + +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_drawn_tracker_observer.h" + +namespace mus { + +namespace ws { + +ServerWindowDrawnTracker::ServerWindowDrawnTracker( + ServerWindow* window, + ServerWindowDrawnTrackerObserver* observer) + : window_(window), observer_(observer), drawn_(window->IsDrawn()) { + AddObservers(); +} + +ServerWindowDrawnTracker::~ServerWindowDrawnTracker() { + RemoveObservers(); +} + +void ServerWindowDrawnTracker::SetDrawn(ServerWindow* ancestor, bool drawn) { + // If |windows_| is empty when this code runs, that means |window_| has been + // destroyed. So set |window_| to nullptr, but make sure the right value is + // sent to OnDrawnStateChanged(). + ServerWindow* window = window_; + if (windows_.empty()) + window_ = nullptr; + + if (drawn == drawn_) + return; + + drawn_ = drawn; + observer_->OnDrawnStateChanged(ancestor, window, drawn); +} + +void ServerWindowDrawnTracker::AddObservers() { + if (!window_) + return; + + for (ServerWindow* v = window_; v; v = v->parent()) { + v->AddObserver(this); + windows_.insert(v); + } +} + +void ServerWindowDrawnTracker::RemoveObservers() { + for (ServerWindow* window : windows_) + window->RemoveObserver(this); + + windows_.clear(); +} + +void ServerWindowDrawnTracker::OnWindowDestroying(ServerWindow* window) { + if (!drawn_) + return; + observer_->OnDrawnStateWillChange(window->parent(), window_, false); +} + +void ServerWindowDrawnTracker::OnWindowDestroyed(ServerWindow* window) { + // As windows are removed before being destroyed, resulting in + // OnWindowHierarchyChanged() and us removing ourself as an observer, the only + // window we should ever get notified of destruction on is |window_|. + DCHECK_EQ(window, window_); + RemoveObservers(); + SetDrawn(nullptr, false); +} + +void ServerWindowDrawnTracker::OnWillChangeWindowHierarchy( + ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) { + bool new_is_drawn = new_parent && new_parent->IsDrawn(); + if (new_is_drawn) { + for (ServerWindow* w = window_; new_is_drawn && w != old_parent; + w = w->parent()) { + new_is_drawn = w->visible(); + } + } + if (drawn_ != new_is_drawn) { + observer_->OnDrawnStateWillChange(new_is_drawn ? nullptr : old_parent, + window_, new_is_drawn); + } +} + +void ServerWindowDrawnTracker::OnWindowHierarchyChanged( + ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) { + RemoveObservers(); + AddObservers(); + const bool is_drawn = window_->IsDrawn(); + SetDrawn(is_drawn ? nullptr : old_parent, is_drawn); +} + +void ServerWindowDrawnTracker::OnWillChangeWindowVisibility( + ServerWindow* window) { + bool will_change = false; + if (drawn_) { + // If |window_| is currently drawn, then any change of visibility of the + // windows will toggle the drawn status. + will_change = true; + } else { + // If |window| is currently visible, then it's becoming invisible, and so + // |window_| will remain not drawn. + if (window->visible()) { + will_change = false; + } else { + bool is_drawn = (window->GetRoot() == window) || + (window->parent() && window->parent()->IsDrawn()); + if (is_drawn) { + for (ServerWindow* w = window_; is_drawn && w != window; + w = w->parent()) + is_drawn = w->visible(); + } + will_change = drawn_ != is_drawn; + } + } + if (will_change) { + bool new_is_drawn = !drawn_; + observer_->OnDrawnStateWillChange(new_is_drawn ? nullptr : window->parent(), + window_, new_is_drawn); + } +} + +void ServerWindowDrawnTracker::OnWindowVisibilityChanged(ServerWindow* window) { + const bool is_drawn = window_->IsDrawn(); + SetDrawn(is_drawn ? nullptr : window->parent(), is_drawn); +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/server_window_drawn_tracker.h b/chromium/components/mus/ws/server_window_drawn_tracker.h new file mode 100644 index 00000000000..36f2de27590 --- /dev/null +++ b/chromium/components/mus/ws/server_window_drawn_tracker.h @@ -0,0 +1,65 @@ +// 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 COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_H_ + +#include <set> + +#include "base/macros.h" +#include "components/mus/ws/server_window_observer.h" + +namespace mus { + +namespace ws { + +class ServerWindowDrawnTrackerObserver; + +// ServerWindowDrawnTracker notifies its observer any time the drawn state of +// the supplied window changes. +// +// NOTE: you must ensure this class is destroyed before the root. +class ServerWindowDrawnTracker : public ServerWindowObserver { + public: + ServerWindowDrawnTracker(ServerWindow* window, + ServerWindowDrawnTrackerObserver* observer); + ~ServerWindowDrawnTracker() override; + + ServerWindow* window() { return window_; } + + private: + void SetDrawn(ServerWindow* ancestor, bool drawn); + + // Adds |this| as an observer to |window_| and its ancestors. + void AddObservers(); + + // Stops observerving any windows we added as an observer in AddObservers(). + void RemoveObservers(); + + // ServerWindowObserver: + void OnWindowDestroying(ServerWindow* window) override; + void OnWindowDestroyed(ServerWindow* window) override; + void OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) override; + void OnWindowHierarchyChanged(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) override; + void OnWillChangeWindowVisibility(ServerWindow* window) override; + void OnWindowVisibilityChanged(ServerWindow* window) override; + + ServerWindow* window_; + ServerWindowDrawnTrackerObserver* observer_; + bool drawn_; + // Set of windows we're observing. This is |window_| and all its ancestors. + std::set<ServerWindow*> windows_; + + DISALLOW_COPY_AND_ASSIGN(ServerWindowDrawnTracker); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_H_ diff --git a/chromium/components/mus/ws/server_window_drawn_tracker_observer.h b/chromium/components/mus/ws/server_window_drawn_tracker_observer.h new file mode 100644 index 00000000000..55f44cbbbc5 --- /dev/null +++ b/chromium/components/mus/ws/server_window_drawn_tracker_observer.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 COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_OBSERVER_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_OBSERVER_H_ + +namespace mus { + +namespace ws { + +class ServerWindow; + +class ServerWindowDrawnTrackerObserver { + public: + // Invoked right before the drawn state changes. If |is_drawn| is false, + // |ancestor| identifies where the change will occur. In the case of a remove, + // |ancestor| is the parent of the window that will be removed (causing the + // drawn state to change). In the case of visibility change, |ancestor| is the + // parent of the window whose visibility will change. + virtual void OnDrawnStateWillChange(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) {} + + // Invoked when the drawn state changes. If |is_drawn| is false |ancestor| + // identifies where the change occurred. In the case of a remove |ancestor| is + // the parent of the window that was removed. In the case of a visibility + // change |ancestor| is the parent of the window whose visibility changed. + virtual void OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) {} + + protected: + virtual ~ServerWindowDrawnTrackerObserver() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_DRAWN_TRACKER_OBSERVER_H_ diff --git a/chromium/components/mus/ws/server_window_drawn_tracker_unittest.cc b/chromium/components/mus/ws/server_window_drawn_tracker_unittest.cc new file mode 100644 index 00000000000..53ffa39f7b1 --- /dev/null +++ b/chromium/components/mus/ws/server_window_drawn_tracker_unittest.cc @@ -0,0 +1,235 @@ +// 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 "components/mus/ws/server_window_drawn_tracker.h" + +#include <stddef.h> + +#include "base/macros.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_drawn_tracker_observer.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mus { + +namespace ws { +namespace { + +class TestServerWindowDrawnTrackerObserver + : public ServerWindowDrawnTrackerObserver { + public: + TestServerWindowDrawnTrackerObserver() + : change_count_(0u), + ancestor_(nullptr), + window_(nullptr), + is_drawn_(false) {} + + void clear_change_count() { change_count_ = 0u; } + size_t change_count() const { return change_count_; } + const ServerWindow* ancestor() const { return ancestor_; } + const ServerWindow* window() const { return window_; } + bool is_drawn() const { return is_drawn_; } + + private: + // ServerWindowDrawnTrackerObserver: + void OnDrawnStateWillChange(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) override { + change_count_++; + ancestor_ = ancestor; + window_ = window; + is_drawn_ = is_drawn; + } + + void OnDrawnStateChanged(ServerWindow* ancestor, + ServerWindow* window, + bool is_drawn) override { + EXPECT_EQ(ancestor_, ancestor); + EXPECT_EQ(window_, window); + EXPECT_EQ(is_drawn_, is_drawn); + } + + size_t change_count_; + const ServerWindow* ancestor_; + const ServerWindow* window_; + bool is_drawn_; + + DISALLOW_COPY_AND_ASSIGN(TestServerWindowDrawnTrackerObserver); +}; + +} // namespace + +TEST(ServerWindowDrawnTrackerTest, ChangeBecauseOfDeletionAndVisibility) { + TestServerWindowDelegate server_window_delegate; + std::unique_ptr<ServerWindow> window( + new ServerWindow(&server_window_delegate, WindowId())); + server_window_delegate.set_root_window(window.get()); + TestServerWindowDrawnTrackerObserver drawn_observer; + ServerWindowDrawnTracker tracker(window.get(), &drawn_observer); + window->SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(window.get(), drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + window->SetVisible(false); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(window.get(), drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + window->SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(window.get(), drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + ServerWindow* old_window = window.get(); + window.reset(); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(old_window, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); +} + +TEST(ServerWindowDrawnTrackerTest, ChangeBecauseOfRemovingFromRoot) { + TestServerWindowDelegate server_window_delegate; + ServerWindow root(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&root); + root.SetVisible(true); + ServerWindow child(&server_window_delegate, WindowId()); + child.SetVisible(true); + root.Add(&child); + + TestServerWindowDrawnTrackerObserver drawn_observer; + ServerWindowDrawnTracker tracker(&child, &drawn_observer); + root.Remove(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child, drawn_observer.window()); + EXPECT_EQ(&root, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + root.Add(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); +} + +TEST(ServerWindowDrawnTrackerTest, ChangeBecauseOfRemovingAncestorFromRoot) { + TestServerWindowDelegate server_window_delegate; + ServerWindow root(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&root); + root.SetVisible(true); + ServerWindow child(&server_window_delegate, WindowId()); + child.SetVisible(true); + root.Add(&child); + + ServerWindow child_child(&server_window_delegate, WindowId()); + child_child.SetVisible(true); + child.Add(&child_child); + + TestServerWindowDrawnTrackerObserver drawn_observer; + ServerWindowDrawnTracker tracker(&child_child, &drawn_observer); + root.Remove(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child_child, drawn_observer.window()); + EXPECT_EQ(&root, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + root.Add(&child_child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child_child, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); +} + +TEST(ServerWindowDrawnTrackerTest, VisibilityChangeFromNonParentAncestor) { + TestServerWindowDelegate server_window_delegate; + ServerWindow root(&server_window_delegate, WindowId()); + ServerWindow child1(&server_window_delegate, WindowId()); + ServerWindow child2(&server_window_delegate, WindowId()); + ServerWindow child3(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&root); + + root.Add(&child1); + child1.Add(&child2); + child2.Add(&child3); + + root.SetVisible(true); + child1.SetVisible(false); + child2.SetVisible(false); + child3.SetVisible(true); + + TestServerWindowDrawnTrackerObserver drawn_observer; + ServerWindowDrawnTracker tracker(&child3, &drawn_observer); + + EXPECT_FALSE(child3.IsDrawn()); + + // Make |child1| visible. |child3| should still be not drawn, since |child2| + // is still invisible. + child1.SetVisible(true); + EXPECT_EQ(0u, drawn_observer.change_count()); + EXPECT_EQ(nullptr, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + EXPECT_FALSE(child3.IsDrawn()); + + child2.SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child3, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + EXPECT_TRUE(child3.IsDrawn()); +} + +TEST(ServerWindowDrawnTrackerTest, TreeHierarchyChangeFromNonParentAncestor) { + TestServerWindowDelegate server_window_delegate; + ServerWindow root(&server_window_delegate, WindowId()); + ServerWindow child1(&server_window_delegate, WindowId()); + ServerWindow child2(&server_window_delegate, WindowId()); + ServerWindow child11(&server_window_delegate, WindowId()); + ServerWindow child111(&server_window_delegate, WindowId()); + server_window_delegate.set_root_window(&root); + + root.Add(&child1); + root.Add(&child2); + child1.Add(&child11); + child11.Add(&child111); + + root.SetVisible(true); + child1.SetVisible(false); + child2.SetVisible(true); + child11.SetVisible(false); + child111.SetVisible(true); + + TestServerWindowDrawnTrackerObserver drawn_observer; + ServerWindowDrawnTracker tracker(&child111, &drawn_observer); + EXPECT_FALSE(child111.IsDrawn()); + + // Move |child11| as a child of |child2|. |child111| should remain not drawn. + child2.Add(&child11); + EXPECT_EQ(0u, drawn_observer.change_count()); + EXPECT_EQ(nullptr, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + EXPECT_FALSE(child111.IsDrawn()); + + child11.SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child111, drawn_observer.window()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + EXPECT_TRUE(child111.IsDrawn()); +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/server_window_observer.h b/chromium/components/mus/ws/server_window_observer.h new file mode 100644 index 00000000000..2ea2f4c310a --- /dev/null +++ b/chromium/components/mus/ws/server_window_observer.h @@ -0,0 +1,97 @@ +// 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 COMPONENTS_MUS_WS_SERVER_WINDOW_OBSERVER_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_OBSERVER_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "components/mus/public/interfaces/mus_constants.mojom.h" + +namespace gfx { +class Insets; +class Rect; +} + +namespace ui { +struct TextInputState; +} + +namespace mus { + +namespace ws { + +class ServerWindow; + +// TODO(sky): rename to OnDid and OnWill everywhere. +class ServerWindowObserver { + public: + // Invoked when a window is about to be destroyed; before any of the children + // have been removed and before the window has been removed from its parent. + virtual void OnWindowDestroying(ServerWindow* window) {} + + // Invoked at the end of the window's destructor (after it has been removed + // from the hierarchy. + virtual void OnWindowDestroyed(ServerWindow* window) {} + + virtual void OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) {} + + virtual void OnWindowHierarchyChanged(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) {} + + virtual void OnWindowBoundsChanged(ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) {} + + virtual void OnWindowClientAreaChanged( + ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas) {} + + virtual void OnWindowReordered(ServerWindow* window, + ServerWindow* relative, + mojom::OrderDirection direction) {} + + virtual void OnWillChangeWindowVisibility(ServerWindow* window) {} + virtual void OnWindowVisibilityChanged(ServerWindow* window) {} + virtual void OnWindowOpacityChanged(ServerWindow* window, + float old_opacity, + float new_opacity) {} + + virtual void OnWindowPredefinedCursorChanged(ServerWindow* window, + int32_t cursor_id) {} + virtual void OnWindowNonClientCursorChanged(ServerWindow* window, + int32_t cursor_id) {} + + virtual void OnWindowTextInputStateChanged(ServerWindow* window, + const ui::TextInputState& state) {} + + virtual void OnWindowSharedPropertyChanged( + ServerWindow* window, + const std::string& name, + const std::vector<uint8_t>* new_data) {} + + // Called when a transient child is added to |window|. + virtual void OnTransientWindowAdded(ServerWindow* window, + ServerWindow* transient_child) {} + + // Called when a transient child is removed from |window|. + virtual void OnTransientWindowRemoved(ServerWindow* window, + ServerWindow* transient_child) {} + + protected: + virtual ~ServerWindowObserver() {} +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_OBSERVER_H_ diff --git a/chromium/components/mus/ws/server_window_surface.cc b/chromium/components/mus/ws/server_window_surface.cc new file mode 100644 index 00000000000..d03bc5bd789 --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface.cc @@ -0,0 +1,110 @@ +// 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 "components/mus/ws/server_window_surface.h" + +#include "base/callback.h" +#include "cc/output/compositor_frame.h" +#include "cc/quads/shared_quad_state.h" +#include "cc/quads/surface_draw_quad.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/server_window_surface_manager.h" + +namespace mus { +namespace ws { +namespace { + +void CallCallback(const base::Closure& callback, cc::SurfaceDrawStatus status) { + callback.Run(); +} + +} // namespace + +ServerWindowSurface::ServerWindowSurface( + ServerWindowSurfaceManager* manager, + mojo::InterfaceRequest<Surface> request, + mojom::SurfaceClientPtr client) + : manager_(manager), + surface_id_(manager->GenerateId()), + surface_factory_(manager_->GetSurfaceManager(), this), + client_(std::move(client)), + binding_(this, std::move(request)), + registered_surface_factory_client_(false) { + surface_factory_.Create(surface_id_); +} + +ServerWindowSurface::~ServerWindowSurface() { + // SurfaceFactory's destructor will attempt to return resources which will + // call back into here and access |client_| so we should destroy + // |surface_factory_|'s resources early on. + surface_factory_.DestroyAll(); + + if (registered_surface_factory_client_) { + cc::SurfaceManager* surface_manager = manager_->GetSurfaceManager(); + surface_manager->UnregisterSurfaceFactoryClient(manager_->id_namespace()); + } +} + +void ServerWindowSurface::SubmitCompositorFrame( + cc::CompositorFrame frame, + const SubmitCompositorFrameCallback& callback) { + gfx::Size frame_size = + frame.delegated_frame_data->render_pass_list[0]->output_rect.size(); + if (!surface_id_.is_null()) { + // If the size of the CompostiorFrame has changed then destroy the existing + // Surface and create a new one of the appropriate size. + if (frame_size != last_submitted_frame_size_) { + // Rendering of the topmost frame happens in two phases. First the frame + // is generated and submitted, and a later date it is actually drawn. + // During the time the frame is generated and drawn we can't destroy the + // surface, otherwise when drawn you get an empty surface. To deal with + // this we schedule destruction via the delegate. The delegate will call + // us back when we're not waiting on a frame to be drawn (which may be + // synchronously). + surfaces_scheduled_for_destruction_.insert(surface_id_); + window()->delegate()->ScheduleSurfaceDestruction(window()); + surface_id_ = manager_->GenerateId(); + surface_factory_.Create(surface_id_); + } + } + surface_factory_.SubmitCompositorFrame(surface_id_, std::move(frame), + base::Bind(&CallCallback, callback)); + last_submitted_frame_size_ = frame_size; + window()->delegate()->OnScheduleWindowPaint(window()); +} + +void ServerWindowSurface::DestroySurfacesScheduledForDestruction() { + std::set<cc::SurfaceId> surfaces; + surfaces.swap(surfaces_scheduled_for_destruction_); + for (auto& id : surfaces) + surface_factory_.Destroy(id); +} + +void ServerWindowSurface::RegisterForBeginFrames() { + DCHECK(!registered_surface_factory_client_); + registered_surface_factory_client_ = true; + cc::SurfaceManager* surface_manager = manager_->GetSurfaceManager(); + surface_manager->RegisterSurfaceFactoryClient(manager_->id_namespace(), this); +} + +ServerWindow* ServerWindowSurface::window() { + return manager_->window(); +} + +void ServerWindowSurface::ReturnResources( + const cc::ReturnedResourceArray& resources) { + if (!client_ || !base::MessageLoop::current()) + return; + client_->ReturnResources(mojo::Array<cc::ReturnedResource>::From(resources)); +} + +void ServerWindowSurface::SetBeginFrameSource( + cc::BeginFrameSource* begin_frame_source) { + // TODO(tansell): Implement this. +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/server_window_surface.h b/chromium/components/mus/ws/server_window_surface.h new file mode 100644 index 00000000000..126a99f73fc --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface.h @@ -0,0 +1,86 @@ +// 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 COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_H_ + +#include <set> + +#include "base/macros.h" +#include "cc/ipc/compositor_frame.mojom.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_factory_client.h" +#include "cc/surfaces/surface_id.h" +#include "cc/surfaces/surface_id_allocator.h" +#include "components/mus/public/interfaces/surface.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/ids.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { + +class SurfacesState; + +namespace ws { + +class ServerWindow; +class ServerWindowSurfaceManager; + +// Server side representation of a WindowSurface. +class ServerWindowSurface : public mojom::Surface, + public cc::SurfaceFactoryClient { + public: + ServerWindowSurface(ServerWindowSurfaceManager* manager, + mojo::InterfaceRequest<mojom::Surface> request, + mojom::SurfaceClientPtr client); + + ~ServerWindowSurface() override; + + const gfx::Size& last_submitted_frame_size() const { + return last_submitted_frame_size_; + } + + // mojom::Surface: + void SubmitCompositorFrame( + cc::CompositorFrame frame, + const SubmitCompositorFrameCallback& callback) override; + + const cc::SurfaceId& id() const { return surface_id_; } + + // Destroys old surfaces that have been outdated by a new surface. + void DestroySurfacesScheduledForDestruction(); + + // Registers this with the SurfaceManager + void RegisterForBeginFrames(); + + private: + ServerWindow* window(); + + // SurfaceFactoryClient implementation. + void ReturnResources(const cc::ReturnedResourceArray& resources) override; + void SetBeginFrameSource(cc::BeginFrameSource* begin_frame_source) override; + + ServerWindowSurfaceManager* manager_; // Owns this. + + gfx::Size last_submitted_frame_size_; + + cc::SurfaceId surface_id_; + cc::SurfaceFactory surface_factory_; + + mojom::SurfaceClientPtr client_; + mojo::Binding<Surface> binding_; + + // Set of surface ids that need to be destroyed. + std::set<cc::SurfaceId> surfaces_scheduled_for_destruction_; + + bool registered_surface_factory_client_; + + DISALLOW_COPY_AND_ASSIGN(ServerWindowSurface); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_H_ diff --git a/chromium/components/mus/ws/server_window_surface_manager.cc b/chromium/components/mus/ws/server_window_surface_manager.cc new file mode 100644 index 00000000000..1109d6ee8ac --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface_manager.cc @@ -0,0 +1,102 @@ +// 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 "components/mus/ws/server_window_surface_manager.h" + +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/server_window_surface.h" + +namespace mus { +namespace ws { + +ServerWindowSurfaceManager::ServerWindowSurfaceManager(ServerWindow* window) + : window_(window), + surface_id_allocator_( + window->delegate()->GetSurfacesState()->next_id_namespace()), + waiting_for_initial_frames_( + window_->properties().count(mus::mojom::kWaitForUnderlay_Property) > + 0) { + surface_id_allocator_.RegisterSurfaceIdNamespace(GetSurfaceManager()); +} + +ServerWindowSurfaceManager::~ServerWindowSurfaceManager() { + // Explicitly clear the type to surface manager so that this manager + // is still valid prior during ~ServerWindowSurface. + type_to_surface_map_.clear(); +} + +bool ServerWindowSurfaceManager::ShouldDraw() { + if (!waiting_for_initial_frames_) + return true; + + waiting_for_initial_frames_ = + !IsSurfaceReadyAndNonEmpty(mojom::SurfaceType::DEFAULT) || + !IsSurfaceReadyAndNonEmpty(mojom::SurfaceType::UNDERLAY); + return !waiting_for_initial_frames_; +} + +void ServerWindowSurfaceManager::CreateSurface( + mojom::SurfaceType surface_type, + mojo::InterfaceRequest<mojom::Surface> request, + mojom::SurfaceClientPtr client) { + std::unique_ptr<ServerWindowSurface> surface( + new ServerWindowSurface(this, std::move(request), std::move(client))); + if (!HasAnySurface()) { + // Only one SurfaceFactoryClient can be registered per surface id namespace, + // so register the first one. Since all surfaces created by this manager + // represent the same window, the begin frame source can be shared by + // all surfaces created here. + surface->RegisterForBeginFrames(); + } + type_to_surface_map_[surface_type] = std::move(surface); +} + +ServerWindowSurface* ServerWindowSurfaceManager::GetDefaultSurface() const { + return GetSurfaceByType(mojom::SurfaceType::DEFAULT); +} + +ServerWindowSurface* ServerWindowSurfaceManager::GetUnderlaySurface() const { + return GetSurfaceByType(mojom::SurfaceType::UNDERLAY); +} + +ServerWindowSurface* ServerWindowSurfaceManager::GetSurfaceByType( + mojom::SurfaceType type) const { + auto iter = type_to_surface_map_.find(type); + return iter == type_to_surface_map_.end() ? nullptr : iter->second.get(); +} + +bool ServerWindowSurfaceManager::HasSurfaceOfType( + mojom::SurfaceType type) const { + return type_to_surface_map_.count(type) > 0; +} + +bool ServerWindowSurfaceManager::HasAnySurface() const { + return GetDefaultSurface() || GetUnderlaySurface(); +} + +cc::SurfaceManager* ServerWindowSurfaceManager::GetSurfaceManager() { + return window()->delegate()->GetSurfacesState()->manager(); +} + +bool ServerWindowSurfaceManager::IsSurfaceReadyAndNonEmpty( + mojom::SurfaceType type) const { + auto iter = type_to_surface_map_.find(type); + if (iter == type_to_surface_map_.end()) + return false; + if (iter->second->last_submitted_frame_size().IsEmpty()) + return false; + const gfx::Size& last_submitted_frame_size = + iter->second->last_submitted_frame_size(); + return last_submitted_frame_size.width() >= window_->bounds().width() && + last_submitted_frame_size.height() >= window_->bounds().height(); +} + +cc::SurfaceId ServerWindowSurfaceManager::GenerateId() { + return surface_id_allocator_.GenerateId(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/server_window_surface_manager.h b/chromium/components/mus/ws/server_window_surface_manager.h new file mode 100644 index 00000000000..1bae2008ca8 --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface_manager.h @@ -0,0 +1,84 @@ +// 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 COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_H_ + +#include <map> + +#include "base/macros.h" +#include "cc/ipc/compositor_frame.mojom.h" +#include "cc/surfaces/surface_factory.h" +#include "cc/surfaces/surface_id.h" +#include "cc/surfaces/surface_id_allocator.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace ws { + +class ServerWindow; +class ServerWindowSurface; +class ServerWindowSurfaceManagerTestApi; + +// ServerWindowSurfaceManager tracks the surfaces associated with a +// ServerWindow. +class ServerWindowSurfaceManager { + public: + explicit ServerWindowSurfaceManager(ServerWindow* window); + ~ServerWindowSurfaceManager(); + + // Returns true if the surfaces from this manager should be drawn. + bool ShouldDraw(); + + // Creates a new surface of the specified type, replacing the existing one of + // the specified type. + void CreateSurface(mojom::SurfaceType surface_type, + mojo::InterfaceRequest<mojom::Surface> request, + mojom::SurfaceClientPtr client); + + ServerWindow* window() { return window_; } + + ServerWindowSurface* GetDefaultSurface() const; + ServerWindowSurface* GetUnderlaySurface() const; + ServerWindowSurface* GetSurfaceByType(mojom::SurfaceType type) const; + bool HasSurfaceOfType(mojom::SurfaceType type) const; + bool HasAnySurface() const; + + uint32_t id_namespace() const { return surface_id_allocator_.id_namespace(); } + cc::SurfaceManager* GetSurfaceManager(); + + private: + friend class ServerWindowSurfaceManagerTestApi; + friend class ServerWindowSurface; + + // Returns true if a surface of |type| has been set and its size is greater + // than the size of the window. + bool IsSurfaceReadyAndNonEmpty(mojom::SurfaceType type) const; + + cc::SurfaceId GenerateId(); + + ServerWindow* window_; + + cc::SurfaceIdAllocator surface_id_allocator_; + + using TypeToSurfaceMap = + std::map<mojom::SurfaceType, std::unique_ptr<ServerWindowSurface>>; + + TypeToSurfaceMap type_to_surface_map_; + + // While true the window is not drawn. This is initially true if the window + // has the property |kWaitForUnderlay_Property|. This is set to false once + // the underlay and default surface have been set *and* their size is at + // least that of the window. Ideally we would wait for sizes to match, but + // the underlay is not necessarily as big as the window. + bool waiting_for_initial_frames_; + + DISALLOW_COPY_AND_ASSIGN(ServerWindowSurfaceManager); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_H_ diff --git a/chromium/components/mus/ws/server_window_surface_manager_test_api.cc b/chromium/components/mus/ws/server_window_surface_manager_test_api.cc new file mode 100644 index 00000000000..d65ae0f6376 --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface_manager_test_api.cc @@ -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. + +#include "components/mus/ws/server_window_surface_manager_test_api.h" + +#include "components/mus/ws/server_window.h" + +namespace mus { +namespace ws { + +ServerWindowSurfaceManagerTestApi::ServerWindowSurfaceManagerTestApi( + ServerWindowSurfaceManager* manager) + : manager_(manager) {} + +ServerWindowSurfaceManagerTestApi::~ServerWindowSurfaceManagerTestApi() {} + +void ServerWindowSurfaceManagerTestApi::CreateEmptyDefaultSurface() { + manager_->type_to_surface_map_[mojom::SurfaceType::DEFAULT] = nullptr; +} + +void ServerWindowSurfaceManagerTestApi::DestroyDefaultSurface() { + manager_->type_to_surface_map_.erase(mojom::SurfaceType::DEFAULT); +} + +void EnableHitTest(ServerWindow* window) { + ServerWindowSurfaceManagerTestApi test_api( + window->GetOrCreateSurfaceManager()); + test_api.CreateEmptyDefaultSurface(); +} + +void DisableHitTest(ServerWindow* window) { + ServerWindowSurfaceManagerTestApi test_api( + window->GetOrCreateSurfaceManager()); + test_api.DestroyDefaultSurface(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/server_window_surface_manager_test_api.h b/chromium/components/mus/ws/server_window_surface_manager_test_api.h new file mode 100644 index 00000000000..bc34027110a --- /dev/null +++ b/chromium/components/mus/ws/server_window_surface_manager_test_api.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 COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_TEST_API_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_TEST_API_H_ + +#include "base/macros.h" +#include "components/mus/ws/server_window_surface_manager.h" + +namespace mus { +namespace ws { + +class ServerWindow; + +// Use to poke at the internals of ServerWindowSurfaceManager. +class ServerWindowSurfaceManagerTestApi { + public: + explicit ServerWindowSurfaceManagerTestApi( + ServerWindowSurfaceManager* manager); + ~ServerWindowSurfaceManagerTestApi(); + + void CreateEmptyDefaultSurface(); + void DestroyDefaultSurface(); + + private: + ServerWindowSurfaceManager* manager_; + + DISALLOW_COPY_AND_ASSIGN(ServerWindowSurfaceManagerTestApi); +}; + +// Use to make |window| a target for events. +void EnableHitTest(ServerWindow* window); +void DisableHitTest(ServerWindow* window); + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_SURFACE_MANAGER_TEST_API_H_ diff --git a/chromium/components/mus/ws/server_window_tracker.h b/chromium/components/mus/ws/server_window_tracker.h new file mode 100644 index 00000000000..d3b73f06be1 --- /dev/null +++ b/chromium/components/mus/ws/server_window_tracker.h @@ -0,0 +1,25 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_SERVER_WINDOW_TRACKER_H_ +#define COMPONENTS_MUS_WS_SERVER_WINDOW_TRACKER_H_ + +#include <stdint.h> +#include <set> + +#include "base/macros.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_observer.h" +#include "ui/base/window_tracker_template.h" + +namespace mus { +namespace ws { + +using ServerWindowTracker = + ui::WindowTrackerTemplate<ServerWindow, ServerWindowObserver>; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_SERVER_WINDOW_TRACKER_H_ diff --git a/chromium/components/mus/ws/test_change_tracker.cc b/chromium/components/mus/ws/test_change_tracker.cc new file mode 100644 index 00000000000..26cd89ed64a --- /dev/null +++ b/chromium/components/mus/ws/test_change_tracker.cc @@ -0,0 +1,448 @@ +// 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 "components/mus/ws/test_change_tracker.h" + +#include <stddef.h> + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "components/mus/common/util.h" +#include "mojo/common/common_type_converters.h" + +using mojo::Array; +using mojo::String; + +namespace mus { + +namespace ws { + +std::string WindowIdToString(Id id) { + return (id == 0) ? "null" + : base::StringPrintf("%d,%d", HiWord(id), LoWord(id)); +} + +namespace { + +std::string DirectionToString(mojom::OrderDirection direction) { + return direction == mojom::OrderDirection::ABOVE ? "above" : "below"; +} + +enum class ChangeDescriptionType { + ONE, + TWO, +}; + +std::string ChangeToDescription(const Change& change, + ChangeDescriptionType type) { + switch (change.type) { + case CHANGE_TYPE_EMBED: + if (type == ChangeDescriptionType::ONE) + return "OnEmbed"; + return base::StringPrintf("OnEmbed drawn=%s", + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED: + return base::StringPrintf("OnEmbeddedAppDisconnected window=%s", + WindowIdToString(change.window_id).c_str()); + + case CHANGE_TYPE_UNEMBED: + return base::StringPrintf("OnUnembed window=%s", + WindowIdToString(change.window_id).c_str()); + + case CHANGE_TYPE_LOST_CAPTURE: + return base::StringPrintf("OnLostCapture window=%s", + WindowIdToString(change.window_id).c_str()); + + case CHANGE_TYPE_NODE_ADD_TRANSIENT_WINDOW: + return base::StringPrintf("AddTransientWindow parent = %s child = %s", + WindowIdToString(change.window_id).c_str(), + WindowIdToString(change.window_id2).c_str()); + + case CHANGE_TYPE_NODE_BOUNDS_CHANGED: + return base::StringPrintf( + "BoundsChanged window=%s old_bounds=%s new_bounds=%s", + WindowIdToString(change.window_id).c_str(), + change.bounds.ToString().c_str(), change.bounds2.ToString().c_str()); + + case CHANGE_TYPE_NODE_HIERARCHY_CHANGED: + return base::StringPrintf( + "HierarchyChanged window=%s old_parent=%s new_parent=%s", + WindowIdToString(change.window_id).c_str(), + WindowIdToString(change.window_id2).c_str(), + WindowIdToString(change.window_id3).c_str()); + + case CHANGE_TYPE_NODE_REMOVE_TRANSIENT_WINDOW_FROM_PARENT: + return base::StringPrintf( + "RemoveTransientWindowFromParent parent = %s child = %s", + WindowIdToString(change.window_id).c_str(), + WindowIdToString(change.window_id2).c_str()); + + case CHANGE_TYPE_NODE_REORDERED: + return base::StringPrintf("Reordered window=%s relative=%s direction=%s", + WindowIdToString(change.window_id).c_str(), + WindowIdToString(change.window_id2).c_str(), + DirectionToString(change.direction).c_str()); + + case CHANGE_TYPE_NODE_DELETED: + return base::StringPrintf("WindowDeleted window=%s", + WindowIdToString(change.window_id).c_str()); + + case CHANGE_TYPE_NODE_VISIBILITY_CHANGED: + return base::StringPrintf("VisibilityChanged window=%s visible=%s", + WindowIdToString(change.window_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED: + return base::StringPrintf("DrawnStateChanged window=%s drawn=%s", + WindowIdToString(change.window_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_INPUT_EVENT: { + std::string result = base::StringPrintf( + "InputEvent window=%s event_action=%d", + WindowIdToString(change.window_id).c_str(), change.event_action); + if (change.event_observer_id != 0) + base::StringAppendF(&result, " event_observer_id=%u", + change.event_observer_id); + return result; + } + + case CHANGE_TYPE_EVENT_OBSERVED: + return base::StringPrintf( + "EventObserved event_action=%d event_observer_id=%u", + change.event_action, change.event_observer_id); + + case CHANGE_TYPE_PROPERTY_CHANGED: + return base::StringPrintf("PropertyChanged window=%s key=%s value=%s", + WindowIdToString(change.window_id).c_str(), + change.property_key.c_str(), + change.property_value.c_str()); + + case CHANGE_TYPE_FOCUSED: + return base::StringPrintf("Focused id=%s", + WindowIdToString(change.window_id).c_str()); + + case CHANGE_TYPE_CURSOR_CHANGED: + return base::StringPrintf("CursorChanged id=%s cursor_id=%d", + WindowIdToString(change.window_id).c_str(), + change.cursor_id); + case CHANGE_TYPE_ON_CHANGE_COMPLETED: + return base::StringPrintf("ChangeCompleted id=%d sucess=%s", + change.change_id, + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_ON_TOP_LEVEL_CREATED: + return base::StringPrintf("TopLevelCreated id=%d window_id=%s drawn=%s", + change.change_id, + WindowIdToString(change.window_id).c_str(), + change.bool_value ? "true" : "false"); + case CHANGE_TYPE_OPACITY: + return base::StringPrintf("OpacityChanged window_id=%s opacity=%.2f", + WindowIdToString(change.window_id).c_str(), + change.float_value); + } + return std::string(); +} + +std::string SingleChangeToDescriptionImpl(const std::vector<Change>& changes, + ChangeDescriptionType change_type) { + std::string result; + for (auto& change : changes) { + if (!result.empty()) + result += "\n"; + result += ChangeToDescription(change, change_type); + } + return result; +} + +} // 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] = ChangeToDescription(changes[i], ChangeDescriptionType::ONE); + return strings; +} + +std::string SingleChangeToDescription(const std::vector<Change>& changes) { + return SingleChangeToDescriptionImpl(changes, ChangeDescriptionType::ONE); +} + +std::string SingleChangeToDescription2(const std::vector<Change>& changes) { + return SingleChangeToDescriptionImpl(changes, ChangeDescriptionType::TWO); +} + +std::string SingleWindowDescription(const std::vector<TestWindow>& windows) { + if (windows.empty()) + return "no windows"; + std::string result; + for (const TestWindow& window : windows) + result += window.ToString(); + return result; +} + +std::string ChangeWindowDescription(const std::vector<Change>& changes) { + if (changes.size() != 1) + return std::string(); + std::vector<std::string> window_strings(changes[0].windows.size()); + for (size_t i = 0; i < changes[0].windows.size(); ++i) + window_strings[i] = "[" + changes[0].windows[i].ToString() + "]"; + return base::JoinString(window_strings, ","); +} + +TestWindow WindowDataToTestWindow(const mojom::WindowDataPtr& data) { + TestWindow window; + window.parent_id = data->parent_id; + window.window_id = data->window_id; + window.visible = data->visible; + window.properties = + data->properties.To<std::map<std::string, std::vector<uint8_t>>>(); + return window; +} + +void WindowDatasToTestWindows(const Array<mojom::WindowDataPtr>& data, + std::vector<TestWindow>* test_windows) { + for (size_t i = 0; i < data.size(); ++i) + test_windows->push_back(WindowDataToTestWindow(data[i])); +} + +Change::Change() + : type(CHANGE_TYPE_EMBED), + client_id(0), + window_id(0), + window_id2(0), + window_id3(0), + event_action(0), + event_observer_id(0u), + direction(mojom::OrderDirection::ABOVE), + bool_value(false), + float_value(0.f), + cursor_id(0), + change_id(0u) {} + +Change::Change(const Change& other) = default; + +Change::~Change() {} + +TestChangeTracker::TestChangeTracker() : delegate_(NULL) {} + +TestChangeTracker::~TestChangeTracker() {} + +void TestChangeTracker::OnEmbed(ClientSpecificId client_id, + mojom::WindowDataPtr root, + bool drawn) { + Change change; + change.type = CHANGE_TYPE_EMBED; + change.client_id = client_id; + change.bool_value = drawn; + change.windows.push_back(WindowDataToTestWindow(root)); + AddChange(change); +} + +void TestChangeTracker::OnEmbeddedAppDisconnected(Id window_id) { + Change change; + change.type = CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED; + change.window_id = window_id; + AddChange(change); +} + +void TestChangeTracker::OnWindowBoundsChanged(Id window_id, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + Change change; + change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED; + change.window_id = window_id; + change.bounds = old_bounds; + change.bounds2 = new_bounds; + AddChange(change); +} + +void TestChangeTracker::OnUnembed(Id window_id) { + Change change; + change.type = CHANGE_TYPE_UNEMBED; + change.window_id = window_id; + AddChange(change); +} + +void TestChangeTracker::OnTransientWindowAdded(Id window_id, + Id transient_window_id) { + Change change; + change.type = CHANGE_TYPE_NODE_ADD_TRANSIENT_WINDOW; + change.window_id = window_id; + change.window_id2 = transient_window_id; + AddChange(change); +} + +void TestChangeTracker::OnTransientWindowRemoved(Id window_id, + Id transient_window_id) { + Change change; + change.type = CHANGE_TYPE_NODE_REMOVE_TRANSIENT_WINDOW_FROM_PARENT; + change.window_id = window_id; + change.window_id2 = transient_window_id; + AddChange(change); +} + +void TestChangeTracker::OnLostCapture(Id window_id) { + Change change; + change.type = CHANGE_TYPE_LOST_CAPTURE; + change.window_id = window_id; + AddChange(change); +} + +void TestChangeTracker::OnWindowHierarchyChanged( + Id window_id, + Id old_parent_id, + Id new_parent_id, + Array<mojom::WindowDataPtr> windows) { + Change change; + change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED; + change.window_id = window_id; + change.window_id2 = old_parent_id; + change.window_id3 = new_parent_id; + WindowDatasToTestWindows(windows, &change.windows); + AddChange(change); +} + +void TestChangeTracker::OnWindowReordered(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) { + Change change; + change.type = CHANGE_TYPE_NODE_REORDERED; + change.window_id = window_id; + change.window_id2 = relative_window_id; + change.direction = direction; + AddChange(change); +} + +void TestChangeTracker::OnWindowDeleted(Id window_id) { + Change change; + change.type = CHANGE_TYPE_NODE_DELETED; + change.window_id = window_id; + AddChange(change); +} + +void TestChangeTracker::OnWindowVisibilityChanged(Id window_id, bool visible) { + Change change; + change.type = CHANGE_TYPE_NODE_VISIBILITY_CHANGED; + change.window_id = window_id; + change.bool_value = visible; + AddChange(change); +} + +void TestChangeTracker::OnWindowOpacityChanged(Id window_id, float opacity) { + Change change; + change.type = CHANGE_TYPE_OPACITY; + change.window_id = window_id; + change.float_value = opacity; + AddChange(change); +} + +void TestChangeTracker::OnWindowParentDrawnStateChanged(Id window_id, + bool drawn) { + Change change; + change.type = CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED; + change.window_id = window_id; + change.bool_value = drawn; + AddChange(change); +} + +void TestChangeTracker::OnWindowInputEvent(Id window_id, + const ui::Event& event, + uint32_t event_observer_id) { + Change change; + change.type = CHANGE_TYPE_INPUT_EVENT; + change.window_id = window_id; + change.event_action = static_cast<int32_t>(event.type()); + change.event_observer_id = event_observer_id; + AddChange(change); +} + +void TestChangeTracker::OnEventObserved(const ui::Event& event, + uint32_t event_observer_id) { + Change change; + change.type = CHANGE_TYPE_EVENT_OBSERVED; + change.event_action = static_cast<int32_t>(event.type()); + change.event_observer_id = event_observer_id; + AddChange(change); +} + +void TestChangeTracker::OnWindowSharedPropertyChanged(Id window_id, + String name, + Array<uint8_t> data) { + Change change; + change.type = CHANGE_TYPE_PROPERTY_CHANGED; + change.window_id = window_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::OnWindowFocused(Id window_id) { + Change change; + change.type = CHANGE_TYPE_FOCUSED; + change.window_id = window_id; + AddChange(change); +} + +void TestChangeTracker::OnWindowPredefinedCursorChanged( + Id window_id, + mojom::Cursor cursor_id) { + Change change; + change.type = CHANGE_TYPE_CURSOR_CHANGED; + change.window_id = window_id; + change.cursor_id = static_cast<int32_t>(cursor_id); + AddChange(change); +} + +void TestChangeTracker::OnChangeCompleted(uint32_t change_id, bool success) { + Change change; + change.type = CHANGE_TYPE_ON_CHANGE_COMPLETED; + change.change_id = change_id; + change.bool_value = success; + AddChange(change); +} + +void TestChangeTracker::OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr window_data, + bool drawn) { + Change change; + change.type = CHANGE_TYPE_ON_TOP_LEVEL_CREATED; + change.change_id = change_id; + change.window_id = window_data->window_id; + change.bool_value = drawn; + AddChange(change); +} + +void TestChangeTracker::AddChange(const Change& change) { + changes_.push_back(change); + if (delegate_) + delegate_->OnChangeAdded(); +} + +TestWindow::TestWindow() {} + +TestWindow::TestWindow(const TestWindow& other) = default; + +TestWindow::~TestWindow() {} + +std::string TestWindow::ToString() const { + return base::StringPrintf("window=%s parent=%s", + WindowIdToString(window_id).c_str(), + WindowIdToString(parent_id).c_str()); +} + +std::string TestWindow::ToString2() const { + return base::StringPrintf( + "window=%s parent=%s visible=%s", WindowIdToString(window_id).c_str(), + WindowIdToString(parent_id).c_str(), visible ? "true" : "false"); +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/test_change_tracker.h b/chromium/components/mus/ws/test_change_tracker.h new file mode 100644 index 00000000000..7d82b2f339b --- /dev/null +++ b/chromium/components/mus/ws/test_change_tracker.h @@ -0,0 +1,185 @@ +// 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 COMPONENTS_MUS_WS_TEST_CHANGE_TRACKER_H_ +#define COMPONENTS_MUS_WS_TEST_CHANGE_TRACKER_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "components/mus/common/types.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/array.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" + +namespace mus { + +namespace ws { + +enum ChangeType { + CHANGE_TYPE_EMBED, + CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED, + CHANGE_TYPE_UNEMBED, + CHANGE_TYPE_LOST_CAPTURE, + // TODO(sky): nuke NODE. + CHANGE_TYPE_NODE_ADD_TRANSIENT_WINDOW, + CHANGE_TYPE_NODE_BOUNDS_CHANGED, + CHANGE_TYPE_NODE_HIERARCHY_CHANGED, + CHANGE_TYPE_NODE_REMOVE_TRANSIENT_WINDOW_FROM_PARENT, + 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_EVENT_OBSERVED, + CHANGE_TYPE_PROPERTY_CHANGED, + CHANGE_TYPE_FOCUSED, + CHANGE_TYPE_CURSOR_CHANGED, + CHANGE_TYPE_ON_CHANGE_COMPLETED, + CHANGE_TYPE_ON_TOP_LEVEL_CREATED, + CHANGE_TYPE_OPACITY, +}; + +// TODO(sky): consider nuking and converting directly to WindowData. +struct TestWindow { + TestWindow(); + TestWindow(const TestWindow& other); + ~TestWindow(); + + // Returns a string description of this. + std::string ToString() const; + + // Returns a string description that includes visible and drawn. + std::string ToString2() const; + + Id parent_id; + Id window_id; + bool visible; + std::map<std::string, std::vector<uint8_t>> properties; +}; + +// Tracks a call to WindowTreeClient. See the individual functions for the +// fields that are used. +struct Change { + Change(); + Change(const Change& other); + ~Change(); + + ChangeType type; + ClientSpecificId client_id; + std::vector<TestWindow> windows; + Id window_id; + Id window_id2; + Id window_id3; + gfx::Rect bounds; + gfx::Rect bounds2; + int32_t event_action; + uint32_t event_observer_id; + mojo::String embed_url; + mojom::OrderDirection direction; + bool bool_value; + float float_value; + std::string property_key; + std::string property_value; + int32_t cursor_id; + uint32_t change_id; +}; + +// 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); +std::string SingleChangeToDescription2(const std::vector<Change>& changes); + +// Convenience for returning the description of the first item in |windows|. +// Returns an empty string if |windows| has something other than one entry. +std::string SingleWindowDescription(const std::vector<TestWindow>& windows); + +// Returns a string description of |changes[0].windows|. Returns an empty string +// if change.size() != 1. +std::string ChangeWindowDescription(const std::vector<Change>& changes); + +// Converts WindowDatas to TestWindows. +void WindowDatasToTestWindows(const mojo::Array<mojom::WindowDataPtr>& data, + std::vector<TestWindow>* test_windows); + +// TestChangeTracker is used to record WindowTreeClient 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 WindowTreeClient 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 + // WindowTreeClient function. + void OnEmbed(ClientSpecificId client_id, + mojom::WindowDataPtr root, + bool drawn); + void OnEmbeddedAppDisconnected(Id window_id); + void OnUnembed(Id window_id); + void OnLostCapture(Id window_id); + void OnTransientWindowAdded(Id window_id, Id transient_window_id); + void OnTransientWindowRemoved(Id window_id, Id transient_window_id); + void OnWindowBoundsChanged(Id window_id, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds); + void OnWindowHierarchyChanged(Id window_id, + Id old_parent_id, + Id new_parent_id, + mojo::Array<mojom::WindowDataPtr> windows); + void OnWindowReordered(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction); + void OnWindowDeleted(Id window_id); + void OnWindowVisibilityChanged(Id window_id, bool visible); + void OnWindowOpacityChanged(Id window_id, float opacity); + void OnWindowParentDrawnStateChanged(Id window_id, bool drawn); + void OnWindowInputEvent(Id window_id, + const ui::Event& event, + uint32_t event_observer_id); + void OnEventObserved(const ui::Event& event, uint32_t event_observer_id); + void OnWindowSharedPropertyChanged(Id window_id, + mojo::String name, + mojo::Array<uint8_t> data); + void OnWindowFocused(Id window_id); + void OnWindowPredefinedCursorChanged(Id window_id, mojom::Cursor cursor_id); + void OnChangeCompleted(uint32_t change_id, bool success); + void OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr window_data, + bool drawn); + + private: + void AddChange(const Change& change); + + Delegate* delegate_; + std::vector<Change> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestChangeTracker); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_TEST_CHANGE_TRACKER_H_ diff --git a/chromium/components/mus/ws/test_server_window_delegate.cc b/chromium/components/mus/ws/test_server_window_delegate.cc new file mode 100644 index 00000000000..57f1aaec6ed --- /dev/null +++ b/chromium/components/mus/ws/test_server_window_delegate.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 "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "components/mus/ws/server_window.h" + +namespace mus { + +namespace ws { + +TestServerWindowDelegate::TestServerWindowDelegate() + : root_window_(nullptr), surfaces_state_(new SurfacesState()) {} + +TestServerWindowDelegate::~TestServerWindowDelegate() {} + +mus::SurfacesState* TestServerWindowDelegate::GetSurfacesState() { + return surfaces_state_.get(); +} + +void TestServerWindowDelegate::OnScheduleWindowPaint(ServerWindow* window) {} + +const ServerWindow* TestServerWindowDelegate::GetRootWindow( + const ServerWindow* window) const { + return root_window_; +} + +void TestServerWindowDelegate::ScheduleSurfaceDestruction( + ServerWindow* window) {} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/test_server_window_delegate.h b/chromium/components/mus/ws/test_server_window_delegate.h new file mode 100644 index 00000000000..77808771d9d --- /dev/null +++ b/chromium/components/mus/ws/test_server_window_delegate.h @@ -0,0 +1,41 @@ +// 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 COMPONENTS_MUS_WS_TEST_SERVER_WINDOW_DELEGATE_H_ +#define COMPONENTS_MUS_WS_TEST_SERVER_WINDOW_DELEGATE_H_ + +#include "base/macros.h" +#include "components/mus/ws/server_window_delegate.h" + +namespace mus { + +namespace ws { + +struct WindowId; + +class TestServerWindowDelegate : public ServerWindowDelegate { + public: + TestServerWindowDelegate(); + ~TestServerWindowDelegate() override; + + void set_root_window(const ServerWindow* window) { root_window_ = window; } + + private: + // ServerWindowDelegate: + mus::SurfacesState* GetSurfacesState() override; + void OnScheduleWindowPaint(ServerWindow* window) override; + const ServerWindow* GetRootWindow(const ServerWindow* window) const override; + void ScheduleSurfaceDestruction(ServerWindow* window) override; + + const ServerWindow* root_window_; + scoped_refptr<mus::SurfacesState> surfaces_state_; + + DISALLOW_COPY_AND_ASSIGN(TestServerWindowDelegate); +}; + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_TEST_SERVER_WINDOW_DELEGATE_H_ diff --git a/chromium/components/mus/ws/test_utils.cc b/chromium/components/mus/ws/test_utils.cc new file mode 100644 index 00000000000..78f7d4714d3 --- /dev/null +++ b/chromium/components/mus/ws/test_utils.cc @@ -0,0 +1,496 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/test_utils.h" + +#include "base/memory/ptr_util.h" +#include "cc/output/copy_output_request.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/window_manager_access_policy.h" +#include "components/mus/ws/window_manager_window_tree_factory.h" +#include "services/shell/public/interfaces/connector.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mus { +namespace ws { +namespace test { +namespace { + +// ----------------------------------------------------------------------------- +// Empty implementation of PlatformDisplay. +class TestPlatformDisplay : public PlatformDisplay { + public: + explicit TestPlatformDisplay(int32_t* cursor_id_storage) + : cursor_id_storage_(cursor_id_storage) { + display_metrics_.size_in_pixels = gfx::Size(400, 300); + display_metrics_.device_scale_factor = 1.f; + } + ~TestPlatformDisplay() override {} + + // PlatformDisplay: + void Init(PlatformDisplayDelegate* delegate) override { + // It is necessary to tell the delegate about the ViewportMetrics to make + // sure that the DisplayBinding is correctly initialized (and a root-window + // is created). + delegate->OnViewportMetricsChanged(ViewportMetrics(), display_metrics_); + } + void SchedulePaint(const ServerWindow* window, + const gfx::Rect& bounds) override {} + void SetViewportSize(const gfx::Size& size) override {} + void SetTitle(const base::string16& title) override {} + void SetCapture() override {} + void ReleaseCapture() override {} + void SetCursorById(int32_t cursor) override { *cursor_id_storage_ = cursor; } + mojom::Rotation GetRotation() override { return mojom::Rotation::VALUE_0; } + float GetDeviceScaleFactor() override { + return display_metrics_.device_scale_factor; + } + void UpdateTextInputState(const ui::TextInputState& state) override {} + void SetImeVisibility(bool visible) override {} + bool IsFramePending() const override { return false; } + void RequestCopyOfOutput( + std::unique_ptr<cc::CopyOutputRequest> output_request) override {} + int64_t GetDisplayId() const override { return 1; } + + private: + ViewportMetrics display_metrics_; + + int32_t* cursor_id_storage_; + + DISALLOW_COPY_AND_ASSIGN(TestPlatformDisplay); +}; + +ClientWindowId NextUnusedClientWindowId(WindowTree* tree) { + ClientWindowId client_id; + for (ClientSpecificId id = 1;; ++id) { + // Used the id of the client in the upper bits to simplify things. + const ClientWindowId client_id = + ClientWindowId(WindowIdToTransportId(WindowId(tree->id(), id))); + if (!tree->GetWindowByClientId(client_id)) + return client_id; + } +} + +} // namespace + +// WindowManagerWindowTreeFactorySetTestApi ------------------------------------ + +WindowManagerWindowTreeFactorySetTestApi:: + WindowManagerWindowTreeFactorySetTestApi( + WindowManagerWindowTreeFactorySet* + window_manager_window_tree_factory_set) + : window_manager_window_tree_factory_set_( + window_manager_window_tree_factory_set) {} + +WindowManagerWindowTreeFactorySetTestApi:: + ~WindowManagerWindowTreeFactorySetTestApi() {} + +void WindowManagerWindowTreeFactorySetTestApi::Add(const UserId& user_id) { + WindowManagerWindowTreeFactory* factory = + window_manager_window_tree_factory_set_->Add(user_id, nullptr); + factory->CreateWindowTree(nullptr, nullptr); +} + +// TestPlatformDisplayFactory ------------------------------------------------- + +TestPlatformDisplayFactory::TestPlatformDisplayFactory( + int32_t* cursor_id_storage) + : cursor_id_storage_(cursor_id_storage) {} + +TestPlatformDisplayFactory::~TestPlatformDisplayFactory() {} + +PlatformDisplay* TestPlatformDisplayFactory::CreatePlatformDisplay() { + return new TestPlatformDisplay(cursor_id_storage_); +} + +// WindowTreeTestApi --------------------------------------------------------- + +WindowTreeTestApi::WindowTreeTestApi(WindowTree* tree) : tree_(tree) {} +WindowTreeTestApi::~WindowTreeTestApi() {} + +void WindowTreeTestApi::SetEventObserver(mojom::EventMatcherPtr matcher, + uint32_t event_observer_id) { + tree_->SetEventObserver(std::move(matcher), event_observer_id); +} + +// DisplayTestApi ------------------------------------------------------------ + +DisplayTestApi::DisplayTestApi(Display* display) : display_(display) {} +DisplayTestApi::~DisplayTestApi() {} + +// EventDispatcherTestApi ---------------------------------------------------- + +bool EventDispatcherTestApi::IsWindowPointerTarget( + const ServerWindow* window) const { + for (const auto& pair : ed_->pointer_targets_) { + if (pair.second.window == window) + return true; + } + return false; +} + +int EventDispatcherTestApi::NumberPointerTargetsForWindow( + ServerWindow* window) { + int count = 0; + for (const auto& pair : ed_->pointer_targets_) + if (pair.second.window == window) + count++; + return count; +} + +// TestDisplayBinding --------------------------------------------------------- + +WindowTree* TestDisplayBinding::CreateWindowTree(ServerWindow* root) { + const uint32_t embed_flags = 0; + WindowTree* tree = window_server_->EmbedAtWindow( + root, shell::mojom::kRootUserID, mus::mojom::WindowTreeClientPtr(), + embed_flags, base::WrapUnique(new WindowManagerAccessPolicy)); + tree->ConfigureWindowManager(); + return tree; +} + +// TestWindowManager ---------------------------------------------------------- + +void TestWindowManager::WmCreateTopLevelWindow( + uint32_t change_id, + ClientSpecificId requesting_client_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> properties) { + got_create_top_level_window_ = true; + change_id_ = change_id; +} + +void TestWindowManager::WmClientJankinessChanged(ClientSpecificId client_id, + bool janky) {} + +void TestWindowManager::OnAccelerator(uint32_t id, + std::unique_ptr<ui::Event> event) { + on_accelerator_called_ = true; + on_accelerator_id_ = id; +} + +// TestWindowTreeClient ------------------------------------------------------- + +TestWindowTreeClient::TestWindowTreeClient() + : binding_(this), record_on_change_completed_(false) {} +TestWindowTreeClient::~TestWindowTreeClient() {} + +void TestWindowTreeClient::Bind( + mojo::InterfaceRequest<mojom::WindowTreeClient> request) { + binding_.Bind(std::move(request)); +} + +void TestWindowTreeClient::OnEmbed(uint16_t client_id, + mojom::WindowDataPtr root, + mus::mojom::WindowTreePtr tree, + int64_t display_id, + Id focused_window_id, + bool drawn) { + // TODO(sky): add test coverage of |focused_window_id|. + tracker_.OnEmbed(client_id, std::move(root), drawn); +} + +void TestWindowTreeClient::OnEmbeddedAppDisconnected(uint32_t window) { + tracker_.OnEmbeddedAppDisconnected(window); +} + +void TestWindowTreeClient::OnUnembed(Id window_id) { + tracker_.OnUnembed(window_id); +} + +void TestWindowTreeClient::OnLostCapture(Id window_id) {} + +void TestWindowTreeClient::OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr data, + int64_t display_id, + bool drawn) { + tracker_.OnTopLevelCreated(change_id, std::move(data), drawn); +} + +void TestWindowTreeClient::OnWindowBoundsChanged(uint32_t window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + tracker_.OnWindowBoundsChanged(window, std::move(old_bounds), + std::move(new_bounds)); +} + +void TestWindowTreeClient::OnClientAreaChanged( + uint32_t window_id, + const gfx::Insets& new_client_area, + mojo::Array<gfx::Rect> new_additional_client_areas) {} + +void TestWindowTreeClient::OnTransientWindowAdded( + uint32_t window_id, + uint32_t transient_window_id) {} + +void TestWindowTreeClient::OnTransientWindowRemoved( + uint32_t window_id, + uint32_t transient_window_id) {} + +void TestWindowTreeClient::OnWindowHierarchyChanged( + uint32_t window, + uint32_t old_parent, + uint32_t new_parent, + mojo::Array<mojom::WindowDataPtr> windows) { + tracker_.OnWindowHierarchyChanged(window, old_parent, new_parent, + std::move(windows)); +} + +void TestWindowTreeClient::OnWindowReordered(uint32_t window_id, + uint32_t relative_window_id, + mojom::OrderDirection direction) { + tracker_.OnWindowReordered(window_id, relative_window_id, direction); +} + +void TestWindowTreeClient::OnWindowDeleted(uint32_t window) { + tracker_.OnWindowDeleted(window); +} + +void TestWindowTreeClient::OnWindowVisibilityChanged(uint32_t window, + bool visible) { + tracker_.OnWindowVisibilityChanged(window, visible); +} + +void TestWindowTreeClient::OnWindowOpacityChanged(uint32_t window, + float old_opacity, + float new_opacity) { + tracker_.OnWindowOpacityChanged(window, new_opacity); +} + +void TestWindowTreeClient::OnWindowParentDrawnStateChanged(uint32_t window, + bool drawn) { + tracker_.OnWindowParentDrawnStateChanged(window, drawn); +} + +void TestWindowTreeClient::OnWindowSharedPropertyChanged( + uint32_t window, + const mojo::String& name, + mojo::Array<uint8_t> new_data) { + tracker_.OnWindowSharedPropertyChanged(window, name, std::move(new_data)); +} + +void TestWindowTreeClient::OnWindowInputEvent(uint32_t event_id, + uint32_t window, + std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) { + tracker_.OnWindowInputEvent(window, *event.get(), event_observer_id); +} + +void TestWindowTreeClient::OnEventObserved(std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) { + tracker_.OnEventObserved(*event.get(), event_observer_id); +} + +void TestWindowTreeClient::OnWindowFocused(uint32_t focused_window_id) { + tracker_.OnWindowFocused(focused_window_id); +} + +void TestWindowTreeClient::OnWindowPredefinedCursorChanged( + uint32_t window_id, + mojom::Cursor cursor_id) { + tracker_.OnWindowPredefinedCursorChanged(window_id, cursor_id); +} + +void TestWindowTreeClient::OnChangeCompleted(uint32_t change_id, bool success) { + if (record_on_change_completed_) + tracker_.OnChangeCompleted(change_id, success); +} + +void TestWindowTreeClient::RequestClose(uint32_t window_id) {} + +void TestWindowTreeClient::GetWindowManager( + mojo::AssociatedInterfaceRequest<mojom::WindowManager> internal) {} + +// TestWindowTreeBinding ------------------------------------------------------ + +TestWindowTreeBinding::TestWindowTreeBinding(WindowTree* tree) + : WindowTreeBinding(&client_), tree_(tree) {} +TestWindowTreeBinding::~TestWindowTreeBinding() {} + +mojom::WindowManager* TestWindowTreeBinding::GetWindowManager() { + if (!window_manager_.get()) + window_manager_.reset(new TestWindowManager); + return window_manager_.get(); +} +void TestWindowTreeBinding::SetIncomingMethodCallProcessingPaused(bool paused) { + is_paused_ = paused; +} + +// TestWindowServerDelegate ---------------------------------------------- + +TestWindowServerDelegate::TestWindowServerDelegate() {} +TestWindowServerDelegate::~TestWindowServerDelegate() {} + +Display* TestWindowServerDelegate::AddDisplay() { + // Display manages its own lifetime. + Display* display = new Display(window_server_, PlatformDisplayInitParams()); + display->Init(nullptr); + return display; +} + +void TestWindowServerDelegate::OnNoMoreDisplays() { + got_on_no_more_displays_ = true; +} + +std::unique_ptr<WindowTreeBinding> +TestWindowServerDelegate::CreateWindowTreeBinding( + BindingType type, + ws::WindowServer* window_server, + ws::WindowTree* tree, + mojom::WindowTreeRequest* tree_request, + mojom::WindowTreeClientPtr* client) { + std::unique_ptr<TestWindowTreeBinding> binding( + new TestWindowTreeBinding(tree)); + bindings_.push_back(binding.get()); + return std::move(binding); +} + +void TestWindowServerDelegate::CreateDefaultDisplays() { + DCHECK(num_displays_to_create_); + DCHECK(window_server_); + + for (int i = 0; i < num_displays_to_create_; ++i) + AddDisplay(); +} + +bool TestWindowServerDelegate::IsTestConfig() const { + return true; +} + +// WindowEventTargetingHelper ------------------------------------------------ + +WindowEventTargetingHelper::WindowEventTargetingHelper() + : wm_client_(nullptr), + cursor_id_(0), + platform_display_factory_(&cursor_id_), + display_binding_(nullptr), + display_(nullptr), + surfaces_state_(new SurfacesState()), + window_server_(nullptr) { + PlatformDisplay::set_factory_for_testing(&platform_display_factory_); + window_server_.reset( + new WindowServer(&window_server_delegate_, surfaces_state_)); + PlatformDisplayInitParams display_init_params; + display_init_params.surfaces_state = surfaces_state_; + display_ = new Display(window_server_.get(), display_init_params); + display_binding_ = new TestDisplayBinding(window_server_.get()); + display_->Init(base::WrapUnique(display_binding_)); + wm_client_ = window_server_delegate_.last_client(); + wm_client_->tracker()->changes()->clear(); +} + +WindowEventTargetingHelper::~WindowEventTargetingHelper() {} + +ServerWindow* WindowEventTargetingHelper::CreatePrimaryTree( + const gfx::Rect& root_window_bounds, + const gfx::Rect& window_bounds) { + WindowTree* wm_tree = window_server_->GetTreeWithId(1); + const ClientWindowId embed_window_id( + WindowIdToTransportId(WindowId(wm_tree->id(), 1))); + EXPECT_TRUE(wm_tree->NewWindow(embed_window_id, ServerWindow::Properties())); + EXPECT_TRUE(wm_tree->SetWindowVisibility(embed_window_id, true)); + EXPECT_TRUE(wm_tree->AddWindow(FirstRootId(wm_tree), embed_window_id)); + display_->root_window()->SetBounds(root_window_bounds); + mojom::WindowTreeClientPtr client; + mojom::WindowTreeClientRequest client_request = GetProxy(&client); + wm_client_->Bind(std::move(client_request)); + const uint32_t embed_flags = 0; + wm_tree->Embed(embed_window_id, std::move(client), embed_flags); + ServerWindow* embed_window = wm_tree->GetWindowByClientId(embed_window_id); + WindowTree* tree1 = window_server_->GetTreeWithRoot(embed_window); + EXPECT_NE(nullptr, tree1); + EXPECT_NE(tree1, wm_tree); + WindowTreeTestApi(tree1).set_user_id(wm_tree->user_id()); + + embed_window->SetBounds(window_bounds); + + return embed_window; +} + +void WindowEventTargetingHelper::CreateSecondaryTree( + ServerWindow* embed_window, + const gfx::Rect& window_bounds, + TestWindowTreeClient** out_client, + WindowTree** window_tree, + ServerWindow** window) { + WindowTree* tree1 = window_server_->GetTreeWithRoot(embed_window); + ASSERT_TRUE(tree1 != nullptr); + const ClientWindowId child1_id( + WindowIdToTransportId(WindowId(tree1->id(), 1))); + EXPECT_TRUE(tree1->NewWindow(child1_id, ServerWindow::Properties())); + ServerWindow* child1 = tree1->GetWindowByClientId(child1_id); + ASSERT_TRUE(child1); + EXPECT_TRUE(tree1->AddWindow(ClientWindowIdForWindow(tree1, embed_window), + child1_id)); + tree1->GetDisplay(embed_window)->AddActivationParent(embed_window); + + child1->SetVisible(true); + child1->SetBounds(window_bounds); + EnableHitTest(child1); + + TestWindowTreeClient* embed_client = + window_server_delegate_.last_client(); + embed_client->tracker()->changes()->clear(); + wm_client_->tracker()->changes()->clear(); + + *out_client = embed_client; + *window_tree = tree1; + *window = child1; +} + +void WindowEventTargetingHelper::SetTaskRunner( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + message_loop_.SetTaskRunner(task_runner); +} + +// ---------------------------------------------------------------------------- + +ServerWindow* FirstRoot(WindowTree* tree) { + return tree->roots().size() == 1u + ? tree->GetWindow((*tree->roots().begin())->id()) + : nullptr; +} + +ClientWindowId FirstRootId(WindowTree* tree) { + ServerWindow* first_root = FirstRoot(tree); + return first_root ? ClientWindowIdForWindow(tree, first_root) + : ClientWindowId(); +} + +ClientWindowId ClientWindowIdForWindow(WindowTree* tree, + const ServerWindow* window) { + ClientWindowId client_window_id; + // If window isn't known we'll return 0, which should then error out. + tree->IsWindowKnown(window, &client_window_id); + return client_window_id; +} + +ServerWindow* NewWindowInTree(WindowTree* tree, ClientWindowId* client_id) { + return NewWindowInTreeWithParent(tree, FirstRoot(tree), client_id); +} + +ServerWindow* NewWindowInTreeWithParent(WindowTree* tree, + ServerWindow* parent, + ClientWindowId* client_id) { + if (!parent) + return nullptr; + ClientWindowId parent_client_id; + if (!tree->IsWindowKnown(parent, &parent_client_id)) + return nullptr; + ClientWindowId client_window_id = NextUnusedClientWindowId(tree); + if (!tree->NewWindow(client_window_id, ServerWindow::Properties())) + return nullptr; + if (!tree->SetWindowVisibility(client_window_id, true)) + return nullptr; + if (!tree->AddWindow(parent_client_id, client_window_id)) + return nullptr; + *client_id = client_window_id; + return tree->GetWindowByClientId(client_window_id); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/test_utils.h b/chromium/components/mus/ws/test_utils.h new file mode 100644 index 00000000000..ede927722ec --- /dev/null +++ b/chromium/components/mus/ws/test_utils.h @@ -0,0 +1,537 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_TEST_UTILS_H_ +#define COMPONENTS_MUS_WS_TEST_UTILS_H_ + +#include <stdint.h> + +#include <vector> + +#include "components/mus/public/interfaces/display.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/event_dispatcher.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/test_change_tracker.h" +#include "components/mus/ws/user_activity_monitor.h" +#include "components/mus/ws/user_display_manager.h" +#include "components/mus/ws/user_id.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_manager_window_tree_factory_set.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" + +namespace mus { +namespace ws { +namespace test { + +// Collection of utilities useful in creating mus tests. + +class WindowManagerWindowTreeFactorySetTestApi { + public: + explicit WindowManagerWindowTreeFactorySetTestApi( + WindowManagerWindowTreeFactorySet* + window_manager_window_tree_factory_set); + ~WindowManagerWindowTreeFactorySetTestApi(); + + void Add(const UserId& user_id); + + private: + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerWindowTreeFactorySetTestApi); +}; + +// ----------------------------------------------------------------------------- + +class UserDisplayManagerTestApi { + public: + explicit UserDisplayManagerTestApi(UserDisplayManager* udm) : udm_(udm) {} + ~UserDisplayManagerTestApi() {} + + void SetTestObserver(mojom::DisplayManagerObserver* observer) { + udm_->test_observer_ = observer; + if (observer) + udm_->OnObserverAdded(observer); + } + + private: + UserDisplayManager* udm_; + + DISALLOW_COPY_AND_ASSIGN(UserDisplayManagerTestApi); +}; + +// ----------------------------------------------------------------------------- + +class UserActivityMonitorTestApi { + public: + explicit UserActivityMonitorTestApi(UserActivityMonitor* monitor) + : monitor_(monitor) {} + + void SetTimerTaskRunner( + scoped_refptr<base::SingleThreadTaskRunner> task_runner) { + monitor_->idle_timer_.SetTaskRunner(task_runner); + } + + private: + UserActivityMonitor* monitor_; + DISALLOW_COPY_AND_ASSIGN(UserActivityMonitorTestApi); +}; + +// ----------------------------------------------------------------------------- + +class WindowTreeTestApi { + public: + explicit WindowTreeTestApi(WindowTree* tree); + ~WindowTreeTestApi(); + + void set_user_id(const UserId& user_id) { tree_->user_id_ = user_id; } + void set_window_manager_internal(mojom::WindowManager* wm_internal) { + tree_->window_manager_internal_ = wm_internal; + } + void AckOldestEvent() { + tree_->OnWindowInputEventAck(tree_->event_ack_id_, + mojom::EventResult::UNHANDLED); + } + void EnableCapture() { tree_->event_ack_id_ = 1u; } + void AckLastEvent(mojom::EventResult result) { + tree_->OnWindowInputEventAck(tree_->event_ack_id_, result); + } + + void SetEventObserver(mojom::EventMatcherPtr matcher, + uint32_t event_observer_id); + + private: + WindowTree* tree_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeTestApi); +}; + +// ----------------------------------------------------------------------------- + +class DisplayTestApi { + public: + explicit DisplayTestApi(Display* display); + ~DisplayTestApi(); + + void OnEvent(const ui::Event& event) { display_->OnEvent(event); } + + private: + Display* display_; + + DISALLOW_COPY_AND_ASSIGN(DisplayTestApi); +}; + +// ----------------------------------------------------------------------------- + +class EventDispatcherTestApi { + public: + explicit EventDispatcherTestApi(EventDispatcher* ed) : ed_(ed) {} + ~EventDispatcherTestApi() {} + + bool AreAnyPointersDown() const { return ed_->AreAnyPointersDown(); } + bool is_mouse_button_down() const { return ed_->mouse_button_down_; } + bool IsWindowPointerTarget(const ServerWindow* window) const; + int NumberPointerTargetsForWindow(ServerWindow* window); + ModalWindowController* modal_window_controller() const { + return &ed_->modal_window_controller_; + } + ServerWindow* capture_window() { return ed_->capture_window_; } + + private: + EventDispatcher* ed_; + + DISALLOW_COPY_AND_ASSIGN(EventDispatcherTestApi); +}; + +// ----------------------------------------------------------------------------- + +class ModalWindowControllerTestApi { + public: + explicit ModalWindowControllerTestApi(ModalWindowController* mwc) + : mwc_(mwc) {} + ~ModalWindowControllerTestApi() {} + + ServerWindow* GetActiveSystemModalWindow() const { + return mwc_->GetActiveSystemModalWindow(); + } + + private: + ModalWindowController* mwc_; + + DISALLOW_COPY_AND_ASSIGN(ModalWindowControllerTestApi); +}; + +// ----------------------------------------------------------------------------- + +class WindowManagerStateTestApi { + public: + explicit WindowManagerStateTestApi(WindowManagerState* wms) : wms_(wms) {} + ~WindowManagerStateTestApi() {} + + void DispatchInputEventToWindow(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + Accelerator* accelerator) { + wms_->DispatchInputEventToWindow(target, client_id, event, accelerator); + } + + ClientSpecificId GetEventTargetClientId(ServerWindow* window, + bool in_nonclient_area) { + return wms_->GetEventTargetClientId(window, in_nonclient_area); + } + + void ProcessEvent(const ui::Event& event) { wms_->ProcessEvent(event); } + + void OnEventAckTimeout(ClientSpecificId client_id) { + wms_->OnEventAckTimeout(client_id); + } + + ClientSpecificId GetEventTargetClientId(const ServerWindow* window, + bool in_nonclient_area) { + return wms_->GetEventTargetClientId(window, in_nonclient_area); + } + + mojom::WindowTree* tree_awaiting_input_ack() { + return wms_->tree_awaiting_input_ack_; + } + + private: + WindowManagerState* wms_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerStateTestApi); +}; + +// ----------------------------------------------------------------------------- + +// Factory that always embeds the new WindowTree as the root user id. +class TestDisplayBinding : public DisplayBinding { + public: + explicit TestDisplayBinding(WindowServer* window_server) + : window_server_(window_server) {} + ~TestDisplayBinding() override {} + + private: + // DisplayBinding: + WindowTree* CreateWindowTree(ServerWindow* root) override; + + WindowServer* window_server_; + + DISALLOW_COPY_AND_ASSIGN(TestDisplayBinding); +}; + +// ----------------------------------------------------------------------------- + +// Factory that dispenses TestPlatformDisplays. +class TestPlatformDisplayFactory : public PlatformDisplayFactory { + public: + explicit TestPlatformDisplayFactory(int32_t* cursor_id_storage); + ~TestPlatformDisplayFactory(); + + // PlatformDisplayFactory: + PlatformDisplay* CreatePlatformDisplay() override; + + private: + int32_t* cursor_id_storage_; + + DISALLOW_COPY_AND_ASSIGN(TestPlatformDisplayFactory); +}; + +// ----------------------------------------------------------------------------- + +class TestWindowManager : public mojom::WindowManager { + public: + TestWindowManager() + : got_create_top_level_window_(false), + change_id_(0u), + on_accelerator_called_(false), + on_accelerator_id_(0u) {} + ~TestWindowManager() override {} + + bool did_call_create_top_level_window(uint32_t* change_id) { + if (!got_create_top_level_window_) + return false; + + got_create_top_level_window_ = false; + *change_id = change_id_; + return true; + } + + bool on_accelerator_called() { return on_accelerator_called_; } + uint32_t on_accelerator_id() { return on_accelerator_id_; } + + private: + // WindowManager: + void OnConnect(uint16_t client_id) override {} + void WmNewDisplayAdded(mus::mojom::DisplayPtr display, + mus::mojom::WindowDataPtr root, + bool drawn) override {} + void WmSetBounds(uint32_t change_id, + uint32_t window_id, + const gfx::Rect& bounds) override {} + void WmSetProperty(uint32_t change_id, + uint32_t window_id, + const mojo::String& name, + mojo::Array<uint8_t> value) override {} + void WmCreateTopLevelWindow( + uint32_t change_id, + ClientSpecificId requesting_client_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> properties) override; + void WmClientJankinessChanged(ClientSpecificId client_id, + bool janky) override; + void OnAccelerator(uint32_t id, std::unique_ptr<ui::Event> event) override; + + bool got_create_top_level_window_; + uint32_t change_id_; + + bool on_accelerator_called_; + uint32_t on_accelerator_id_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowManager); +}; + +// ----------------------------------------------------------------------------- + +// WindowTreeClient implementation that logs all calls to a TestChangeTracker. +class TestWindowTreeClient : public mus::mojom::WindowTreeClient { + public: + TestWindowTreeClient(); + ~TestWindowTreeClient() override; + + TestChangeTracker* tracker() { return &tracker_; } + + void Bind(mojo::InterfaceRequest<mojom::WindowTreeClient> request); + + void set_record_on_change_completed(bool value) { + record_on_change_completed_ = value; + } + + private: + // WindowTreeClient: + void OnEmbed(uint16_t client_id, + mojom::WindowDataPtr root, + mus::mojom::WindowTreePtr tree, + int64_t display_id, + Id focused_window_id, + bool drawn) override; + void OnEmbeddedAppDisconnected(uint32_t window) override; + void OnUnembed(Id window_id) override; + void OnLostCapture(Id window_id) override; + void OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr data, + int64_t display_id, + bool drawn) override; + void OnWindowBoundsChanged(uint32_t window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnClientAreaChanged( + uint32_t window_id, + const gfx::Insets& new_client_area, + mojo::Array<gfx::Rect> new_additional_client_areas) override; + void OnTransientWindowAdded(uint32_t window_id, + uint32_t transient_window_id) override; + void OnTransientWindowRemoved(uint32_t window_id, + uint32_t transient_window_id) override; + void OnWindowHierarchyChanged( + uint32_t window, + uint32_t old_parent, + uint32_t new_parent, + mojo::Array<mojom::WindowDataPtr> windows) override; + void OnWindowReordered(uint32_t window_id, + uint32_t relative_window_id, + mojom::OrderDirection direction) override; + void OnWindowDeleted(uint32_t window) override; + void OnWindowVisibilityChanged(uint32_t window, bool visible) override; + void OnWindowOpacityChanged(uint32_t window, + float old_opacity, + float new_opacity) override; + void OnWindowParentDrawnStateChanged(uint32_t window, bool drawn) override; + void OnWindowSharedPropertyChanged(uint32_t window, + const mojo::String& name, + mojo::Array<uint8_t> new_data) override; + void OnWindowInputEvent(uint32_t event_id, + uint32_t window, + std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) override; + void OnEventObserved(std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) override; + void OnWindowFocused(uint32_t focused_window_id) override; + void OnWindowPredefinedCursorChanged(uint32_t window_id, + mojom::Cursor cursor_id) override; + void OnChangeCompleted(uint32_t change_id, bool success) override; + void RequestClose(uint32_t window_id) override; + void GetWindowManager( + mojo::AssociatedInterfaceRequest<mojom::WindowManager> internal) override; + + TestChangeTracker tracker_; + mojo::Binding<mojom::WindowTreeClient> binding_; + bool record_on_change_completed_ = false; + + DISALLOW_COPY_AND_ASSIGN(TestWindowTreeClient); +}; + +// ----------------------------------------------------------------------------- + +// WindowTreeBinding implementation that vends TestWindowTreeBinding. +class TestWindowTreeBinding : public WindowTreeBinding { + public: + explicit TestWindowTreeBinding(WindowTree* tree); + ~TestWindowTreeBinding() override; + + WindowTree* tree() { return tree_; } + TestWindowTreeClient* client() { return &client_; } + + bool is_paused() const { return is_paused_; } + + // WindowTreeBinding: + mojom::WindowManager* GetWindowManager() override; + void SetIncomingMethodCallProcessingPaused(bool paused) override; + + private: + WindowTree* tree_; + TestWindowTreeClient client_; + bool is_paused_ = false; + std::unique_ptr<TestWindowManager> window_manager_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowTreeBinding); +}; + +// ----------------------------------------------------------------------------- + +// WindowServerDelegate that creates TestWindowTreeClients. +class TestWindowServerDelegate : public WindowServerDelegate { + public: + TestWindowServerDelegate(); + ~TestWindowServerDelegate() override; + + void set_window_server(WindowServer* window_server) { + window_server_ = window_server; + } + + void set_num_displays_to_create(int count) { + num_displays_to_create_ = count; + } + + TestWindowTreeClient* last_client() { + return last_binding() ? last_binding()->client() : nullptr; + } + TestWindowTreeBinding* last_binding() { + return bindings_.empty() ? nullptr : bindings_.back(); + } + + std::vector<TestWindowTreeBinding*>* bindings() { return &bindings_; } + + bool got_on_no_more_displays() const { return got_on_no_more_displays_; } + + Display* AddDisplay(); + + // WindowServerDelegate: + void OnNoMoreDisplays() override; + std::unique_ptr<WindowTreeBinding> CreateWindowTreeBinding( + BindingType type, + ws::WindowServer* window_server, + ws::WindowTree* tree, + mojom::WindowTreeRequest* tree_request, + mojom::WindowTreeClientPtr* client) override; + void CreateDefaultDisplays() override; + bool IsTestConfig() const override; + + private: + // If CreateDefaultDisplays() this is the number of Displays that are + // created. The default is 0, which results in a DCHECK. + int num_displays_to_create_ = 0; + WindowServer* window_server_ = nullptr; + bool got_on_no_more_displays_ = false; + // All TestWindowTreeBinding objects created via CreateWindowTreeBinding. + // These are owned by the corresponding WindowTree. + std::vector<TestWindowTreeBinding*> bindings_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowServerDelegate); +}; + +// ----------------------------------------------------------------------------- + +// Helper class which owns all of the necessary objects to test event targeting +// of ServerWindow objects. +class WindowEventTargetingHelper { + public: + WindowEventTargetingHelper(); + ~WindowEventTargetingHelper(); + + // Creates |window| as an embeded window of the primary tree. This window is a + // root window of its own tree, with bounds |window_bounds|. The bounds of the + // root window of |display_| are defined by |root_window_bounds|. + ServerWindow* CreatePrimaryTree(const gfx::Rect& root_window_bounds, + const gfx::Rect& window_bounds); + // Creates a secondary tree, embedded as a child of |embed_window|. The + // resulting |window| is setup for event targeting, with bounds + // |window_bounds|. + void CreateSecondaryTree(ServerWindow* embed_window, + const gfx::Rect& window_bounds, + TestWindowTreeClient** out_client, + WindowTree** window_tree, + ServerWindow** window); + // Sets the task runner for |message_loop_| + void SetTaskRunner(scoped_refptr<base::SingleThreadTaskRunner> task_runner); + + int32_t cursor_id() { return cursor_id_; } + Display* display() { return display_; } + TestWindowTreeBinding* last_binding() { + return window_server_delegate_.last_binding(); + } + TestWindowTreeClient* last_window_tree_client() { + return window_server_delegate_.last_client(); + } + TestWindowTreeClient* wm_client() { return wm_client_; } + WindowServer* window_server() { return window_server_.get(); } + + private: + // TestWindowTreeClient that is used for the WM client. Owned by + // |window_server_delegate_| + TestWindowTreeClient* wm_client_; + int32_t cursor_id_; + TestPlatformDisplayFactory platform_display_factory_; + TestWindowServerDelegate window_server_delegate_; + // Owned by WindowServer + TestDisplayBinding* display_binding_; + // Owned by WindowServer's DisplayManager. + Display* display_; + scoped_refptr<SurfacesState> surfaces_state_; + std::unique_ptr<WindowServer> window_server_; + // Needed to Bind to |wm_client_| + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(WindowEventTargetingHelper); +}; + +// ----------------------------------------------------------------------------- + +// Returns the first and only root of |tree|. If |tree| has zero or more than +// one root returns null. +ServerWindow* FirstRoot(WindowTree* tree); + +// Returns the ClientWindowId of the first root of |tree|, or an empty +// ClientWindowId if |tree| has zero or more than one root. +ClientWindowId FirstRootId(WindowTree* tree); + +// Returns |tree|s ClientWindowId for |window|. +ClientWindowId ClientWindowIdForWindow(WindowTree* tree, + const ServerWindow* window); + +// Creates a new visible window as a child of the single root of |tree|. +// |client_id| set to the ClientWindowId of the new window. +ServerWindow* NewWindowInTree(WindowTree* tree, ClientWindowId* client_id); +ServerWindow* NewWindowInTreeWithParent(WindowTree* tree, + ServerWindow* parent, + ClientWindowId* client_id); + +} // namespace test +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_TEST_UTILS_H_ diff --git a/chromium/components/mus/ws/touch_controller.cc b/chromium/components/mus/ws/touch_controller.cc new file mode 100644 index 00000000000..e78b3489dc9 --- /dev/null +++ b/chromium/components/mus/ws/touch_controller.cc @@ -0,0 +1,105 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/touch_controller.h" + +#include <set> +#include <vector> + +#include "base/logging.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_manager.h" +#include "ui/events/devices/device_data_manager.h" +#include "ui/events/devices/touchscreen_device.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/transform.h" + +namespace mus { +namespace ws { + +namespace { + +// Computes the scale ratio for the TouchEvent's radius. +double ComputeTouchResolutionScale(const Display* touch_display, + const ui::TouchscreenDevice& touch_device) { + gfx::Size touch_display_size = touch_display->GetSize(); + + if (touch_device.size.IsEmpty() || touch_display_size.IsEmpty()) + return 1.0; + + double display_area = touch_display_size.GetArea(); + double touch_area = touch_device.size.GetArea(); + double ratio = std::sqrt(display_area / touch_area); + + return ratio; +} + +// Computes a touch transform that maps from window bounds to touchscreen size. +// Assumes scale factor of 1.0. +gfx::Transform ComputeTouchTransform( + const Display* touch_display, + const ui::TouchscreenDevice& touch_device) { + gfx::Size touch_display_size = touch_display->GetSize(); + + gfx::SizeF current_size(touch_display_size); + gfx::SizeF touch_area(touch_device.size); + gfx::Transform transform; + + if (current_size.IsEmpty() || touch_area.IsEmpty()) + return transform; + + // Take care of scaling between touchscreen area and display resolution. + transform.Scale(current_size.width() / touch_area.width(), + current_size.height() / touch_area.height()); + return transform; +} + +} // namespace + +TouchController::TouchController(DisplayManager* display_manager) + : display_manager_(display_manager) { + DCHECK(display_manager_); + ui::DeviceDataManager::GetInstance()->AddObserver(this); +} + +TouchController::~TouchController() { + ui::DeviceDataManager::GetInstance()->RemoveObserver(this); +} + +void TouchController::UpdateTouchTransforms() const { + ui::DeviceDataManager* device_manager = ui::DeviceDataManager::GetInstance(); + device_manager->ClearTouchDeviceAssociations(); + + const std::set<Display*>& touch_displays = display_manager_->displays(); + const std::vector<ui::TouchscreenDevice>& touch_devices = + device_manager->GetTouchscreenDevices(); + + // Mash can only handle a single display so this doesn't implement support for + // matching touchscreens with multiple displays. + // TODO(kylechar): Implement support for multiple displays when needed. + if (touch_displays.size() == 1 && touch_devices.size() == 1) { + const Display* touch_display = *touch_displays.begin(); + const ui::TouchscreenDevice& touch_device = touch_devices[0]; + + int64_t touch_display_id = touch_display->GetPlatformDisplayId(); + int touch_device_id = touch_device.id; + + if (touch_device_id != ui::InputDevice::kInvalidId) { + device_manager->UpdateTouchRadiusScale( + touch_device_id, + ComputeTouchResolutionScale(touch_display, touch_device)); + + device_manager->UpdateTouchInfoForDisplay( + touch_display_id, touch_device_id, + ComputeTouchTransform(touch_display, touch_device)); + } + } +} + +void TouchController::OnTouchscreenDeviceConfigurationChanged() { + UpdateTouchTransforms(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/touch_controller.h b/chromium/components/mus/ws/touch_controller.h new file mode 100644 index 00000000000..4505ce47164 --- /dev/null +++ b/chromium/components/mus/ws/touch_controller.h @@ -0,0 +1,37 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_TOUCH_CONTROLLER_H_ +#define COMPONENTS_MUS_WS_TOUCH_CONTROLLER_H_ + +#include "base/macros.h" +#include "ui/events/devices/input_device_event_observer.h" + +namespace mus { +namespace ws { + +class DisplayManager; + +// Tracks changes to displays and touchscreen devices. Updates the mapping +// between display devices and touchscreen devices when changes occur. +class TouchController : public ui::InputDeviceEventObserver { + public: + explicit TouchController(DisplayManager* display_manager); + ~TouchController() override; + + void UpdateTouchTransforms() const; + + // ui::InputDeviceEventObserver: + void OnTouchscreenDeviceConfigurationChanged() override; + + private: + DisplayManager* display_manager_; + + DISALLOW_COPY_AND_ASSIGN(TouchController); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_TOUCH_CONTROLLER_H_ diff --git a/chromium/components/mus/ws/transient_windows_unittest.cc b/chromium/components/mus/ws/transient_windows_unittest.cc new file mode 100644 index 00000000000..b20e40e467f --- /dev/null +++ b/chromium/components/mus/ws/transient_windows_unittest.cc @@ -0,0 +1,342 @@ +// 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/macros.h" +#include "base/strings/string_number_conversions.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_observer.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mus { +namespace ws { + +namespace { + +class TestTransientWindowObserver : public ServerWindowObserver { + public: + TestTransientWindowObserver() : add_count_(0), remove_count_(0) {} + + ~TestTransientWindowObserver() override {} + + int add_count() const { return add_count_; } + int remove_count() const { return remove_count_; } + + // TransientWindowObserver overrides: + void OnTransientWindowAdded(ServerWindow* window, + ServerWindow* transient) override { + add_count_++; + } + void OnTransientWindowRemoved(ServerWindow* window, + ServerWindow* transient) override { + remove_count_++; + } + + private: + int add_count_; + int remove_count_; + + DISALLOW_COPY_AND_ASSIGN(TestTransientWindowObserver); +}; + +ServerWindow* CreateTestWindow(TestServerWindowDelegate* delegate, + const WindowId& window_id, + ServerWindow* parent) { + ServerWindow* window = new ServerWindow(delegate, window_id); + window->SetVisible(true); + if (parent) + parent->Add(window); + else + delegate->set_root_window(window); + return window; +} + +std::string ChildWindowIDsAsString(ServerWindow* parent) { + std::string result; + for (auto i = parent->children().begin(); i != parent->children().end(); + ++i) { + if (!result.empty()) + result += " "; + result += base::IntToString(WindowIdToTransportId((*i)->id())); + } + return result; +} + +} // namespace + +class TransientWindowsTest : public testing::Test { + public: + TransientWindowsTest() {} + ~TransientWindowsTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TransientWindowsTest); +}; + +TEST_F(TransientWindowsTest, TransientChildren) { + TestServerWindowDelegate server_window_delegate; + + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> w1( + CreateTestWindow(&server_window_delegate, WindowId(1, 1), parent.get())); + std::unique_ptr<ServerWindow> w3( + CreateTestWindow(&server_window_delegate, WindowId(1, 2), parent.get())); + + ServerWindow* w2 = + CreateTestWindow(&server_window_delegate, WindowId(1, 3), parent.get()); + + // w2 is now owned by w1. + w1->AddTransientWindow(w2); + // Stack w1 at the top (end), this should force w2 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + ASSERT_EQ(3u, parent->children().size()); + EXPECT_EQ(w2, parent->children().back()); + + // Destroy w1, which should also destroy w3 (since it's a transient child). + w1.reset(); + w2 = nullptr; + ASSERT_EQ(1u, parent->children().size()); + EXPECT_EQ(w3.get(), parent->children()[0]); +} + +// Tests that transient children are stacked as a unit when using stack above. +TEST_F(TransientWindowsTest, TransientChildrenGroupAbove) { + TestServerWindowDelegate server_window_delegate; + + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> w1( + CreateTestWindow(&server_window_delegate, WindowId(0, 1), parent.get())); + + ServerWindow* w11 = + CreateTestWindow(&server_window_delegate, WindowId(0, 11), parent.get()); + std::unique_ptr<ServerWindow> w2( + CreateTestWindow(&server_window_delegate, WindowId(0, 2), parent.get())); + + ServerWindow* w21 = + CreateTestWindow(&server_window_delegate, WindowId(0, 21), parent.get()); + ServerWindow* w211 = + CreateTestWindow(&server_window_delegate, WindowId(0, 211), parent.get()); + ServerWindow* w212 = + CreateTestWindow(&server_window_delegate, WindowId(0, 212), parent.get()); + ServerWindow* w213 = + CreateTestWindow(&server_window_delegate, WindowId(0, 213), parent.get()); + ServerWindow* w22 = + CreateTestWindow(&server_window_delegate, WindowId(0, 22), parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + // w11 is now owned by w1. + w1->AddTransientWindow(w11); + // w21 is now owned by w2. + w2->AddTransientWindow(w21); + // w22 is now owned by w2. + w2->AddTransientWindow(w22); + // w211 is now owned by w21. + w21->AddTransientWindow(w211); + // w212 is now owned by w21. + w21->AddTransientWindow(w212); + // w213 is now owned by w21. + w21->AddTransientWindow(w213); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w1 at the top (end), this should force w11 to be last (on top of w1). + parent->StackChildAtTop(w1.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtTop(w2.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + w11->Reorder(w2.get(), mojom::OrderDirection::ABOVE); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + w21->Reorder(w1.get(), mojom::OrderDirection::ABOVE); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + w21->Reorder(w22, mojom::OrderDirection::ABOVE); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + w11->Reorder(w21, mojom::OrderDirection::ABOVE); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + w213->Reorder(w21, mojom::OrderDirection::ABOVE); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent above its transient child. + w21->Reorder(w211, mojom::OrderDirection::ABOVE); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // This tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + w2->Reorder(w1.get(), mojom::OrderDirection::ABOVE); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + w11->Reorder(w213, mojom::OrderDirection::ABOVE); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +TEST_F(TransientWindowsTest, TransienChildGroupBelow) { + TestServerWindowDelegate server_window_delegate; + + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&server_window_delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> w1( + CreateTestWindow(&server_window_delegate, WindowId(0, 1), parent.get())); + + ServerWindow* w11 = + CreateTestWindow(&server_window_delegate, WindowId(0, 11), parent.get()); + std::unique_ptr<ServerWindow> w2( + CreateTestWindow(&server_window_delegate, WindowId(0, 2), parent.get())); + + ServerWindow* w21 = + CreateTestWindow(&server_window_delegate, WindowId(0, 21), parent.get()); + ServerWindow* w211 = + CreateTestWindow(&server_window_delegate, WindowId(0, 211), parent.get()); + ServerWindow* w212 = + CreateTestWindow(&server_window_delegate, WindowId(0, 212), parent.get()); + ServerWindow* w213 = + CreateTestWindow(&server_window_delegate, WindowId(0, 213), parent.get()); + ServerWindow* w22 = + CreateTestWindow(&server_window_delegate, WindowId(0, 22), parent.get()); + ASSERT_EQ(8u, parent->children().size()); + + // w11 is now owned by w1. + w1->AddTransientWindow(w11); + // w21 is now owned by w2. + w2->AddTransientWindow(w21); + // w22 is now owned by w2. + w2->AddTransientWindow(w22); + // w211 is now owned by w21. + w21->AddTransientWindow(w211); + // w212 is now owned by w21. + w21->AddTransientWindow(w212); + // w213 is now owned by w21. + w21->AddTransientWindow(w213); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + // Stack w2 at the bottom, this should force w11 to be last (on top of w1). + // This also tests that the order in children_ array rather than in + // transient_children_ array is used when reinserting transient children. + // If transient_children_ array was used '22' would be following '21'. + parent->StackChildAtBottom(w2.get()); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + parent->StackChildAtBottom(w1.get()); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + w21->Reorder(w1.get(), mojom::OrderDirection::BELOW); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get())); + + w11->Reorder(w2.get(), mojom::OrderDirection::BELOW); + EXPECT_EQ(w22, parent->children().back()); + EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get())); + + w22->Reorder(w21, mojom::OrderDirection::BELOW); + EXPECT_EQ(w213, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get())); + + w21->Reorder(w11, mojom::OrderDirection::BELOW); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get())); + + w213->Reorder(w211, mojom::OrderDirection::BELOW); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + // No change when stacking a transient parent below its transient child. + w21->Reorder(w211, mojom::OrderDirection::BELOW); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); + + w1->Reorder(w2.get(), mojom::OrderDirection::BELOW); + EXPECT_EQ(w212, parent->children().back()); + EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get())); + + w213->Reorder(w11, mojom::OrderDirection::BELOW); + EXPECT_EQ(w11, parent->children().back()); + EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get())); +} + +// Tests that transient windows are stacked properly when created. +TEST_F(TransientWindowsTest, StackUponCreation) { + TestServerWindowDelegate delegate; + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> window0( + CreateTestWindow(&delegate, WindowId(0, 1), parent.get())); + std::unique_ptr<ServerWindow> window1( + CreateTestWindow(&delegate, WindowId(0, 2), parent.get())); + + ServerWindow* window2 = + CreateTestWindow(&delegate, WindowId(0, 3), parent.get()); + window0->AddTransientWindow(window2); + EXPECT_EQ("1 3 2", ChildWindowIDsAsString(parent.get())); +} + +// Tests that windows are restacked properly after a call to +// AddTransientWindow() or RemoveTransientWindow(). +TEST_F(TransientWindowsTest, RestackUponAddOrRemoveTransientWindow) { + TestServerWindowDelegate delegate; + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> windows[4]; + for (int i = 0; i < 4; i++) + windows[i].reset(CreateTestWindow(&delegate, WindowId(0, i), parent.get())); + + EXPECT_EQ("0 1 2 3", ChildWindowIDsAsString(parent.get())); + + windows[0]->AddTransientWindow(windows[2].get()); + EXPECT_EQ("0 2 1 3", ChildWindowIDsAsString(parent.get())); + + windows[0]->AddTransientWindow(windows[3].get()); + EXPECT_EQ("0 2 3 1", ChildWindowIDsAsString(parent.get())); + + windows[0]->RemoveTransientWindow(windows[2].get()); + EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get())); + + windows[0]->RemoveTransientWindow(windows[3].get()); + EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get())); +} + +// Verifies TransientWindowObserver is notified appropriately. +TEST_F(TransientWindowsTest, TransientWindowObserverNotified) { + TestServerWindowDelegate delegate; + std::unique_ptr<ServerWindow> parent( + CreateTestWindow(&delegate, WindowId(), nullptr)); + std::unique_ptr<ServerWindow> w1( + CreateTestWindow(&delegate, WindowId(0, 1), parent.get())); + + TestTransientWindowObserver test_observer; + parent->AddObserver(&test_observer); + + parent->AddTransientWindow(w1.get()); + EXPECT_EQ(1, test_observer.add_count()); + EXPECT_EQ(0, test_observer.remove_count()); + + parent->RemoveTransientWindow(w1.get()); + EXPECT_EQ(1, test_observer.add_count()); + EXPECT_EQ(1, test_observer.remove_count()); + + parent->RemoveObserver(&test_observer); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_activity_monitor.cc b/chromium/components/mus/ws/user_activity_monitor.cc new file mode 100644 index 00000000000..3cd66419a27 --- /dev/null +++ b/chromium/components/mus/ws/user_activity_monitor.cc @@ -0,0 +1,134 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/user_activity_monitor.h" + +#include "base/bind.h" +#include "base/time/default_tick_clock.h" + +namespace mus { +namespace ws { + +UserActivityMonitor::UserActivityMonitor(std::unique_ptr<base::TickClock> clock) + : now_clock_(std::move(clock)) { + if (!now_clock_) + now_clock_ = base::WrapUnique(new base::DefaultTickClock); + last_activity_ = now_clock_->NowTicks(); +} + +UserActivityMonitor::~UserActivityMonitor() {} + +void UserActivityMonitor::OnUserActivity() { + base::TimeTicks now = now_clock_->NowTicks(); + for (auto& pair : activity_observers_) { + ActivityObserverInfo* info = &(pair.first); + if (info->last_activity_notification.is_null() || + (now - info->last_activity_notification) > info->delay) { + pair.second->OnUserActivity(); + info->last_activity_notification = now; + } + } + + // Wake up all the 'idle' observers. + for (auto& pair : idle_observers_) { + IdleObserverInfo* info = &(pair.first); + if (info->idle_state == mojom::UserIdleObserver::IdleState::ACTIVE) + continue; + info->last_idle_state_notification = now; + info->idle_state = mojom::UserIdleObserver::IdleState::ACTIVE; + pair.second->OnUserIdleStateChanged(info->idle_state); + } + + last_activity_ = now; + + // Start the timer if there are some idle observers. + if (idle_timer_.IsRunning()) + idle_timer_.Reset(); +} + +void UserActivityMonitor::Add(mojom::UserActivityMonitorRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +void UserActivityMonitor::AddUserActivityObserver( + uint32_t delay_between_notify_secs, + mojom::UserActivityObserverPtr observer) { + ActivityObserverInfo info; + info.delay = base::TimeDelta::FromSeconds(delay_between_notify_secs); + observer.set_connection_error_handler( + base::Bind(&UserActivityMonitor::OnActivityObserverDisconnected, + base::Unretained(this), observer.get())); + activity_observers_.push_back(std::make_pair(info, std::move(observer))); +} + +void UserActivityMonitor::AddUserIdleObserver( + uint32_t idleness_in_minutes, + mojom::UserIdleObserverPtr observer) { + IdleObserverInfo info; + info.idle_duration = base::TimeDelta::FromMinutes(idleness_in_minutes); + base::TimeTicks now = now_clock_->NowTicks(); + DCHECK(!last_activity_.is_null()); + bool user_is_active = (now - last_activity_ < info.idle_duration); + info.idle_state = user_is_active ? mojom::UserIdleObserver::IdleState::ACTIVE + : mojom::UserIdleObserver::IdleState::IDLE; + info.last_idle_state_notification = now; + observer->OnUserIdleStateChanged(info.idle_state); + observer.set_connection_error_handler( + base::Bind(&UserActivityMonitor::OnIdleObserverDisconnected, + base::Unretained(this), observer.get())); + idle_observers_.push_back(std::make_pair(info, std::move(observer))); + if (user_is_active) + ActivateIdleTimer(); +} + +void UserActivityMonitor::ActivateIdleTimer() { + if (idle_timer_.IsRunning()) + return; + idle_timer_.Start(FROM_HERE, base::TimeDelta::FromMinutes(1), this, + &UserActivityMonitor::OnMinuteTimer); +} + +void UserActivityMonitor::OnMinuteTimer() { + base::TimeTicks now = now_clock_->NowTicks(); + bool active_observer = false; + for (auto& pair : idle_observers_) { + IdleObserverInfo* info = &(pair.first); + if (info->idle_state == mojom::UserIdleObserver::IdleState::IDLE) + continue; + if (now - info->last_idle_state_notification < info->idle_duration) { + active_observer = true; + continue; + } + info->last_idle_state_notification = now; + info->idle_state = mojom::UserIdleObserver::IdleState::IDLE; + pair.second->OnUserIdleStateChanged(info->idle_state); + } + // All observers are already notified of IDLE. No point running the timer + // anymore. + if (!active_observer) + idle_timer_.Stop(); +} + +void UserActivityMonitor::OnActivityObserverDisconnected( + mojom::UserActivityObserver* observer) { + activity_observers_.erase(std::remove_if( + activity_observers_.begin(), activity_observers_.end(), + [observer](const std::pair<ActivityObserverInfo, + mojom::UserActivityObserverPtr>& pair) { + return pair.second.get() == observer; + })); +} + +void UserActivityMonitor::OnIdleObserverDisconnected( + mojom::UserIdleObserver* observer) { + idle_observers_.erase(std::remove_if( + idle_observers_.begin(), idle_observers_.end(), + [observer]( + const std::pair<IdleObserverInfo, mojom::UserIdleObserverPtr>& pair) { + return pair.second.get() == observer; + })); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_activity_monitor.h b/chromium/components/mus/ws/user_activity_monitor.h new file mode 100644 index 00000000000..f490a6dfdfb --- /dev/null +++ b/chromium/components/mus/ws/user_activity_monitor.h @@ -0,0 +1,82 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_USER_ACTIVITY_MONITOR_H_ +#define COMPONENTS_MUS_WS_USER_ACTIVITY_MONITOR_H_ + +#include "base/time/tick_clock.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "components/mus/public/interfaces/user_activity_monitor.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_ptr_set.h" + +namespace mus { +namespace ws { + +namespace test { +class UserActivityMonitorTestApi; +} + +class UserActivityMonitor : public mojom::UserActivityMonitor { + public: + // |now_clock| is used to get the timestamp. If |now_clock| is nullptr, then + // DefaultTickClock is used. + explicit UserActivityMonitor(std::unique_ptr<base::TickClock> now_clock); + ~UserActivityMonitor() override; + + // Should be called whenever some input event is received from the user. + void OnUserActivity(); + + // Provides an implementation for the remote request. + void Add(mojom::UserActivityMonitorRequest request); + + // mojom::UserActivityMonitor: + void AddUserActivityObserver( + uint32_t delay_between_notify_secs, + mojom::UserActivityObserverPtr observer) override; + void AddUserIdleObserver(uint32_t idleness_in_minutes, + mojom::UserIdleObserverPtr observer) override; + + private: + friend class test::UserActivityMonitorTestApi; + + // Makes sure the idle timer is running. + void ActivateIdleTimer(); + + // Called every minute when |idle_timer_| is active. + void OnMinuteTimer(); + + void OnActivityObserverDisconnected(mojom::UserActivityObserver* observer); + void OnIdleObserverDisconnected(mojom::UserIdleObserver* observer); + + mojo::BindingSet<mojom::UserActivityMonitor> bindings_; + std::unique_ptr<base::TickClock> now_clock_; + + struct ActivityObserverInfo { + base::TimeTicks last_activity_notification; + base::TimeDelta delay; + }; + std::vector<std::pair<ActivityObserverInfo, mojom::UserActivityObserverPtr>> + activity_observers_; + + struct IdleObserverInfo { + base::TimeTicks last_idle_state_notification; + base::TimeDelta idle_duration; + mojom::UserIdleObserver::IdleState idle_state; + }; + std::vector<std::pair<IdleObserverInfo, mojom::UserIdleObserverPtr>> + idle_observers_; + // Timer used to determine user's idleness. The timer is run only when at + // least one of the idle-observers are notified ACTIVE. + base::RepeatingTimer idle_timer_; + base::TimeTicks last_activity_; + + DISALLOW_COPY_AND_ASSIGN(UserActivityMonitor); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_USER_ACTIVITY_MONITOR_H_ diff --git a/chromium/components/mus/ws/user_activity_monitor_unittest.cc b/chromium/components/mus/ws/user_activity_monitor_unittest.cc new file mode 100644 index 00000000000..6c26ead8608 --- /dev/null +++ b/chromium/components/mus/ws/user_activity_monitor_unittest.cc @@ -0,0 +1,218 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/user_activity_monitor.h" + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" +#include "base/test/test_mock_time_task_runner.h" +#include "components/mus/ws/test_utils.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "testing/gtest/include/gtest/gtest.h" + +using mus::mojom::UserIdleObserver; + +namespace mus { +namespace ws { +namespace test { + +class TestUserActivityObserver : public mojom::UserActivityObserver { + public: + TestUserActivityObserver() : binding_(this) {} + ~TestUserActivityObserver() override {} + + mojom::UserActivityObserverPtr GetPtr() { + return binding_.CreateInterfacePtrAndBind(); + } + + bool GetAndResetReceivedUserActivity() { + bool val = received_user_activity_; + received_user_activity_ = false; + return val; + } + + private: + // mojom::UserActivityObserver: + void OnUserActivity() override { received_user_activity_ = true; } + + mojo::Binding<mojom::UserActivityObserver> binding_; + bool received_user_activity_ = false; + + DISALLOW_COPY_AND_ASSIGN(TestUserActivityObserver); +}; + +class TestUserIdleObserver : public mojom::UserIdleObserver { + public: + TestUserIdleObserver() : binding_(this) {} + ~TestUserIdleObserver() override {} + + mojom::UserIdleObserverPtr GetPtr() { + return binding_.CreateInterfacePtrAndBind(); + } + + bool GetAndResetIdleState(UserIdleObserver::IdleState* state) { + if (!received_idle_state_) + return false; + received_idle_state_ = false; + *state = idle_state_; + return true; + } + + private: + // mojom::UserIdleObserver: + void OnUserIdleStateChanged(UserIdleObserver::IdleState new_state) override { + received_idle_state_ = true; + idle_state_ = new_state; + } + + mojo::Binding<mojom::UserIdleObserver> binding_; + bool received_idle_state_ = false; + UserIdleObserver::IdleState idle_state_ = UserIdleObserver::IdleState::ACTIVE; + + DISALLOW_COPY_AND_ASSIGN(TestUserIdleObserver); +}; + +class UserActivityMonitorTest : public testing::Test { + public: + UserActivityMonitorTest() {} + ~UserActivityMonitorTest() override {} + + UserActivityMonitor* monitor() { return monitor_.get(); } + + void FastForwardBy(base::TimeDelta delta) { + task_runner_->FastForwardBy(delta); + } + void RunUntilIdle() { task_runner_->RunUntilIdle(); } + + private: + // testing::Test: + void SetUp() override { + task_runner_ = make_scoped_refptr(new base::TestMockTimeTaskRunner( + base::Time::Now(), base::TimeTicks::Now())); + message_loop_.SetTaskRunner(task_runner_); + monitor_.reset(new UserActivityMonitor(task_runner_->GetMockTickClock())); + } + + base::MessageLoop message_loop_; + scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; + std::unique_ptr<UserActivityMonitor> monitor_; + + DISALLOW_COPY_AND_ASSIGN(UserActivityMonitorTest); +}; + +TEST_F(UserActivityMonitorTest, UserActivityObserver) { + TestUserActivityObserver first_observer, second_observer; + monitor()->AddUserActivityObserver(3, first_observer.GetPtr()); + monitor()->AddUserActivityObserver(4, second_observer.GetPtr()); + + // The first activity should notify both observers. + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_TRUE(first_observer.GetAndResetReceivedUserActivity()); + EXPECT_TRUE(second_observer.GetAndResetReceivedUserActivity()); + + // The next activity after just one second should not notify either observer. + FastForwardBy(base::TimeDelta::FromSeconds(1)); + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_FALSE(first_observer.GetAndResetReceivedUserActivity()); + EXPECT_FALSE(second_observer.GetAndResetReceivedUserActivity()); + + FastForwardBy(base::TimeDelta::FromMilliseconds(2001)); + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_TRUE(first_observer.GetAndResetReceivedUserActivity()); + EXPECT_FALSE(second_observer.GetAndResetReceivedUserActivity()); + + FastForwardBy(base::TimeDelta::FromSeconds(1)); + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_FALSE(first_observer.GetAndResetReceivedUserActivity()); + EXPECT_TRUE(second_observer.GetAndResetReceivedUserActivity()); +} + +// Tests that idleness observers receive the correct notification upon +// connection. +TEST_F(UserActivityMonitorTest, UserIdleObserverConnectNotification) { + UserIdleObserver::IdleState idle_state; + + // If an observer is added without any user activity, then it still receives + // an ACTIVE notification immediately. + TestUserIdleObserver first_observer; + monitor()->AddUserIdleObserver(1, first_observer.GetPtr()); + RunUntilIdle(); + EXPECT_TRUE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + + // If an observer is added without any user activity and the system has been + // idle, then the observer receives an IDLE notification immediately. + FastForwardBy(base::TimeDelta::FromMinutes(5)); + TestUserIdleObserver second_observer; + monitor()->AddUserIdleObserver(4, second_observer.GetPtr()); + RunUntilIdle(); + EXPECT_TRUE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + EXPECT_TRUE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + + // If an observer is added after some user activity, then it receives an + // immediate ACTIVE notification. + monitor()->OnUserActivity(); + TestUserIdleObserver third_observer; + monitor()->AddUserIdleObserver(1, third_observer.GetPtr()); + RunUntilIdle(); + EXPECT_TRUE(third_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + EXPECT_TRUE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + EXPECT_TRUE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + + FastForwardBy(base::TimeDelta::FromMinutes(10)); + TestUserIdleObserver fourth_observer; + monitor()->AddUserIdleObserver(1, fourth_observer.GetPtr()); + RunUntilIdle(); + EXPECT_TRUE(fourth_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + EXPECT_TRUE(third_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + EXPECT_TRUE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + EXPECT_TRUE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::IDLE, idle_state); + + // All observers are idle. These should not receive any IDLE notifications as + // more time passes by. + FastForwardBy(base::TimeDelta::FromMinutes(100)); + RunUntilIdle(); + EXPECT_FALSE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(third_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(fourth_observer.GetAndResetIdleState(&idle_state)); + + // Some activity would notify ACTIVE to all observers. + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_TRUE(fourth_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + EXPECT_TRUE(third_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + EXPECT_TRUE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + EXPECT_TRUE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_EQ(UserIdleObserver::IdleState::ACTIVE, idle_state); + + // Yet more activity should not send any notifications. + monitor()->OnUserActivity(); + RunUntilIdle(); + EXPECT_FALSE(first_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(second_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(third_observer.GetAndResetIdleState(&idle_state)); + EXPECT_FALSE(fourth_observer.GetAndResetIdleState(&idle_state)); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_display_manager.cc b/chromium/components/mus/ws/user_display_manager.cc new file mode 100644 index 00000000000..92aeac675fc --- /dev/null +++ b/chromium/components/mus/ws/user_display_manager.cc @@ -0,0 +1,151 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/user_display_manager.h" + +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/display_manager_delegate.h" + +namespace mus { +namespace ws { + +UserDisplayManager::UserDisplayManager(ws::DisplayManager* display_manager, + DisplayManagerDelegate* delegate, + const UserId& user_id) + : display_manager_(display_manager), + delegate_(delegate), + user_id_(user_id), + got_valid_frame_decorations_( + delegate->GetFrameDecorationsForUser(user_id, nullptr)), + current_cursor_location_(0) {} + +UserDisplayManager::~UserDisplayManager() {} + +void UserDisplayManager::OnFrameDecorationValuesChanged() { + if (!got_valid_frame_decorations_) { + got_valid_frame_decorations_ = true; + display_manager_observers_.ForAllPtrs([this]( + mojom::DisplayManagerObserver* observer) { CallOnDisplays(observer); }); + if (test_observer_) + CallOnDisplays(test_observer_); + return; + } + + mojo::Array<mojom::DisplayPtr> displays = GetAllDisplays(); + display_manager_observers_.ForAllPtrs( + [this, &displays](mojom::DisplayManagerObserver* observer) { + observer->OnDisplaysChanged(displays.Clone()); + }); + if (test_observer_) + test_observer_->OnDisplaysChanged(displays.Clone()); +} + +void UserDisplayManager::AddDisplayManagerBinding( + mojo::InterfaceRequest<mojom::DisplayManager> request) { + display_manager_bindings_.AddBinding(this, std::move(request)); +} + +void UserDisplayManager::OnWillDestroyDisplay(Display* display) { + if (!got_valid_frame_decorations_) + return; + + display_manager_observers_.ForAllPtrs( + [this, &display](mojom::DisplayManagerObserver* observer) { + observer->OnDisplayRemoved(display->id()); + }); + if (test_observer_) + test_observer_->OnDisplayRemoved(display->id()); +} + +void UserDisplayManager::OnMouseCursorLocationChanged(const gfx::Point& point) { + current_cursor_location_ = + static_cast<base::subtle::Atomic32>( + (point.x() & 0xFFFF) << 16 | (point.y() & 0xFFFF)); + if (cursor_location_memory()) { + base::subtle::NoBarrier_Store(cursor_location_memory(), + current_cursor_location_); + } +} + +void UserDisplayManager::OnDisplayUpdate(Display* display) { + if (!got_valid_frame_decorations_) + return; + + mojo::Array<mojom::DisplayPtr> displays(1); + displays[0] = display->ToMojomDisplay(); + delegate_->GetFrameDecorationsForUser( + user_id_, &(displays[0]->frame_decoration_values)); + display_manager_observers_.ForAllPtrs( + [this, &displays](mojom::DisplayManagerObserver* observer) { + observer->OnDisplaysChanged(displays.Clone()); + }); + if (test_observer_) + test_observer_->OnDisplaysChanged(displays.Clone()); +} + +mojo::ScopedSharedBufferHandle UserDisplayManager::GetCursorLocationMemory() { + if (!cursor_location_handle_.is_valid()) { + // Create our shared memory segment to share the cursor state with our + // window clients. + cursor_location_handle_ = + mojo::SharedBufferHandle::Create(sizeof(base::subtle::Atomic32)); + + if (!cursor_location_handle_.is_valid()) + return mojo::ScopedSharedBufferHandle(); + + cursor_location_mapping_ = + cursor_location_handle_->Map(sizeof(base::subtle::Atomic32)); + if (!cursor_location_mapping_) + return mojo::ScopedSharedBufferHandle(); + base::subtle::NoBarrier_Store(cursor_location_memory(), + current_cursor_location_); + } + + return cursor_location_handle_->Clone( + mojo::SharedBufferHandle::AccessMode::READ_ONLY); +} + + +void UserDisplayManager::OnObserverAdded( + mojom::DisplayManagerObserver* observer) { + // Many clients key off the frame decorations to size widgets. Wait for frame + // decorations before notifying so that we don't have to worry about clients + // resizing appropriately. + if (!got_valid_frame_decorations_) + return; + + CallOnDisplays(observer); +} + +mojo::Array<mojom::DisplayPtr> UserDisplayManager::GetAllDisplays() { + const std::set<Display*>& displays = display_manager_->displays(); + mojo::Array<mojom::DisplayPtr> display_ptrs(displays.size()); + { + size_t i = 0; + // TODO(sky): need ordering! + for (Display* display : displays) { + display_ptrs[i] = display->ToMojomDisplay(); + delegate_->GetFrameDecorationsForUser( + user_id_, &(display_ptrs[i]->frame_decoration_values)); + ++i; + } + } + return display_ptrs; +} + +void UserDisplayManager::CallOnDisplays( + mojom::DisplayManagerObserver* observer) { + observer->OnDisplays(GetAllDisplays()); +} + +void UserDisplayManager::AddObserver( + mojom::DisplayManagerObserverPtr observer) { + mojom::DisplayManagerObserver* observer_impl = observer.get(); + display_manager_observers_.AddPtr(std::move(observer)); + OnObserverAdded(observer_impl); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_display_manager.h b/chromium/components/mus/ws/user_display_manager.h new file mode 100644 index 00000000000..1bfcdf81d8c --- /dev/null +++ b/chromium/components/mus/ws/user_display_manager.h @@ -0,0 +1,118 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_USER_DISPLAY_MANAGER_H_ +#define COMPONENTS_MUS_WS_USER_DISPLAY_MANAGER_H_ + +#include <set> + +#include "base/atomicops.h" +#include "base/macros.h" +#include "components/mus/public/interfaces/display.mojom.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_ptr_set.h" + +namespace gfx { +class Point; +} + +namespace mus { +namespace ws { + +class Display; +class DisplayManager; +class DisplayManagerDelegate; + +namespace test { +class UserDisplayManagerTestApi; +} + +// Provides per user display state. +class UserDisplayManager : public mojom::DisplayManager { + public: + UserDisplayManager(ws::DisplayManager* display_manager, + DisplayManagerDelegate* delegate, + const UserId& user_id); + ~UserDisplayManager() override; + + // Called when the frame decorations for this user change. + void OnFrameDecorationValuesChanged(); + + void AddDisplayManagerBinding( + mojo::InterfaceRequest<mojom::DisplayManager> request); + + // Called by Display prior to |display| being removed and destroyed. + void OnWillDestroyDisplay(Display* display); + + // Called from WindowManagerState when its EventDispatcher receives a mouse + // event. + void OnMouseCursorLocationChanged(const gfx::Point& point); + + // Called when something about the display (e.g. pixel-ratio, size) changes. + void OnDisplayUpdate(Display* display); + + // Returns a read-only handle to the shared memory which contains the global + // mouse cursor position. Each call returns a new handle. + mojo::ScopedSharedBufferHandle GetCursorLocationMemory(); + + private: + friend class test::UserDisplayManagerTestApi; + + // Called when a new observer is added. If frame decorations are available + // notifies the observer immediately. + void OnObserverAdded(mojom::DisplayManagerObserver* observer); + + mojo::Array<mojom::DisplayPtr> GetAllDisplays(); + + // Calls OnDisplays() on |observer|. + void CallOnDisplays(mojom::DisplayManagerObserver* observer); + + // Overriden from mojom::DisplayManager: + void AddObserver(mojom::DisplayManagerObserverPtr observer) override; + + base::subtle::Atomic32* cursor_location_memory() { + return reinterpret_cast<base::subtle::Atomic32*>( + cursor_location_mapping_.get()); + } + + ws::DisplayManager* display_manager_; + + DisplayManagerDelegate* delegate_; + + const UserId user_id_; + + // Set to true the first time at least one Display has valid frame values. + bool got_valid_frame_decorations_; + + mojo::BindingSet<mojom::DisplayManager> display_manager_bindings_; + + // WARNING: only use these once |got_valid_frame_decorations_| is true. + mojo::InterfacePtrSet<mojom::DisplayManagerObserver> + display_manager_observers_; + + // Observer used for tests. + mojom::DisplayManagerObserver* test_observer_ = nullptr; + + // The current location of the cursor. This is always kept up to date so we + // can atomically write this to |cursor_location_memory()| once it is created. + base::subtle::Atomic32 current_cursor_location_; + + // A handle to a shared memory buffer that is one 64 bit integer long. We + // share this with any client as the same user. This buffer is lazily + // created on the first access. + mojo::ScopedSharedBufferHandle cursor_location_handle_; + + // The one int32 in |cursor_location_handle_|. When we write to this + // location, we must always write to it atomically. (On the other side of the + // mojo connection, this data must be read atomically.) + mojo::ScopedSharedBufferMapping cursor_location_mapping_; + + DISALLOW_COPY_AND_ASSIGN(UserDisplayManager); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_USER_DISPLAY_MANAGER_H_ diff --git a/chromium/components/mus/ws/user_display_manager_unittest.cc b/chromium/components/mus/ws/user_display_manager_unittest.cc new file mode 100644 index 00000000000..2c2506cce26 --- /dev/null +++ b/chromium/components/mus/ws/user_display_manager_unittest.cc @@ -0,0 +1,244 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <string> + +#include "base/atomicops.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "components/mus/common/types.h" +#include "components/mus/common/util.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/test_utils.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_manager_window_tree_factory_set.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { +namespace ws { +namespace test { +namespace { + +class TestDisplayManagerObserver : public mojom::DisplayManagerObserver { + public: + TestDisplayManagerObserver() {} + ~TestDisplayManagerObserver() override {} + + std::string GetAndClearObserverCalls() { + std::string result; + std::swap(observer_calls_, result); + return result; + } + + private: + void AddCall(const std::string& call) { + if (!observer_calls_.empty()) + observer_calls_ += "\n"; + observer_calls_ += call; + } + + std::string DisplayIdsToString( + const mojo::Array<mojom::DisplayPtr>& displays) { + std::string display_ids; + for (const auto& display : displays) { + if (!display_ids.empty()) + display_ids += " "; + display_ids += base::Int64ToString(display->id); + } + return display_ids; + } + + // mojom::DisplayManagerObserver: + void OnDisplays(mojo::Array<mojom::DisplayPtr> displays) override { + AddCall("OnDisplays " + DisplayIdsToString(displays)); + } + void OnDisplaysChanged(mojo::Array<mojom::DisplayPtr> displays) override { + AddCall("OnDisplaysChanged " + DisplayIdsToString(displays)); + } + void OnDisplayRemoved(int64_t id) override { + AddCall("OnDisplayRemoved " + base::Int64ToString(id)); + } + + std::string observer_calls_; + + DISALLOW_COPY_AND_ASSIGN(TestDisplayManagerObserver); +}; + +mojom::FrameDecorationValuesPtr CreateDefaultFrameDecorationValues() { + return mojom::FrameDecorationValues::New(); +} + +} // namespace + +// ----------------------------------------------------------------------------- + +class UserDisplayManagerTest : public testing::Test { + public: + UserDisplayManagerTest() + : cursor_id_(0), platform_display_factory_(&cursor_id_) {} + ~UserDisplayManagerTest() override {} + + protected: + // testing::Test: + void SetUp() override { + PlatformDisplay::set_factory_for_testing(&platform_display_factory_); + window_server_.reset(new WindowServer(&window_server_delegate_, + scoped_refptr<SurfacesState>())); + window_server_delegate_.set_window_server(window_server_.get()); + } + + protected: + int32_t cursor_id_; + TestPlatformDisplayFactory platform_display_factory_; + TestWindowServerDelegate window_server_delegate_; + std::unique_ptr<WindowServer> window_server_; + base::MessageLoop message_loop_; + + private: + DISALLOW_COPY_AND_ASSIGN(UserDisplayManagerTest); +}; + +TEST_F(UserDisplayManagerTest, OnlyNotifyWhenFrameDecorationsSet) { + window_server_delegate_.set_num_displays_to_create(1); + + const UserId kUserId1 = "2"; + TestDisplayManagerObserver display_manager_observer1; + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kUserId1); + UserDisplayManager* user_display_manager1 = + display_manager->GetUserDisplayManager(kUserId1); + ASSERT_TRUE(user_display_manager1); + UserDisplayManagerTestApi(user_display_manager1) + .SetTestObserver(&display_manager_observer1); + // Observer should not have been notified yet. + EXPECT_EQ(std::string(), + display_manager_observer1.GetAndClearObserverCalls()); + + // Set the frame decoration values, which should trigger sending immediately. + ASSERT_EQ(1u, display_manager->displays().size()); + window_server_->window_manager_window_tree_factory_set() + ->GetWindowManagerStateForUser(kUserId1) + ->SetFrameDecorationValues(CreateDefaultFrameDecorationValues()); + EXPECT_EQ("OnDisplays 1", + display_manager_observer1.GetAndClearObserverCalls()); + + UserDisplayManagerTestApi(user_display_manager1).SetTestObserver(nullptr); +} + +TEST_F(UserDisplayManagerTest, AddObserverAfterFrameDecorationsSet) { + window_server_delegate_.set_num_displays_to_create(1); + + const UserId kUserId1 = "2"; + TestDisplayManagerObserver display_manager_observer1; + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kUserId1); + UserDisplayManager* user_display_manager1 = + display_manager->GetUserDisplayManager(kUserId1); + ASSERT_TRUE(user_display_manager1); + ASSERT_EQ(1u, display_manager->displays().size()); + window_server_->window_manager_window_tree_factory_set() + ->GetWindowManagerStateForUser(kUserId1) + ->SetFrameDecorationValues(CreateDefaultFrameDecorationValues()); + + UserDisplayManagerTestApi(user_display_manager1) + .SetTestObserver(&display_manager_observer1); + EXPECT_EQ("OnDisplays 1", + display_manager_observer1.GetAndClearObserverCalls()); + + UserDisplayManagerTestApi(user_display_manager1).SetTestObserver(nullptr); +} + +TEST_F(UserDisplayManagerTest, AddRemoveDisplay) { + window_server_delegate_.set_num_displays_to_create(1); + + const UserId kUserId1 = "2"; + TestDisplayManagerObserver display_manager_observer1; + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kUserId1); + UserDisplayManager* user_display_manager1 = + display_manager->GetUserDisplayManager(kUserId1); + ASSERT_TRUE(user_display_manager1); + ASSERT_EQ(1u, display_manager->displays().size()); + window_server_->window_manager_window_tree_factory_set() + ->GetWindowManagerStateForUser(kUserId1) + ->SetFrameDecorationValues(CreateDefaultFrameDecorationValues()); + UserDisplayManagerTestApi(user_display_manager1) + .SetTestObserver(&display_manager_observer1); + EXPECT_EQ("OnDisplays 1", + display_manager_observer1.GetAndClearObserverCalls()); + + // Add another display. + Display* display2 = + new Display(window_server_.get(), PlatformDisplayInitParams()); + display2->Init(nullptr); + + // Observer should be notified immediately as frame decorations were set. + EXPECT_EQ("OnDisplaysChanged 2", + display_manager_observer1.GetAndClearObserverCalls()); + + // Remove the display and verify observer is notified. + display_manager->DestroyDisplay(display2); + display2 = nullptr; + EXPECT_EQ("OnDisplayRemoved 2", + display_manager_observer1.GetAndClearObserverCalls()); + + UserDisplayManagerTestApi(user_display_manager1).SetTestObserver(nullptr); +} + +TEST_F(UserDisplayManagerTest, NegativeCoordinates) { + window_server_delegate_.set_num_displays_to_create(1); + + const UserId kUserId1 = "2"; + TestDisplayManagerObserver display_manager_observer1; + DisplayManager* display_manager = window_server_->display_manager(); + WindowManagerWindowTreeFactorySetTestApi( + window_server_->window_manager_window_tree_factory_set()) + .Add(kUserId1); + UserDisplayManager* user_display_manager1 = + display_manager->GetUserDisplayManager(kUserId1); + ASSERT_TRUE(user_display_manager1); + + user_display_manager1->OnMouseCursorLocationChanged(gfx::Point(-10, -11)); + + base::subtle::Atomic32* cursor_location_memory = nullptr; + mojo::ScopedSharedBufferHandle handle = + user_display_manager1->GetCursorLocationMemory(); + mojo::ScopedSharedBufferMapping cursor_location_mapping = + handle->Map(sizeof(base::subtle::Atomic32)); + ASSERT_TRUE(cursor_location_mapping); + cursor_location_memory = + reinterpret_cast<base::subtle::Atomic32*>(cursor_location_mapping.get()); + + base::subtle::Atomic32 location = + base::subtle::NoBarrier_Load(cursor_location_memory); + EXPECT_EQ(gfx::Point(static_cast<int16_t>(location >> 16), + static_cast<int16_t>(location & 0xFFFF)), + gfx::Point(-10, -11)); +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_id.h b/chromium/components/mus/ws/user_id.h new file mode 100644 index 00000000000..dbb59965487 --- /dev/null +++ b/chromium/components/mus/ws/user_id.h @@ -0,0 +1,22 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_USER_ID_H_ +#define COMPONENTS_MUS_WS_USER_ID_H_ + +#include <string> + +namespace mus { +namespace ws { + +using UserId = std::string; + +inline UserId InvalidUserId() { + return std::string(); +} + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_USER_ID_H_ diff --git a/chromium/components/mus/ws/user_id_tracker.cc b/chromium/components/mus/ws/user_id_tracker.cc new file mode 100644 index 00000000000..40a7dde987b --- /dev/null +++ b/chromium/components/mus/ws/user_id_tracker.cc @@ -0,0 +1,68 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/user_id_tracker.h" + +#include "components/mus/ws/user_id_tracker_observer.h" +#include "services/shell/public/interfaces/connector.mojom.h" + +namespace mus { +namespace ws { + +UserIdTracker::UserIdTracker() : active_id_(shell::mojom::kRootUserID) { + ids_.insert(active_id_); +} + +UserIdTracker::~UserIdTracker() { +} + +bool UserIdTracker::IsValidUserId(const UserId& id) const { + return ids_.count(id) > 0; +} + +void UserIdTracker::SetActiveUserId(const UserId& id) { + DCHECK(IsValidUserId(id)); + if (id == active_id_) + return; + + const UserId previously_active_id = active_id_; + active_id_ = id; + FOR_EACH_OBSERVER(UserIdTrackerObserver, observers_, + OnActiveUserIdChanged(previously_active_id, id)); +} + +void UserIdTracker::AddUserId(const UserId& id) { + if (IsValidUserId(id)) + return; + + ids_.insert(id); + FOR_EACH_OBSERVER(UserIdTrackerObserver, observers_, OnUserIdAdded(id)); +} + +void UserIdTracker::RemoveUserId(const UserId& id) { + DCHECK(IsValidUserId(id)); + ids_.erase(id); + FOR_EACH_OBSERVER(UserIdTrackerObserver, observers_, OnUserIdRemoved(id)); +} + +void UserIdTracker::AddObserver(UserIdTrackerObserver* observer) { + observers_.AddObserver(observer); +} + +void UserIdTracker::RemoveObserver(UserIdTrackerObserver* observer) { + observers_.RemoveObserver(observer); +} + +void UserIdTracker::Bind(mojom::UserAccessManagerRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +void UserIdTracker::SetActiveUser(const mojo::String& user_id) { + if (!IsValidUserId(user_id)) + AddUserId(user_id); + SetActiveUserId(user_id); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/user_id_tracker.h b/chromium/components/mus/ws/user_id_tracker.h new file mode 100644 index 00000000000..c0b2ca54407 --- /dev/null +++ b/chromium/components/mus/ws/user_id_tracker.h @@ -0,0 +1,62 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_USER_ID_TRACKER_H_ +#define COMPONENTS_MUS_WS_USER_ID_TRACKER_H_ + +#include <stdint.h> + +#include <set> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/public/interfaces/user_access_manager.mojom.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +namespace mus { +namespace ws { + +class UserIdTrackerObserver; + +// Tracks the set of known/valid user ids. +class UserIdTracker : public mojom::UserAccessManager { + public: + UserIdTracker(); + ~UserIdTracker() override; + + bool IsValidUserId(const UserId& id) const; + + const UserId& active_id() const { return active_id_; } + + // Adds a new known user. Does nothing if |id| is not known. + void SetActiveUserId(const UserId& id); + void AddUserId(const UserId& id); + void RemoveUserId(const UserId& id); + + void AddObserver(UserIdTrackerObserver* observer); + void RemoveObserver(UserIdTrackerObserver* observer); + + void Bind(mojom::UserAccessManagerRequest request); + + private: + using UserIdSet = std::set<UserId>; + + // mojom::UserAccessManager: + void SetActiveUser(const mojo::String& user_id) override; + + base::ObserverList<UserIdTrackerObserver> observers_; + + UserIdSet ids_; + UserId active_id_; + + mojo::BindingSet<mojom::UserAccessManager> bindings_; + + DISALLOW_COPY_AND_ASSIGN(UserIdTracker); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_USER_ID_TRACKER_H_ diff --git a/chromium/components/mus/ws/user_id_tracker_observer.h b/chromium/components/mus/ws/user_id_tracker_observer.h new file mode 100644 index 00000000000..e770ac3de23 --- /dev/null +++ b/chromium/components/mus/ws/user_id_tracker_observer.h @@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_USER_ID_TRACKER_OBSERVER_H_ +#define COMPONENTS_MUS_WS_USER_ID_TRACKER_OBSERVER_H_ + +#include <stdint.h> + +#include "components/mus/ws/user_id.h" + +namespace mus { +namespace ws { + +class UserIdTrackerObserver { + public: + virtual void OnActiveUserIdChanged(const UserId& previously_active_id, + const UserId& active_id) {} + virtual void OnUserIdAdded(const UserId& id) {} + virtual void OnUserIdRemoved(const UserId& id) {} + + protected: + virtual ~UserIdTrackerObserver() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_USER_ID_TRACKER_OBSERVER_H_ diff --git a/chromium/components/mus/ws/window_coordinate_conversions.cc b/chromium/components/mus/ws/window_coordinate_conversions.cc new file mode 100644 index 00000000000..81530fa820e --- /dev/null +++ b/chromium/components/mus/ws/window_coordinate_conversions.cc @@ -0,0 +1,74 @@ +// 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 "components/mus/ws/window_coordinate_conversions.h" + +#include "components/mus/ws/server_window.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace mus { + +namespace ws { + +namespace { + +gfx::Vector2dF CalculateOffsetToAncestor(const ServerWindow* window, + const ServerWindow* ancestor) { + DCHECK(ancestor->Contains(window)); + gfx::Vector2d result; + for (const ServerWindow* v = window; v != ancestor; v = v->parent()) + result += v->bounds().OffsetFromOrigin(); + return gfx::Vector2dF(result.x(), result.y()); +} + +} // namespace + +gfx::Point ConvertPointBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::Point& point) { + return gfx::ToFlooredPoint( + ConvertPointFBetweenWindows(from, to, gfx::PointF(point.x(), point.y()))); +} + +gfx::PointF ConvertPointFBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::PointF& point) { + DCHECK(from); + DCHECK(to); + if (from == to) + return point; + + if (from->Contains(to)) { + const gfx::Vector2dF offset(CalculateOffsetToAncestor(to, from)); + return point - offset; + } + DCHECK(to->Contains(from)); + const gfx::Vector2dF offset(CalculateOffsetToAncestor(from, to)); + return point + offset; +} + +gfx::Rect ConvertRectBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::Rect& rect) { + DCHECK(from); + DCHECK(to); + if (from == to) + return rect; + + const gfx::Point top_left( + ConvertPointBetweenWindows(from, to, rect.origin())); + const gfx::Point bottom_right(gfx::ToCeiledPoint(ConvertPointFBetweenWindows( + from, to, gfx::PointF(rect.right(), rect.bottom())))); + return gfx::Rect(top_left.x(), top_left.y(), bottom_right.x() - top_left.x(), + bottom_right.y() - top_left.y()); +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/window_coordinate_conversions.h b/chromium/components/mus/ws/window_coordinate_conversions.h new file mode 100644 index 00000000000..39601590a80 --- /dev/null +++ b/chromium/components/mus/ws/window_coordinate_conversions.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 COMPONENTS_MUS_WS_WINDOW_COORDINATE_CONVERSIONS_H_ +#define COMPONENTS_MUS_WS_WINDOW_COORDINATE_CONVERSIONS_H_ + +namespace gfx { +class Point; +class PointF; +class Rect; +} + +namespace mus { + +namespace ws { + +class ServerWindow; + +// Converts |point| from the coordinates of |from| to the coordinates of |to|. +// |from| and |to| must be an ancestors or descendants of each other. +gfx::Point ConvertPointBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::Point& point); +gfx::PointF ConvertPointFBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::PointF& point); + +// 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 ConvertRectBetweenWindows(const ServerWindow* from, + const ServerWindow* to, + const gfx::Rect& rect); + +} // namespace ws + +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_COORDINATE_CONVERSIONS_H_ diff --git a/chromium/components/mus/ws/window_coordinate_conversions_unittest.cc b/chromium/components/mus/ws/window_coordinate_conversions_unittest.cc new file mode 100644 index 00000000000..8c42c09f6c7 --- /dev/null +++ b/chromium/components/mus/ws/window_coordinate_conversions_unittest.cc @@ -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. + +#include "components/mus/ws/window_coordinate_conversions.h" + +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { + +namespace ws { + +using WindowCoordinateConversionsTest = testing::Test; + +TEST_F(WindowCoordinateConversionsTest, ConvertRectBetweenWindows) { + TestServerWindowDelegate d1, d2, d3; + ServerWindow v1(&d1, WindowId()), v2(&d2, WindowId()), v3(&d3, WindowId()); + 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), + ConvertRectBetweenWindows(&v1, &v3, gfx::Rect(10, 11, 8, 9))); + + EXPECT_EQ(gfx::Rect(18, 21, 8, 9), + ConvertRectBetweenWindows(&v3, &v1, gfx::Rect(10, 11, 8, 9))); +} + +TEST_F(WindowCoordinateConversionsTest, ConvertPointFBetweenWindows) { + TestServerWindowDelegate d1, d2, d3; + ServerWindow v1(&d1, WindowId()), v2(&d2, WindowId()), v3(&d3, WindowId()); + 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); + + { + const gfx::PointF result( + ConvertPointFBetweenWindows(&v1, &v3, gfx::PointF(10.5f, 11.9f))); + EXPECT_FLOAT_EQ(2.5f, result.x()); + EXPECT_FLOAT_EQ(1.9f, result.y()); + } + + { + const gfx::PointF result( + ConvertPointFBetweenWindows(&v3, &v1, gfx::PointF(10.2f, 11.4f))); + EXPECT_FLOAT_EQ(18.2f, result.x()); + EXPECT_FLOAT_EQ(21.4f, result.y()); + } +} + +} // namespace ws + +} // namespace mus diff --git a/chromium/components/mus/ws/window_finder.cc b/chromium/components/mus/ws/window_finder.cc new file mode 100644 index 00000000000..466bd5a6a8e --- /dev/null +++ b/chromium/components/mus/ws/window_finder.cc @@ -0,0 +1,68 @@ +// 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 "components/mus/ws/window_finder.h" + +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/server_window_surface.h" +#include "components/mus/ws/server_window_surface_manager.h" +#include "components/mus/ws/window_coordinate_conversions.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/transform.h" + +namespace mus { +namespace ws { + +bool IsValidWindowForEvents(ServerWindow* window) { + ServerWindowSurfaceManager* surface_manager = window->surface_manager(); + return surface_manager && + surface_manager->HasSurfaceOfType(mojom::SurfaceType::DEFAULT); +} + +ServerWindow* FindDeepestVisibleWindowForEvents(ServerWindow* window, + gfx::Point* location) { + const ServerWindow::Windows children(window->GetChildren()); + for (auto iter = children.rbegin(); iter != children.rend(); ++iter) { + ServerWindow* child = *iter; + if (!child->visible()) + continue; + + // TODO(sky): support transform. + gfx::Point child_location(location->x() - child->bounds().x(), + location->y() - child->bounds().y()); + gfx::Rect child_bounds(child->bounds().size()); + child_bounds.Inset(-child->extended_hit_test_region().left(), + -child->extended_hit_test_region().top(), + -child->extended_hit_test_region().right(), + -child->extended_hit_test_region().bottom()); + if (!child_bounds.Contains(child_location)) + continue; + + if (child->hit_test_mask() && + !child->hit_test_mask()->Contains(child_location)) + continue; + + *location = child_location; + ServerWindow* result = FindDeepestVisibleWindowForEvents(child, location); + if (IsValidWindowForEvents(result)) + return result; + } + return window; +} + +gfx::Transform GetTransformToWindow(ServerWindow* window) { + gfx::Transform transform; + ServerWindow* current = window; + while (current->parent()) { + transform.Translate(-current->bounds().x(), -current->bounds().y()); + current = current->parent(); + } + return transform; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_finder.h b/chromium/components/mus/ws/window_finder.h new file mode 100644 index 00000000000..2b535fcb35b --- /dev/null +++ b/chromium/components/mus/ws/window_finder.h @@ -0,0 +1,33 @@ +// 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 COMPONENTS_MUS_WS_WINDOW_FINDER_H_ +#define COMPONENTS_MUS_WS_WINDOW_FINDER_H_ + +namespace gfx { +class Point; +class Transform; +} + +namespace mus { +namespace ws { + +class ServerWindow; + +// Find the deepest visible child of |root| that should receive an event at +// |location|. |location| is initially in the coordinate space of +// |root_window|, on return it is converted to the coordinates of the return +// value. +ServerWindow* FindDeepestVisibleWindowForEvents( + ServerWindow* root_window, + gfx::Point* location); + +// Retrieve the transform to the provided |window|'s coordinate space from the +// root. +gfx::Transform GetTransformToWindow(ServerWindow* window); + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_FINDER_H_ diff --git a/chromium/components/mus/ws/window_finder_unittest.cc b/chromium/components/mus/ws/window_finder_unittest.cc new file mode 100644 index 00000000000..f910624ce43 --- /dev/null +++ b/chromium/components/mus/ws/window_finder_unittest.cc @@ -0,0 +1,79 @@ +// 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 "components/mus/ws/window_finder.h" + +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface_manager.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "components/mus/ws/window_finder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mus { +namespace ws { + +TEST(WindowFinderTest, FindDeepestVisibleWindow) { + TestServerWindowDelegate window_delegate; + ServerWindow root(&window_delegate, WindowId(1, 2)); + window_delegate.set_root_window(&root); + root.SetVisible(true); + root.SetBounds(gfx::Rect(0, 0, 100, 100)); + + ServerWindow child1(&window_delegate, WindowId(1, 3)); + root.Add(&child1); + EnableHitTest(&child1); + child1.SetVisible(true); + child1.SetBounds(gfx::Rect(10, 10, 20, 20)); + + ServerWindow child2(&window_delegate, WindowId(1, 4)); + root.Add(&child2); + EnableHitTest(&child2); + child2.SetVisible(true); + child2.SetBounds(gfx::Rect(15, 15, 20, 20)); + + gfx::Point local_point(16, 16); + EXPECT_EQ(&child2, FindDeepestVisibleWindowForEvents(&root, &local_point)); + EXPECT_EQ(gfx::Point(1, 1), local_point); + + local_point.SetPoint(13, 14); + EXPECT_EQ(&child1, FindDeepestVisibleWindowForEvents(&root, &local_point)); + EXPECT_EQ(gfx::Point(3, 4), local_point); + + child2.set_extended_hit_test_region(gfx::Insets(10, 10, 10, 10)); + local_point.SetPoint(13, 14); + EXPECT_EQ(&child2, FindDeepestVisibleWindowForEvents(&root, &local_point)); + EXPECT_EQ(gfx::Point(-2, -1), local_point); +} + +TEST(WindowFinderTest, FindDeepestVisibleWindowHitTestMask) { + TestServerWindowDelegate window_delegate; + ServerWindow root(&window_delegate, WindowId(1, 2)); + window_delegate.set_root_window(&root); + EnableHitTest(&root); + root.SetVisible(true); + root.SetBounds(gfx::Rect(0, 0, 100, 100)); + + ServerWindow child_with_mask(&window_delegate, WindowId(1, 4)); + root.Add(&child_with_mask); + EnableHitTest(&child_with_mask); + child_with_mask.SetVisible(true); + child_with_mask.SetBounds(gfx::Rect(10, 10, 20, 20)); + child_with_mask.SetHitTestMask(gfx::Rect(2, 2, 16, 16)); + + // Test a point inside the window but outside the mask. + gfx::Point point_outside_mask(11, 11); + EXPECT_EQ(&root, + FindDeepestVisibleWindowForEvents(&root, &point_outside_mask)); + EXPECT_EQ(gfx::Point(11, 11), point_outside_mask); + + // Test a point inside the window and inside the mask. + gfx::Point point_inside_mask(15, 15); + EXPECT_EQ(&child_with_mask, + FindDeepestVisibleWindowForEvents(&root, &point_inside_mask)); + EXPECT_EQ(gfx::Point(5, 5), point_inside_mask); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_access_policy.cc b/chromium/components/mus/ws/window_manager_access_policy.cc new file mode 100644 index 00000000000..7682c264383 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_access_policy.cc @@ -0,0 +1,184 @@ +// 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 "components/mus/ws/window_manager_access_policy.h" + +#include "components/mus/ws/access_policy_delegate.h" +#include "components/mus/ws/server_window.h" + +namespace mus { +namespace ws { + +WindowManagerAccessPolicy::WindowManagerAccessPolicy() {} + +WindowManagerAccessPolicy::~WindowManagerAccessPolicy() {} + +void WindowManagerAccessPolicy::Init(ClientSpecificId client_id, + AccessPolicyDelegate* delegate) { + client_id_ = client_id; + delegate_ = delegate; +} + +bool WindowManagerAccessPolicy::CanRemoveWindowFromParent( + const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanAddWindow(const ServerWindow* parent, + const ServerWindow* child) const { + return true; +} + +bool WindowManagerAccessPolicy::CanAddTransientWindow( + const ServerWindow* parent, + const ServerWindow* child) const { + return true; +} + +bool WindowManagerAccessPolicy::CanRemoveTransientWindowFromParent( + const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanSetModal( + const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanReorderWindow( + const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const { + return true; +} + +bool WindowManagerAccessPolicy::CanDeleteWindow( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanGetWindowTree( + const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanDescendIntoWindowForWindowTree( + const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanEmbed(const ServerWindow* window) const { + return !delegate_->HasRootForAccessPolicy(window); +} + +bool WindowManagerAccessPolicy::CanChangeWindowVisibility( + const ServerWindow* window) const { + if (WasCreatedByThisClient(window)) + return true; + // The WindowManager can change the visibility of the WindowManager root. + const ServerWindow* root = window->GetRoot(); + return root && window->parent() == root; +} + +bool WindowManagerAccessPolicy::CanChangeWindowOpacity( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanSetWindowSurface( + const ServerWindow* window, + mus::mojom::SurfaceType surface_type) const { + if (surface_type == mojom::SurfaceType::UNDERLAY) + return WasCreatedByThisClient(window); + + if (delegate_->IsWindowRootOfAnotherTreeForAccessPolicy(window)) + return false; + return WasCreatedByThisClient(window) || + (delegate_->HasRootForAccessPolicy(window)); +} + +bool WindowManagerAccessPolicy::CanSetWindowBounds( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanSetWindowProperties( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanSetWindowTextInputState( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanSetCapture( + const ServerWindow* window) const { + return WasCreatedByThisClient(window); +} + +bool WindowManagerAccessPolicy::CanSetFocus(const ServerWindow* window) const { + return true; +} + +bool WindowManagerAccessPolicy::CanSetClientArea( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool WindowManagerAccessPolicy::CanSetHitTestMask( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool WindowManagerAccessPolicy::CanSetCursorProperties( + const ServerWindow* window) const { + return WasCreatedByThisClient(window) || + delegate_->HasRootForAccessPolicy(window); +} + +bool WindowManagerAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerWindow* window, + const ServerWindow** new_parent, + const ServerWindow** old_parent) const { + // Notify if we've already told the window manager about the window, or if + // we've + // already told the window manager about the parent. The later handles the + // case of a window that wasn't parented to the root getting added to the + // root. + return IsWindowKnown(window) || (*new_parent && IsWindowKnown(*new_parent)); +} + +bool WindowManagerAccessPolicy::CanSetWindowManager() const { + return true; +} + +const ServerWindow* WindowManagerAccessPolicy::GetWindowForFocusChange( + const ServerWindow* focused) { + return focused; +} + +bool WindowManagerAccessPolicy::IsWindowKnown( + const ServerWindow* window) const { + return delegate_->IsWindowKnownForAccessPolicy(window); +} + +bool WindowManagerAccessPolicy::IsValidIdForNewWindow( + const ClientWindowId& id) const { + // The WindowManager see windows created from other clients. If the WM doesn't + // use the client id when creating windows the WM could end up with two + // windows with the same id. Because of this the wm must use the same + // client id for all windows it creates. + return WindowIdFromTransportId(id.id).client_id == client_id_; +} + +bool WindowManagerAccessPolicy::WasCreatedByThisClient( + const ServerWindow* window) const { + return window->id().client_id == client_id_; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_access_policy.h b/chromium/components/mus/ws/window_manager_access_policy.h new file mode 100644 index 00000000000..8b9c6edcec9 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_access_policy.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_ACCESS_POLICY_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_ACCESS_POLICY_H_ + +#include <stdint.h> + +#include "base/macros.h" +#include "components/mus/ws/access_policy.h" + +namespace mus { +namespace ws { + +class AccessPolicyDelegate; + +class WindowManagerAccessPolicy : public AccessPolicy { + public: + WindowManagerAccessPolicy(); + ~WindowManagerAccessPolicy() override; + + // AccessPolicy: + void Init(ClientSpecificId client_id, + AccessPolicyDelegate* delegate) override; + bool CanRemoveWindowFromParent(const ServerWindow* window) const override; + bool CanAddWindow(const ServerWindow* parent, + const ServerWindow* child) const override; + bool CanAddTransientWindow(const ServerWindow* parent, + const ServerWindow* child) const override; + bool CanRemoveTransientWindowFromParent( + const ServerWindow* window) const override; + bool CanSetModal(const ServerWindow* window) const override; + bool CanReorderWindow(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const override; + bool CanDeleteWindow(const ServerWindow* window) const override; + bool CanGetWindowTree(const ServerWindow* window) const override; + bool CanDescendIntoWindowForWindowTree( + const ServerWindow* window) const override; + bool CanEmbed(const ServerWindow* window) const override; + bool CanChangeWindowVisibility(const ServerWindow* window) const override; + bool CanChangeWindowOpacity(const ServerWindow* window) const override; + bool CanSetWindowSurface(const ServerWindow* window, + mus::mojom::SurfaceType surface_type) const override; + bool CanSetWindowBounds(const ServerWindow* window) const override; + bool CanSetWindowProperties(const ServerWindow* window) const override; + bool CanSetWindowTextInputState(const ServerWindow* window) const override; + bool CanSetCapture(const ServerWindow* window) const override; + bool CanSetFocus(const ServerWindow* window) const override; + bool CanSetClientArea(const ServerWindow* window) const override; + bool CanSetHitTestMask(const ServerWindow* window) const override; + bool CanSetCursorProperties(const ServerWindow* window) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerWindow* window, + const ServerWindow** new_parent, + const ServerWindow** old_parent) const override; + const ServerWindow* GetWindowForFocusChange( + const ServerWindow* focused) override; + bool CanSetWindowManager() const override; + bool IsValidIdForNewWindow(const ClientWindowId& id) const override; + + private: + bool IsWindowKnown(const ServerWindow* window) const; + bool WasCreatedByThisClient(const ServerWindow* window) const; + + ClientSpecificId client_id_ = 0u; + AccessPolicyDelegate* delegate_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerAccessPolicy); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_ACCESS_POLICY_H_ diff --git a/chromium/components/mus/ws/window_manager_client_unittest.cc b/chromium/components/mus/ws/window_manager_client_unittest.cc new file mode 100644 index 00000000000..a8049c6b69b --- /dev/null +++ b/chromium/components/mus/ws/window_manager_client_unittest.cc @@ -0,0 +1,1171 @@ +// 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 <stddef.h> +#include <stdint.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "components/mus/common/util.h" +#include "components/mus/public/cpp/lib/window_private.h" +#include "components/mus/public/cpp/tests/window_server_test_base.h" +#include "components/mus/public/cpp/window_observer.h" +#include "components/mus/public/cpp/window_tree_client.h" +#include "components/mus/public/cpp/window_tree_client_delegate.h" +#include "components/mus/public/cpp/window_tree_client_observer.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { +namespace ws { + +namespace { + +Id server_id(mus::Window* window) { + return WindowPrivate(window).server_id(); +} + +mus::Window* GetChildWindowByServerId(WindowTreeClient* client, + uint32_t id) { + return client->GetWindowByServerId(id); +} + +int ValidIndexOf(const Window::Children& windows, Window* window) { + Window::Children::const_iterator it = + std::find(windows.begin(), windows.end(), window); + return (it != windows.end()) ? (it - windows.begin()) : -1; +} + +class TestWindowManagerDelegate : public WindowManagerDelegate { + public: + TestWindowManagerDelegate() {} + ~TestWindowManagerDelegate() override {} + + // WindowManagerDelegate: + void SetWindowManagerClient(WindowManagerClient* client) override {} + bool OnWmSetBounds(Window* window, gfx::Rect* bounds) override { + return false; + } + bool OnWmSetProperty( + Window* window, + const std::string& name, + std::unique_ptr<std::vector<uint8_t>>* new_data) override { + return true; + } + Window* OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) override { + return nullptr; + } + void OnWmClientJankinessChanged(const std::set<Window*>& client_windows, + bool janky) override {} + void OnWmNewDisplay(Window* window, + const display::Display& display) override {} + void OnAccelerator(uint32_t id, const ui::Event& event) override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerDelegate); +}; + +class BoundsChangeObserver : public WindowObserver { + public: + explicit BoundsChangeObserver(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~BoundsChangeObserver() override { window_->RemoveObserver(this); } + + private: + // Overridden from WindowObserver: + void OnWindowBoundsChanged(Window* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override { + DCHECK_EQ(window, window_); + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver); +}; + +// Wait until the bounds of the supplied window change; returns false on +// timeout. +bool WaitForBoundsToChange(Window* window) { + BoundsChangeObserver observer(window); + return WindowServerTestBase::DoRunLoopWithTimeout(); +} + +class ClientAreaChangeObserver : public WindowObserver { + public: + explicit ClientAreaChangeObserver(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~ClientAreaChangeObserver() override { window_->RemoveObserver(this); } + + private: + // Overridden from WindowObserver: + void OnWindowClientAreaChanged( + Window* window, + const gfx::Insets& old_client_area, + const std::vector<gfx::Rect>& old_additional_client_areas) override { + DCHECK_EQ(window, window_); + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(ClientAreaChangeObserver); +}; + +// Wait until the bounds of the supplied window change; returns false on +// timeout. +bool WaitForClientAreaToChange(Window* window) { + ClientAreaChangeObserver observer(window); + return WindowServerTestBase::DoRunLoopWithTimeout(); +} + +// Spins a run loop until the tree beginning at |root| has |tree_size| windows +// (including |root|). +class TreeSizeMatchesObserver : public WindowObserver { + public: + TreeSizeMatchesObserver(Window* tree, size_t tree_size) + : tree_(tree), tree_size_(tree_size) { + tree_->AddObserver(this); + } + ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); } + + bool IsTreeCorrectSize() { return CountWindows(tree_) == tree_size_; } + + private: + // Overridden from WindowObserver: + void OnTreeChanged(const TreeChangeParams& params) override { + if (IsTreeCorrectSize()) + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + size_t CountWindows(const Window* window) const { + size_t count = 1; + Window::Children::const_iterator it = window->children().begin(); + for (; it != window->children().end(); ++it) + count += CountWindows(*it); + return count; + } + + Window* tree_; + size_t tree_size_; + + DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver); +}; + +// Wait until |window| has |tree_size| descendants; returns false on timeout. +// The count includes |window|. For example, if you want to wait for |window| to +// have a single child, use a |tree_size| of 2. +bool WaitForTreeSizeToMatch(Window* window, size_t tree_size) { + TreeSizeMatchesObserver observer(window, tree_size); + return observer.IsTreeCorrectSize() || + WindowServerTestBase::DoRunLoopWithTimeout(); +} + +class OrderChangeObserver : public WindowObserver { + public: + OrderChangeObserver(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~OrderChangeObserver() override { window_->RemoveObserver(this); } + + private: + // Overridden from WindowObserver: + void OnWindowReordered(Window* window, + Window* relative_window, + mojom::OrderDirection direction) override { + DCHECK_EQ(window, window_); + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver); +}; + +// Wait until |window|'s tree size matches |tree_size|; returns false on +// timeout. +bool WaitForOrderChange(WindowTreeClient* client, Window* window) { + OrderChangeObserver observer(window); + return WindowServerTestBase::DoRunLoopWithTimeout(); +} + +// Tracks a window's destruction. Query is_valid() for current state. +class WindowTracker : public WindowObserver { + public: + explicit WindowTracker(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~WindowTracker() override { + if (window_) + window_->RemoveObserver(this); + } + + bool is_valid() const { return !!window_; } + + private: + // Overridden from WindowObserver: + void OnWindowDestroyed(Window* window) override { + DCHECK_EQ(window, window_); + window_ = nullptr; + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(WindowTracker); +}; + +} // namespace + +// WindowServer +// ----------------------------------------------------------------- + +struct EmbedResult { + EmbedResult(WindowTreeClient* client, ClientSpecificId id) + : client(client), client_id(id) {} + EmbedResult() : client(nullptr), client_id(0) {} + + WindowTreeClient* client; + + // The id supplied to the callback from OnEmbed(). Depending upon the + // access policy this may or may not match the client id of + // |client|. + ClientSpecificId client_id; +}; + +Window* GetFirstRoot(WindowTreeClient* client) { + return client->GetRoots().empty() ? nullptr : *client->GetRoots().begin(); +} + +// These tests model synchronization of two peer clients of the window server, +// that are given access to some root window. + +class WindowServerTest : public WindowServerTestBase { + public: + WindowServerTest() {} + + Window* GetFirstWMRoot() { return GetFirstRoot(window_manager()); } + + Window* NewVisibleWindow(Window* parent, WindowTreeClient* client) { + Window* window = client->NewWindow(); + window->SetVisible(true); + parent->AddChild(window); + return window; + } + + // Embeds another version of the test app @ window. This runs a run loop until + // a response is received, or a timeout. On success the new WindowServer is + // returned. + EmbedResult Embed(Window* window) { + DCHECK(!embed_details_); + embed_details_.reset(new EmbedDetails); + window->Embed(ConnectAndGetWindowServerClient(), + base::Bind(&WindowServerTest::EmbedCallbackImpl, + base::Unretained(this))); + embed_details_->waiting = true; + if (!WindowServerTestBase::DoRunLoopWithTimeout()) + return EmbedResult(); + const EmbedResult result(embed_details_->client, + embed_details_->client_id); + embed_details_.reset(); + return result; + } + + // Establishes a connection to this application and asks for a + // WindowTreeClient. + mus::mojom::WindowTreeClientPtr ConnectAndGetWindowServerClient() { + mus::mojom::WindowTreeClientPtr client; + connector()->ConnectToInterface(test_name(), &client); + return client; + } + + // WindowServerTestBase: + void OnEmbed(Window* root) override { + if (!embed_details_) { + WindowServerTestBase::OnEmbed(root); + return; + } + + embed_details_->client = root->window_tree(); + if (embed_details_->callback_run) + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + private: + // Used to track the state of a call to window->Embed(). + struct EmbedDetails { + EmbedDetails() + : callback_run(false), + result(false), + waiting(false), + client(nullptr) {} + + // The callback supplied to Embed() was received. + bool callback_run; + + // The boolean supplied to the Embed() callback. + bool result; + + // Whether a MessageLoop is running. + bool waiting; + + // Client id supplied to the Embed() callback. + ClientSpecificId client_id; + + // The WindowTreeClient that resulted from the Embed(). null if |result| is + // false. + WindowTreeClient* client; + }; + + void EmbedCallbackImpl(bool result) { + embed_details_->callback_run = true; + embed_details_->result = result; + if (embed_details_->waiting && (!result || embed_details_->client)) + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + std::unique_ptr<EmbedDetails> embed_details_; + + DISALLOW_COPY_AND_ASSIGN(WindowServerTest); +}; + +TEST_F(WindowServerTest, RootWindow) { + ASSERT_NE(nullptr, window_manager()); + EXPECT_EQ(1u, window_manager()->GetRoots().size()); +} + +TEST_F(WindowServerTest, Embed) { + Window* window = window_manager()->NewWindow(); + ASSERT_NE(nullptr, window); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + WindowTreeClient* embedded = Embed(window).client; + ASSERT_NE(nullptr, embedded); + + Window* window_in_embedded = GetFirstRoot(embedded); + ASSERT_NE(nullptr, window_in_embedded); + EXPECT_EQ(server_id(window), server_id(window_in_embedded)); + EXPECT_EQ(nullptr, window_in_embedded->parent()); + EXPECT_TRUE(window_in_embedded->children().empty()); +} + +// Window manager has two windows, N1 and N11. Embeds A at N1. A should not see +// N11. +TEST_F(WindowServerTest, EmbeddedDoesntSeeChild) { + Window* window = window_manager()->NewWindow(); + ASSERT_NE(nullptr, window); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + Window* nested = window_manager()->NewWindow(); + ASSERT_NE(nullptr, nested); + nested->SetVisible(true); + window->AddChild(nested); + + WindowTreeClient* embedded = Embed(window).client; + ASSERT_NE(nullptr, embedded); + Window* window_in_embedded = GetFirstRoot(embedded); + EXPECT_EQ(server_id(window), server_id(window_in_embedded)); + EXPECT_EQ(nullptr, window_in_embedded->parent()); + EXPECT_TRUE(window_in_embedded->children().empty()); +} + +// TODO(beng): write a replacement test for the one that once existed here: +// This test validates the following scenario: +// - a window originating from one client +// - a window originating from a second client +// + the client originating the window is destroyed +// -> the window should still exist (since the second client is live) but +// should be disconnected from any windows. +// http://crbug.com/396300 +// +// TODO(beng): The new test should validate the scenario as described above +// except that the second client still has a valid tree. + +// Verifies that bounds changes applied to a window hierarchy in one client +// are reflected to another. +TEST_F(WindowServerTest, SetBounds) { + Window* window = window_manager()->NewWindow(); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + WindowTreeClient* embedded = Embed(window).client; + ASSERT_NE(nullptr, embedded); + + Window* window_in_embedded = + GetChildWindowByServerId(embedded, server_id(window)); + EXPECT_EQ(window->bounds(), window_in_embedded->bounds()); + + window->SetBounds(gfx::Rect(0, 0, 100, 100)); + ASSERT_TRUE(WaitForBoundsToChange(window_in_embedded)); + EXPECT_TRUE(window->bounds() == window_in_embedded->bounds()); +} + +// Verifies that bounds changes applied to a window owned by a different +// client can be refused. +TEST_F(WindowServerTest, SetBoundsSecurity) { + TestWindowManagerDelegate wm_delegate; + set_window_manager_delegate(&wm_delegate); + + Window* window = window_manager()->NewWindow(); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + WindowTreeClient* embedded = Embed(window).client; + ASSERT_NE(nullptr, embedded); + + Window* window_in_embedded = + GetChildWindowByServerId(embedded, server_id(window)); + window->SetBounds(gfx::Rect(0, 0, 800, 600)); + ASSERT_TRUE(WaitForBoundsToChange(window_in_embedded)); + + window_in_embedded->SetBounds(gfx::Rect(0, 0, 1024, 768)); + // Bounds change is initially accepted, but the server declines the request. + EXPECT_FALSE(window->bounds() == window_in_embedded->bounds()); + + // The client is notified when the requested is declined, and updates the + // local bounds accordingly. + ASSERT_TRUE(WaitForBoundsToChange(window_in_embedded)); + EXPECT_TRUE(window->bounds() == window_in_embedded->bounds()); + set_window_manager_delegate(nullptr); +} + +// Verifies that a root window can always be destroyed. +TEST_F(WindowServerTest, DestroySecurity) { + Window* window = window_manager()->NewWindow(); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + + WindowTreeClient* embedded = Embed(window).client; + ASSERT_NE(nullptr, embedded); + + // The root can be destroyed, even though it was not created by the client. + Window* embed_root = GetChildWindowByServerId(embedded, server_id(window)); + WindowTracker tracker1(window); + WindowTracker tracker2(embed_root); + embed_root->Destroy(); + EXPECT_FALSE(tracker2.is_valid()); + EXPECT_TRUE(tracker1.is_valid()); + + window->Destroy(); + EXPECT_FALSE(tracker1.is_valid()); +} + +TEST_F(WindowServerTest, MultiRoots) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + Window* window2 = window_manager()->NewWindow(); + window2->SetVisible(true); + GetFirstWMRoot()->AddChild(window2); + WindowTreeClient* embedded1 = Embed(window1).client; + ASSERT_NE(nullptr, embedded1); + WindowTreeClient* embedded2 = Embed(window2).client; + ASSERT_NE(nullptr, embedded2); + EXPECT_NE(embedded1, embedded2); +} + +TEST_F(WindowServerTest, Reorder) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + + WindowTreeClient* embedded = Embed(window1).client; + ASSERT_NE(nullptr, embedded); + + Window* window11 = embedded->NewWindow(); + window11->SetVisible(true); + GetFirstRoot(embedded)->AddChild(window11); + Window* window12 = embedded->NewWindow(); + window12->SetVisible(true); + GetFirstRoot(embedded)->AddChild(window12); + ASSERT_TRUE(WaitForTreeSizeToMatch(window1, 3u)); + + Window* root_in_embedded = GetFirstRoot(embedded); + + { + window11->MoveToFront(); + // The |embedded| tree should be updated immediately. + EXPECT_EQ(root_in_embedded->children().front(), + GetChildWindowByServerId(embedded, server_id(window12))); + EXPECT_EQ(root_in_embedded->children().back(), + GetChildWindowByServerId(embedded, server_id(window11))); + + // The |window_manager()| tree is still not updated. + EXPECT_EQ(window1->children().back(), + GetChildWindowByServerId(window_manager(), server_id(window12))); + + // Wait until |window_manager()| tree is updated. + ASSERT_TRUE(WaitForOrderChange( + window_manager(), + GetChildWindowByServerId(window_manager(), server_id(window11)))); + EXPECT_EQ(window1->children().front(), + GetChildWindowByServerId(window_manager(), server_id(window12))); + EXPECT_EQ(window1->children().back(), + GetChildWindowByServerId(window_manager(), server_id(window11))); + } + + { + window11->MoveToBack(); + // |embedded| should be updated immediately. + EXPECT_EQ(root_in_embedded->children().front(), + GetChildWindowByServerId(embedded, server_id(window11))); + EXPECT_EQ(root_in_embedded->children().back(), + GetChildWindowByServerId(embedded, server_id(window12))); + + // |window_manager()| is also eventually updated. + EXPECT_EQ(window1->children().back(), + GetChildWindowByServerId(window_manager(), server_id(window11))); + ASSERT_TRUE(WaitForOrderChange( + window_manager(), + GetChildWindowByServerId(window_manager(), server_id(window11)))); + EXPECT_EQ(window1->children().front(), + GetChildWindowByServerId(window_manager(), server_id(window11))); + EXPECT_EQ(window1->children().back(), + GetChildWindowByServerId(window_manager(), server_id(window12))); + } +} + +namespace { + +class VisibilityChangeObserver : public WindowObserver { + public: + explicit VisibilityChangeObserver(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~VisibilityChangeObserver() override { window_->RemoveObserver(this); } + + private: + // Overridden from WindowObserver: + void OnWindowVisibilityChanged(Window* window) override { + EXPECT_EQ(window, window_); + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); +}; + +} // namespace + +TEST_F(WindowServerTest, Visible) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + + // Embed another app and verify initial state. + WindowTreeClient* embedded = Embed(window1).client; + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, GetFirstRoot(embedded)); + Window* embedded_root = GetFirstRoot(embedded); + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); + + // Change the visible state from the first client and verify its mirrored + // correctly to the embedded app. + { + VisibilityChangeObserver observer(embedded_root); + window1->SetVisible(false); + ASSERT_TRUE(WindowServerTestBase::DoRunLoopWithTimeout()); + } + + EXPECT_FALSE(window1->visible()); + EXPECT_FALSE(window1->IsDrawn()); + + EXPECT_FALSE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); + + // Make the node visible again. + { + VisibilityChangeObserver observer(embedded_root); + window1->SetVisible(true); + ASSERT_TRUE(WindowServerTestBase::DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(window1->visible()); + EXPECT_TRUE(window1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); +} + +namespace { + +class DrawnChangeObserver : public WindowObserver { + public: + explicit DrawnChangeObserver(Window* window) : window_(window) { + window_->AddObserver(this); + } + ~DrawnChangeObserver() override { window_->RemoveObserver(this); } + + private: + // Overridden from WindowObserver: + void OnWindowDrawnChanged(Window* window) override { + EXPECT_EQ(window, window_); + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + + DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); +}; + +} // namespace + +TEST_F(WindowServerTest, Drawn) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + + // Embed another app and verify initial state. + WindowTreeClient* embedded = Embed(window1).client; + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, GetFirstRoot(embedded)); + Window* embedded_root = GetFirstRoot(embedded); + 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); + GetFirstWMRoot()->SetVisible(false); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(window1->visible()); + EXPECT_FALSE(window1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); +} + +// TODO(beng): tests for window event dispatcher. +// - verify that we see events for all windows. + +namespace { + +class FocusChangeObserver : public WindowObserver { + public: + explicit FocusChangeObserver(Window* window) + : window_(window), + last_gained_focus_(nullptr), + last_lost_focus_(nullptr), + quit_on_change_(true) { + window_->AddObserver(this); + } + ~FocusChangeObserver() override { window_->RemoveObserver(this); } + + void set_quit_on_change(bool value) { quit_on_change_ = value; } + + Window* last_gained_focus() { return last_gained_focus_; } + + Window* last_lost_focus() { return last_lost_focus_; } + + private: + // Overridden from WindowObserver. + void OnWindowFocusChanged(Window* gained_focus, Window* lost_focus) override { + EXPECT_TRUE(!gained_focus || gained_focus->HasFocus()); + EXPECT_FALSE(lost_focus && lost_focus->HasFocus()); + last_gained_focus_ = gained_focus; + last_lost_focus_ = lost_focus; + if (quit_on_change_) + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + Window* window_; + Window* last_gained_focus_; + Window* last_lost_focus_; + bool quit_on_change_; + + DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); +}; + +class NullFocusChangeObserver : public WindowTreeClientObserver { + public: + explicit NullFocusChangeObserver(WindowTreeClient* client) + : client_(client) { + client_->AddObserver(this); + } + ~NullFocusChangeObserver() override { client_->RemoveObserver(this); } + + private: + // Overridden from WindowTreeClientObserver. + void OnWindowTreeFocusChanged(Window* gained_focus, + Window* lost_focus) override { + if (!gained_focus) + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + WindowTreeClient* client_; + + DISALLOW_COPY_AND_ASSIGN(NullFocusChangeObserver); +}; + +bool WaitForWindowToHaveFocus(Window* window) { + if (window->HasFocus()) + return true; + FocusChangeObserver observer(window); + return WindowServerTestBase::DoRunLoopWithTimeout(); +} + +bool WaitForNoWindowToHaveFocus(WindowTreeClient* client) { + if (!client->GetFocusedWindow()) + return true; + NullFocusChangeObserver observer(client); + return WindowServerTestBase::DoRunLoopWithTimeout(); +} + +} // namespace + +TEST_F(WindowServerTest, Focus) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + + WindowTreeClient* embedded = Embed(window1).client; + ASSERT_NE(nullptr, embedded); + Window* window11 = embedded->NewWindow(); + window11->SetVisible(true); + GetFirstRoot(embedded)->AddChild(window11); + + { + // Focus the embed root in |embedded|. + Window* embedded_root = GetFirstRoot(embedded); + FocusChangeObserver observer(embedded_root); + observer.set_quit_on_change(false); + embedded_root->SetFocus(); + ASSERT_TRUE(embedded_root->HasFocus()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + EXPECT_EQ(server_id(embedded_root), + server_id(observer.last_gained_focus())); + + // |embedded_root| is the same as |window1|, make sure |window1| got + // focus too. + ASSERT_TRUE(WaitForWindowToHaveFocus(window1)); + } + + // Focus a child of GetFirstRoot(embedded). + { + FocusChangeObserver observer(window11); + observer.set_quit_on_change(false); + window11->SetFocus(); + ASSERT_TRUE(window11->HasFocus()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + ASSERT_NE(nullptr, observer.last_lost_focus()); + EXPECT_EQ(server_id(window11), server_id(observer.last_gained_focus())); + EXPECT_EQ(server_id(GetFirstRoot(embedded)), + server_id(observer.last_lost_focus())); + } + + { + // Add an observer on the Window that loses focus, and make sure the + // observer sees the right values. + FocusChangeObserver observer(window11); + observer.set_quit_on_change(false); + GetFirstRoot(embedded)->SetFocus(); + ASSERT_NE(nullptr, observer.last_gained_focus()); + ASSERT_NE(nullptr, observer.last_lost_focus()); + EXPECT_EQ(server_id(window11), server_id(observer.last_lost_focus())); + EXPECT_EQ(server_id(GetFirstRoot(embedded)), + server_id(observer.last_gained_focus())); + } +} + +TEST_F(WindowServerTest, ClearFocus) { + Window* window1 = window_manager()->NewWindow(); + window1->SetVisible(true); + GetFirstWMRoot()->AddChild(window1); + + WindowTreeClient* embedded = Embed(window1).client; + ASSERT_NE(nullptr, embedded); + Window* window11 = embedded->NewWindow(); + window11->SetVisible(true); + GetFirstRoot(embedded)->AddChild(window11); + + // Focus the embed root in |embedded|. + Window* embedded_root = GetFirstRoot(embedded); + { + FocusChangeObserver observer(embedded_root); + observer.set_quit_on_change(false); + embedded_root->SetFocus(); + ASSERT_TRUE(embedded_root->HasFocus()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + EXPECT_EQ(server_id(embedded_root), + server_id(observer.last_gained_focus())); + + // |embedded_root| is the same as |window1|, make sure |window1| got + // focus too. + ASSERT_TRUE(WaitForWindowToHaveFocus(window1)); + } + + { + FocusChangeObserver observer(window1); + embedded->ClearFocus(); + ASSERT_FALSE(embedded_root->HasFocus()); + EXPECT_FALSE(embedded->GetFocusedWindow()); + + ASSERT_TRUE(WindowServerTestBase::DoRunLoopWithTimeout()); + EXPECT_FALSE(window1->HasFocus()); + EXPECT_FALSE(window_manager()->GetFocusedWindow()); + } +} + +TEST_F(WindowServerTest, FocusNonFocusableWindow) { + Window* window = window_manager()->NewWindow(); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + + WindowTreeClient* client = Embed(window).client; + ASSERT_NE(nullptr, client); + ASSERT_FALSE(client->GetRoots().empty()); + Window* client_window = *client->GetRoots().begin(); + client_window->SetCanFocus(false); + + client_window->SetFocus(); + ASSERT_TRUE(client_window->HasFocus()); + + WaitForNoWindowToHaveFocus(client); + ASSERT_FALSE(client_window->HasFocus()); +} + +TEST_F(WindowServerTest, Activation) { + Window* parent = NewVisibleWindow(GetFirstWMRoot(), window_manager()); + + // Allow the child windows to be activated. Do this before we wait, that way + // we're guaranteed that when we request focus from a separate client the + // requests are processed in order. + window_manager_client()->AddActivationParent(parent); + + Window* child1 = NewVisibleWindow(parent, window_manager()); + Window* child2 = NewVisibleWindow(parent, window_manager()); + Window* child3 = NewVisibleWindow(parent, window_manager()); + + child1->AddTransientWindow(child3); + + WindowTreeClient* embedded1 = Embed(child1).client; + ASSERT_NE(nullptr, embedded1); + WindowTreeClient* embedded2 = Embed(child2).client; + ASSERT_NE(nullptr, embedded2); + + Window* child11 = NewVisibleWindow(GetFirstRoot(embedded1), embedded1); + Window* child21 = NewVisibleWindow(GetFirstRoot(embedded2), embedded2); + + WaitForTreeSizeToMatch(parent, 6); + + // |child2| and |child3| are stacked about |child1|. + EXPECT_GT(ValidIndexOf(parent->children(), child2), + ValidIndexOf(parent->children(), child1)); + EXPECT_GT(ValidIndexOf(parent->children(), child3), + ValidIndexOf(parent->children(), child1)); + + // Set focus on |child11|. This should activate |child1|, and raise it over + // |child2|. But |child3| should still be above |child1| because of + // transiency. + child11->SetFocus(); + ASSERT_TRUE(WaitForWindowToHaveFocus(child11)); + ASSERT_TRUE(WaitForWindowToHaveFocus( + GetChildWindowByServerId(window_manager(), server_id(child11)))); + EXPECT_EQ(server_id(child11), + server_id(window_manager()->GetFocusedWindow())); + EXPECT_EQ(server_id(child11), server_id(embedded1->GetFocusedWindow())); + EXPECT_EQ(nullptr, embedded2->GetFocusedWindow()); + EXPECT_GT(ValidIndexOf(parent->children(), child1), + ValidIndexOf(parent->children(), child2)); + EXPECT_GT(ValidIndexOf(parent->children(), child3), + ValidIndexOf(parent->children(), child1)); + + // Set focus on |child21|. This should activate |child2|, and raise it over + // |child1|. + child21->SetFocus(); + ASSERT_TRUE(WaitForWindowToHaveFocus(child21)); + ASSERT_TRUE(WaitForWindowToHaveFocus( + GetChildWindowByServerId(window_manager(), server_id(child21)))); + EXPECT_EQ(server_id(child21), + server_id(window_manager()->GetFocusedWindow())); + EXPECT_EQ(server_id(child21), server_id(embedded2->GetFocusedWindow())); + EXPECT_TRUE(WaitForNoWindowToHaveFocus(embedded1)); + EXPECT_EQ(nullptr, embedded1->GetFocusedWindow()); + EXPECT_GT(ValidIndexOf(parent->children(), child2), + ValidIndexOf(parent->children(), child1)); + EXPECT_GT(ValidIndexOf(parent->children(), child3), + ValidIndexOf(parent->children(), child1)); +} + +TEST_F(WindowServerTest, ActivationNext) { + Window* parent = GetFirstWMRoot(); + Window* child1 = NewVisibleWindow(parent, window_manager()); + Window* child2 = NewVisibleWindow(parent, window_manager()); + Window* child3 = NewVisibleWindow(parent, window_manager()); + + WindowTreeClient* embedded1 = Embed(child1).client; + ASSERT_NE(nullptr, embedded1); + WindowTreeClient* embedded2 = Embed(child2).client; + ASSERT_NE(nullptr, embedded2); + WindowTreeClient* embedded3 = Embed(child3).client; + ASSERT_NE(nullptr, embedded3); + + Window* child11 = NewVisibleWindow(GetFirstRoot(embedded1), embedded1); + Window* child21 = NewVisibleWindow(GetFirstRoot(embedded2), embedded2); + Window* child31 = NewVisibleWindow(GetFirstRoot(embedded3), embedded3); + WaitForTreeSizeToMatch(parent, 7); + + Window* expecteds[] = { child3, child2, child1, child3, nullptr }; + Window* focused[] = { child31, child21, child11, child31, nullptr }; + for (size_t index = 0; expecteds[index]; ++index) { + window_manager_client()->ActivateNextWindow(); + WaitForWindowToHaveFocus(focused[index]); + EXPECT_TRUE(focused[index]->HasFocus()); + EXPECT_EQ(parent->children().back(), expecteds[index]); + } +} + +namespace { + +class DestroyedChangedObserver : public WindowObserver { + public: + DestroyedChangedObserver(WindowServerTestBase* test, + Window* window, + bool* got_destroy) + : test_(test), window_(window), got_destroy_(got_destroy) { + window_->AddObserver(this); + } + ~DestroyedChangedObserver() override { + if (window_) + window_->RemoveObserver(this); + } + + private: + // Overridden from WindowObserver: + void OnWindowDestroyed(Window* window) override { + EXPECT_EQ(window, window_); + window_->RemoveObserver(this); + *got_destroy_ = true; + window_ = nullptr; + + // We should always get OnWindowDestroyed() before + // OnWindowTreeClientDestroyed(). + EXPECT_FALSE(test_->window_tree_client_destroyed()); + } + + WindowServerTestBase* test_; + Window* window_; + bool* got_destroy_; + + DISALLOW_COPY_AND_ASSIGN(DestroyedChangedObserver); +}; + +} // namespace + +// Verifies deleting a WindowServer sends the right notifications. +TEST_F(WindowServerTest, DeleteWindowServer) { + Window* window = window_manager()->NewWindow(); + ASSERT_NE(nullptr, window); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + WindowTreeClient* client = Embed(window).client; + ASSERT_TRUE(client); + bool got_destroy = false; + DestroyedChangedObserver observer(this, GetFirstRoot(client), + &got_destroy); + delete client; + EXPECT_TRUE(window_tree_client_destroyed()); + EXPECT_TRUE(got_destroy); +} + +// Verifies two Embed()s in the same window trigger deletion of the first +// WindowServer. +TEST_F(WindowServerTest, DisconnectTriggersDelete) { + Window* window = window_manager()->NewWindow(); + ASSERT_NE(nullptr, window); + window->SetVisible(true); + GetFirstWMRoot()->AddChild(window); + WindowTreeClient* client = Embed(window).client; + EXPECT_NE(client, window_manager()); + Window* embedded_window = client->NewWindow(); + // Embed again, this should trigger disconnect and deletion of client. + bool got_destroy; + DestroyedChangedObserver observer(this, embedded_window, &got_destroy); + EXPECT_FALSE(window_tree_client_destroyed()); + Embed(window); + EXPECT_TRUE(window_tree_client_destroyed()); +} + +class WindowRemovedFromParentObserver : public WindowObserver { + public: + explicit WindowRemovedFromParentObserver(Window* window) + : window_(window), was_removed_(false) { + window_->AddObserver(this); + } + ~WindowRemovedFromParentObserver() override { window_->RemoveObserver(this); } + + bool was_removed() const { return was_removed_; } + + private: + // Overridden from WindowObserver: + void OnTreeChanged(const TreeChangeParams& params) override { + if (params.target == window_ && !params.new_parent) + was_removed_ = true; + } + + Window* window_; + bool was_removed_; + + DISALLOW_COPY_AND_ASSIGN(WindowRemovedFromParentObserver); +}; + +TEST_F(WindowServerTest, EmbedRemovesChildren) { + Window* window1 = window_manager()->NewWindow(); + Window* window2 = window_manager()->NewWindow(); + GetFirstWMRoot()->AddChild(window1); + window1->AddChild(window2); + + WindowRemovedFromParentObserver observer(window2); + window1->Embed(ConnectAndGetWindowServerClient()); + EXPECT_TRUE(observer.was_removed()); + EXPECT_EQ(nullptr, window2->parent()); + EXPECT_TRUE(window1->children().empty()); + + // Run the message loop so the Embed() call above completes. Without this + // we may end up reconnecting to the test and rerunning the test, which is + // problematic since the other services don't shut down. + ASSERT_TRUE(DoRunLoopWithTimeout()); +} + +namespace { + +class DestroyObserver : public WindowObserver { + public: + DestroyObserver(WindowServerTestBase* test, + WindowTreeClient* client, + bool* got_destroy) + : test_(test), got_destroy_(got_destroy) { + GetFirstRoot(client)->AddObserver(this); + } + ~DestroyObserver() override {} + + private: + // Overridden from WindowObserver: + void OnWindowDestroyed(Window* window) override { + *got_destroy_ = true; + window->RemoveObserver(this); + + // We should always get OnWindowDestroyed() before + // OnWindowManagerDestroyed(). + EXPECT_FALSE(test_->window_tree_client_destroyed()); + + EXPECT_TRUE(WindowServerTestBase::QuitRunLoop()); + } + + WindowServerTestBase* test_; + bool* got_destroy_; + + DISALLOW_COPY_AND_ASSIGN(DestroyObserver); +}; + +} // namespace + +// Verifies deleting a Window that is the root of another client notifies +// observers in the right order (OnWindowDestroyed() before +// OnWindowManagerDestroyed()). +TEST_F(WindowServerTest, WindowServerDestroyedAfterRootObserver) { + Window* embed_window = window_manager()->NewWindow(); + GetFirstWMRoot()->AddChild(embed_window); + + WindowTreeClient* embedded_client = Embed(embed_window).client; + + bool got_destroy = false; + DestroyObserver observer(this, embedded_client, &got_destroy); + // Delete the window |embedded_client| is embedded in. This is async, + // but will eventually trigger deleting |embedded_client|. + embed_window->Destroy(); + EXPECT_TRUE(DoRunLoopWithTimeout()); + EXPECT_TRUE(got_destroy); +} + +TEST_F(WindowServerTest, ClientAreaChanged) { + Window* embed_window = window_manager()->NewWindow(); + GetFirstWMRoot()->AddChild(embed_window); + + WindowTreeClient* embedded_client = Embed(embed_window).client; + + // Verify change from embedded makes it to parent. + GetFirstRoot(embedded_client)->SetClientArea(gfx::Insets(1, 2, 3, 4)); + ASSERT_TRUE(WaitForClientAreaToChange(embed_window)); + EXPECT_TRUE(gfx::Insets(1, 2, 3, 4) == embed_window->client_area()); + + // Changing bounds shouldn't effect client area. + embed_window->SetBounds(gfx::Rect(21, 22, 23, 24)); + WaitForBoundsToChange(GetFirstRoot(embedded_client)); + EXPECT_TRUE(gfx::Rect(21, 22, 23, 24) == + GetFirstRoot(embedded_client)->bounds()); + EXPECT_TRUE(gfx::Insets(1, 2, 3, 4) == + GetFirstRoot(embedded_client)->client_area()); +} + +class EstablishConnectionViaFactoryDelegate : public TestWindowManagerDelegate { + public: + explicit EstablishConnectionViaFactoryDelegate( + WindowTreeClient* client) + : client_(client), run_loop_(nullptr), created_window_(nullptr) {} + ~EstablishConnectionViaFactoryDelegate() override {} + + bool QuitOnCreate() { + if (run_loop_) + return false; + + created_window_ = nullptr; + run_loop_.reset(new base::RunLoop); + run_loop_->Run(); + run_loop_.reset(); + return created_window_ != nullptr; + } + + Window* created_window() { return created_window_; } + + // WindowManagerDelegate: + Window* OnWmCreateTopLevelWindow( + std::map<std::string, std::vector<uint8_t>>* properties) override { + created_window_ = client_->NewWindow(properties); + (*client_->GetRoots().begin())->AddChild(created_window_); + if (run_loop_) + run_loop_->Quit(); + return created_window_; + } + + private: + WindowTreeClient* client_; + std::unique_ptr<base::RunLoop> run_loop_; + Window* created_window_; + + DISALLOW_COPY_AND_ASSIGN(EstablishConnectionViaFactoryDelegate); +}; + +TEST_F(WindowServerTest, EstablishConnectionViaFactory) { + EstablishConnectionViaFactoryDelegate delegate(window_manager()); + set_window_manager_delegate(&delegate); + std::unique_ptr<WindowTreeClient> second_client( + new WindowTreeClient(this, nullptr, nullptr)); + second_client->ConnectViaWindowTreeFactory(connector()); + Window* window_in_second_client = + second_client->NewTopLevelWindow(nullptr); + ASSERT_TRUE(window_in_second_client); + ASSERT_TRUE(second_client->GetRoots().count(window_in_second_client) > + 0); + // Wait for the window to appear in the wm. + ASSERT_TRUE(delegate.QuitOnCreate()); + + Window* window_in_wm = delegate.created_window(); + ASSERT_TRUE(window_in_wm); + + // Change the bounds in the wm, and make sure the child sees it. + window_in_wm->SetBounds(gfx::Rect(1, 11, 12, 101)); + ASSERT_TRUE(WaitForBoundsToChange(window_in_second_client)); + EXPECT_EQ(gfx::Rect(1, 11, 12, 101), window_in_second_client->bounds()); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_display_root.cc b/chromium/components/mus/ws/window_manager_display_root.cc new file mode 100644 index 00000000000..351208d4af6 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_display_root.cc @@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_manager_display_root.h" + +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/window_server.h" + +namespace mus { +namespace ws { + +WindowManagerDisplayRoot::WindowManagerDisplayRoot(Display* display) + : display_(display) { + root_.reset(window_server()->CreateServerWindow( + window_server()->display_manager()->GetAndAdvanceNextRootId(), + ServerWindow::Properties())); + // Our root is always a child of the Display's root. Do this + // before the WindowTree has been created so that the client doesn't get + // notified of the add, bounds change and visibility change. + root_->SetBounds(gfx::Rect(display->root_window()->bounds().size())); + root_->SetVisible(true); + display->root_window()->Add(root_.get()); +} + +WindowManagerDisplayRoot::~WindowManagerDisplayRoot() {} + +WindowServer* WindowManagerDisplayRoot::window_server() { + return display_->window_server(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_display_root.h b/chromium/components/mus/ws/window_manager_display_root.h new file mode 100644 index 00000000000..94eda737201 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_display_root.h @@ -0,0 +1,62 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_DISPLAY_ROOT_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_DISPLAY_ROOT_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/macros.h" + +namespace mus { +namespace ws { + +class Display; +class ServerWindow; +class WindowManagerState; +class WindowServer; + +namespace test { +class WindowManagerDisplayRootTestApi; +} + +// Owns the root window of a window manager for one display. Each window manager +// has one WindowManagerDisplayRoot for each Display. The root window is +// parented to the root of a Display. +class WindowManagerDisplayRoot { + public: + explicit WindowManagerDisplayRoot(Display* display); + ~WindowManagerDisplayRoot(); + + ServerWindow* root() { return root_.get(); } + const ServerWindow* root() const { return root_.get(); } + + Display* display() { return display_; } + const Display* display() const { return display_; } + + WindowManagerState* window_manager_state() { return window_manager_state_; } + const WindowManagerState* window_manager_state() const { + return window_manager_state_; + } + + private: + friend class Display; + + WindowServer* window_server(); + + Display* display_; + // Root ServerWindow of this WindowManagerDisplayRoot. |root_| has a parent, + // the root ServerWindow of the Display. + std::unique_ptr<ServerWindow> root_; + WindowManagerState* window_manager_state_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerDisplayRoot); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_DISPLAY_ROOT_H_ diff --git a/chromium/components/mus/ws/window_manager_state.cc b/chromium/components/mus/ws/window_manager_state.cc new file mode 100644 index 00000000000..b394514bef2 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_state.cc @@ -0,0 +1,485 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_manager_state.h" + +#include "base/memory/weak_ptr.h" +#include "components/mus/common/event_matcher_util.h" +#include "components/mus/ws/accelerator.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/user_display_manager.h" +#include "components/mus/ws/user_id_tracker.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" +#include "services/shell/public/interfaces/connector.mojom.h" +#include "ui/events/event.h" + +namespace mus { +namespace ws { +namespace { + +// Debug accelerator IDs start far above the highest valid Windows command ID +// (0xDFFF) and Chrome's highest IDC command ID. +const uint32_t kPrintWindowsDebugAcceleratorId = 1u << 31; + +base::TimeDelta GetDefaultAckTimerDelay() { +#if defined(NDEBUG) + return base::TimeDelta::FromMilliseconds(100); +#else + return base::TimeDelta::FromMilliseconds(1000); +#endif +} + +bool EventsCanBeCoalesced(const ui::Event& one, const ui::Event& two) { + if (one.type() != two.type() || one.flags() != two.flags()) + return false; + + // TODO(sad): wheel events can also be merged. + if (one.type() != ui::ET_POINTER_MOVED) + return false; + + return one.AsPointerEvent()->pointer_id() == + two.AsPointerEvent()->pointer_id(); +} + +std::unique_ptr<ui::Event> CoalesceEvents(std::unique_ptr<ui::Event> first, + std::unique_ptr<ui::Event> second) { + DCHECK(first->type() == ui::ET_POINTER_MOVED) + << " Non-move events cannot be merged yet."; + // For mouse moves, the new event just replaces the old event. + return second; +} + +const ServerWindow* GetEmbedRoot(const ServerWindow* window) { + DCHECK(window); + const ServerWindow* embed_root = window->parent(); + while (embed_root && embed_root->id().client_id == window->id().client_id) + embed_root = embed_root->parent(); + return embed_root; +} + +} // namespace + +class WindowManagerState::ProcessedEventTarget { + public: + ProcessedEventTarget(ServerWindow* window, + ClientSpecificId client_id, + Accelerator* accelerator) + : client_id_(client_id) { + tracker_.Add(window); + if (accelerator) + accelerator_ = accelerator->GetWeakPtr(); + } + + ~ProcessedEventTarget() {} + + // Return true if the event is still valid. The event becomes invalid if + // the window is destroyed while waiting to dispatch. + bool IsValid() const { return !tracker_.windows().empty(); } + + ServerWindow* window() { + DCHECK(IsValid()); + return tracker_.windows().front(); + } + + ClientSpecificId client_id() const { return client_id_; } + + base::WeakPtr<Accelerator> accelerator() { return accelerator_; } + + private: + ServerWindowTracker tracker_; + const ClientSpecificId client_id_; + base::WeakPtr<Accelerator> accelerator_; + + DISALLOW_COPY_AND_ASSIGN(ProcessedEventTarget); +}; + +WindowManagerState::QueuedEvent::QueuedEvent() {} +WindowManagerState::QueuedEvent::~QueuedEvent() {} + +WindowManagerState::WindowManagerState(WindowTree* window_tree) + : window_tree_(window_tree), event_dispatcher_(this), weak_factory_(this) { + frame_decoration_values_ = mojom::FrameDecorationValues::New(); + frame_decoration_values_->max_title_bar_button_width = 0u; + + AddDebugAccelerators(); +} + +WindowManagerState::~WindowManagerState() {} + +void WindowManagerState::SetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) { + got_frame_decoration_values_ = true; + frame_decoration_values_ = values.Clone(); + display_manager() + ->GetUserDisplayManager(user_id()) + ->OnFrameDecorationValuesChanged(); +} + +bool WindowManagerState::SetCapture(ServerWindow* window, + ClientSpecificId client_id) { + DCHECK(IsActive()); + if (capture_window() == window && + client_id == event_dispatcher_.capture_window_client_id()) { + return true; + } +#if !defined(NDEBUG) + if (window) { + WindowManagerDisplayRoot* display_root = + display_manager()->GetWindowManagerDisplayRoot(window); + DCHECK(display_root && display_root->window_manager_state() == this); + } +#endif + return event_dispatcher_.SetCaptureWindow(window, client_id); +} + +void WindowManagerState::ReleaseCaptureBlockedByModalWindow( + const ServerWindow* modal_window) { + event_dispatcher_.ReleaseCaptureBlockedByModalWindow(modal_window); +} + +void WindowManagerState::ReleaseCaptureBlockedByAnyModalWindow() { + event_dispatcher_.ReleaseCaptureBlockedByAnyModalWindow(); +} + +void WindowManagerState::AddSystemModalWindow(ServerWindow* window) { + DCHECK(!window->transient_parent()); + event_dispatcher_.AddSystemModalWindow(window); +} + +const UserId& WindowManagerState::user_id() const { + return window_tree_->user_id(); +} + +void WindowManagerState::OnWillDestroyTree(WindowTree* tree) { + if (tree_awaiting_input_ack_ != tree) + return; + + // The WindowTree is dying. So it's not going to ack the event. + // If the dying tree matches the root |tree_| marked as handled so we don't + // notify it of accelerators. + OnEventAck(tree_awaiting_input_ack_, tree == window_tree_ + ? mojom::EventResult::HANDLED + : mojom::EventResult::UNHANDLED); +} + +bool WindowManagerState::IsActive() const { + return window_server()->user_id_tracker()->active_id() == user_id(); +} + +void WindowManagerState::Activate(const gfx::Point& mouse_location_on_screen) { + SetAllRootWindowsVisible(true); + event_dispatcher_.Reset(); + event_dispatcher_.SetMousePointerScreenLocation(mouse_location_on_screen); +} + +void WindowManagerState::Deactivate() { + SetAllRootWindowsVisible(false); + event_dispatcher_.Reset(); + // The tree is no longer active, so no point in dispatching any further + // events. + std::queue<std::unique_ptr<QueuedEvent>> event_queue; + event_queue.swap(event_queue_); +} + +void WindowManagerState::ProcessEvent(const ui::Event& event) { + // If this is still waiting for an ack from a previously sent event, then + // queue up the event to be dispatched once the ack is received. + if (event_ack_timer_.IsRunning()) { + if (!event_queue_.empty() && !event_queue_.back()->processed_target && + EventsCanBeCoalesced(*event_queue_.back()->event, event)) { + event_queue_.back()->event = CoalesceEvents( + std::move(event_queue_.back()->event), ui::Event::Clone(event)); + return; + } + QueueEvent(event, nullptr); + return; + } + event_dispatcher_.ProcessEvent(event); +} + +void WindowManagerState::OnEventAck(mojom::WindowTree* tree, + mojom::EventResult result) { + if (tree_awaiting_input_ack_ != tree) { + // TODO(sad): The ack must have arrived after the timeout. We should do + // something here, and in OnEventAckTimeout(). + return; + } + tree_awaiting_input_ack_ = nullptr; + event_ack_timer_.Stop(); + + if (result == mojom::EventResult::UNHANDLED && post_target_accelerator_) + OnAccelerator(post_target_accelerator_->id(), *event_awaiting_input_ack_); + + ProcessNextEventFromQueue(); +} + +const WindowServer* WindowManagerState::window_server() const { + return window_tree_->window_server(); +} + +WindowServer* WindowManagerState::window_server() { + return window_tree_->window_server(); +} + +DisplayManager* WindowManagerState::display_manager() { + return window_tree_->display_manager(); +} + +const DisplayManager* WindowManagerState::display_manager() const { + return window_tree_->display_manager(); +} + +void WindowManagerState::SetAllRootWindowsVisible(bool value) { + for (Display* display : display_manager()->displays()) { + WindowManagerDisplayRoot* display_root = + display->GetWindowManagerDisplayRootForUser(user_id()); + if (display_root) + display_root->root()->SetVisible(value); + } +} + +ServerWindow* WindowManagerState::GetWindowManagerRoot(ServerWindow* window) { + for (Display* display : display_manager()->displays()) { + WindowManagerDisplayRoot* display_root = + display->GetWindowManagerDisplayRootForUser(user_id()); + if (display_root && display_root->root()->parent() == window) + return display_root->root(); + } + NOTREACHED(); + return nullptr; +} + +void WindowManagerState::OnEventAckTimeout(ClientSpecificId client_id) { + WindowTree* hung_tree = window_server()->GetTreeWithId(client_id); + if (hung_tree && !hung_tree->janky()) + window_tree_->ClientJankinessChanged(hung_tree); + OnEventAck(tree_awaiting_input_ack_, mojom::EventResult::UNHANDLED); +} + +void WindowManagerState::QueueEvent( + const ui::Event& event, + std::unique_ptr<ProcessedEventTarget> processed_event_target) { + std::unique_ptr<QueuedEvent> queued_event(new QueuedEvent); + queued_event->event = ui::Event::Clone(event); + queued_event->processed_target = std::move(processed_event_target); + event_queue_.push(std::move(queued_event)); +} + +void WindowManagerState::ProcessNextEventFromQueue() { + // Loop through |event_queue_| stopping after dispatching the first valid + // event. + while (!event_queue_.empty()) { + std::unique_ptr<QueuedEvent> queued_event = std::move(event_queue_.front()); + event_queue_.pop(); + if (!queued_event->processed_target) { + event_dispatcher_.ProcessEvent(*queued_event->event); + return; + } + if (queued_event->processed_target->IsValid()) { + DispatchInputEventToWindowImpl( + queued_event->processed_target->window(), + queued_event->processed_target->client_id(), *queued_event->event, + queued_event->processed_target->accelerator()); + return; + } + } +} + +void WindowManagerState::DispatchInputEventToWindowImpl( + ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + base::WeakPtr<Accelerator> accelerator) { + if (target && target->parent() == nullptr) + target = GetWindowManagerRoot(target); + + if (event.IsMousePointerEvent()) { + DCHECK(event_dispatcher_.mouse_cursor_source_window()); + + int32_t cursor_id = 0; + if (event_dispatcher_.GetCurrentMouseCursor(&cursor_id)) { + WindowManagerDisplayRoot* display_root = + display_manager()->GetWindowManagerDisplayRoot(target); + display_root->display()->UpdateNativeCursor(cursor_id); + } + } + + WindowTree* tree = window_server()->GetTreeWithId(client_id); + + // TOOD(sad): Adjust this delay, possibly make this dynamic. + const base::TimeDelta max_delay = base::debug::BeingDebugged() + ? base::TimeDelta::FromDays(1) + : GetDefaultAckTimerDelay(); + event_ack_timer_.Start( + FROM_HERE, max_delay, + base::Bind(&WindowManagerState::OnEventAckTimeout, + weak_factory_.GetWeakPtr(), tree->id())); + + tree_awaiting_input_ack_ = tree; + if (accelerator) { + event_awaiting_input_ack_ = ui::Event::Clone(event); + post_target_accelerator_ = accelerator; + } + + // Ignore |tree| because it will receive the event via normal dispatch. + window_server()->SendToEventObservers(event, user_id(), tree); + + tree->DispatchInputEvent(target, event); +} + +void WindowManagerState::AddDebugAccelerators() { + // Always register the accelerators, even if they only work in debug, so that + // keyboard behavior is the same in release and debug builds. + mojom::EventMatcherPtr matcher = CreateKeyMatcher( + ui::mojom::KeyboardCode::S, ui::mojom::kEventFlagControlDown | + ui::mojom::kEventFlagAltDown | + ui::mojom::kEventFlagShiftDown); + event_dispatcher_.AddAccelerator(kPrintWindowsDebugAcceleratorId, + std::move(matcher)); +} + +bool WindowManagerState::HandleDebugAccelerator(uint32_t accelerator_id) { +#if !defined(NDEBUG) + if (accelerator_id == kPrintWindowsDebugAcceleratorId) { + // Error so it will be collected in system logs. + for (Display* display : display_manager()->displays()) { + WindowManagerDisplayRoot* display_root = + display->GetWindowManagerDisplayRootForUser(user_id()); + if (display_root) { + LOG(ERROR) << "ServerWindow hierarchy:\n" + << display_root->root()->GetDebugWindowHierarchy(); + } + } + return true; + } +#endif + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// EventDispatcherDelegate: + +void WindowManagerState::OnAccelerator(uint32_t accelerator_id, + const ui::Event& event) { + DCHECK(IsActive()); + if (HandleDebugAccelerator(accelerator_id)) + return; + window_tree_->OnAccelerator(accelerator_id, event); +} + +void WindowManagerState::SetFocusedWindowFromEventDispatcher( + ServerWindow* new_focused_window) { + DCHECK(IsActive()); + window_server()->SetFocusedWindow(new_focused_window); +} + +ServerWindow* WindowManagerState::GetFocusedWindowForEventDispatcher() { + return window_server()->GetFocusedWindow(); +} + +void WindowManagerState::SetNativeCapture(ServerWindow* window) { + DCHECK(IsActive()); + WindowManagerDisplayRoot* display_root = + display_manager()->GetWindowManagerDisplayRoot(window); + DCHECK(display_root); + platform_display_with_capture_ = display_root->display()->platform_display(); + platform_display_with_capture_->SetCapture(); +} + +void WindowManagerState::ReleaseNativeCapture() { + // Tests trigger calling this without a corresponding SetNativeCapture(). + // TODO(sky): maybe abstract this away so that DCHECK can be added? + if (!platform_display_with_capture_) + return; + + platform_display_with_capture_->ReleaseCapture(); + platform_display_with_capture_ = nullptr; +} + +void WindowManagerState::OnServerWindowCaptureLost(ServerWindow* window) { + DCHECK(window); + window_server()->ProcessLostCapture(window); +} + +void WindowManagerState::OnMouseCursorLocationChanged(const gfx::Point& point) { + window_server() + ->display_manager() + ->GetUserDisplayManager(user_id()) + ->OnMouseCursorLocationChanged(point); +} + +void WindowManagerState::DispatchInputEventToWindow(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + Accelerator* accelerator) { + DCHECK(IsActive()); + // TODO(sky): this needs to see if another wms has capture and if so forward + // to it. + if (event_ack_timer_.IsRunning()) { + std::unique_ptr<ProcessedEventTarget> processed_event_target( + new ProcessedEventTarget(target, client_id, accelerator)); + QueueEvent(event, std::move(processed_event_target)); + return; + } + + base::WeakPtr<Accelerator> weak_accelerator; + if (accelerator) + weak_accelerator = accelerator->GetWeakPtr(); + DispatchInputEventToWindowImpl(target, client_id, event, weak_accelerator); +} + +ClientSpecificId WindowManagerState::GetEventTargetClientId( + const ServerWindow* window, + bool in_nonclient_area) { + // If the event is in the non-client area the event goes to the owner of + // the window. + WindowTree* tree = nullptr; + if (in_nonclient_area) { + tree = window_server()->GetTreeWithId(window->id().client_id); + } else { + // If the window is an embed root, forward to the embedded window. + tree = window_server()->GetTreeWithRoot(window); + if (!tree) + tree = window_server()->GetTreeWithId(window->id().client_id); + } + + const ServerWindow* embed_root = + tree->HasRoot(window) ? window : GetEmbedRoot(window); + while (tree && tree->embedder_intercepts_events()) { + DCHECK(tree->HasRoot(embed_root)); + tree = window_server()->GetTreeWithId(embed_root->id().client_id); + embed_root = GetEmbedRoot(embed_root); + } + + if (!tree) { + DCHECK(in_nonclient_area); + tree = window_tree_; + } + return tree->id(); +} + +ServerWindow* WindowManagerState::GetRootWindowContaining( + const gfx::Point& location) { + if (display_manager()->displays().empty()) + return nullptr; + + // TODO(sky): this isn't right. To correctly implement need bounds of + // Display, which we aren't tracking yet. For now, use the first display. + Display* display = *(display_manager()->displays().begin()); + WindowManagerDisplayRoot* display_root = + display->GetWindowManagerDisplayRootForUser(user_id()); + return display_root ? display_root->root() : nullptr; +} + +void WindowManagerState::OnEventTargetNotFound(const ui::Event& event) { + window_server()->SendToEventObservers(event, user_id(), + nullptr /* ignore_tree */); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_state.h b/chromium/components/mus/ws/window_manager_state.h new file mode 100644 index 00000000000..dca6670d400 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_state.h @@ -0,0 +1,183 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_STATE_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_STATE_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" +#include "components/mus/public/interfaces/display.mojom.h" +#include "components/mus/ws/event_dispatcher.h" +#include "components/mus/ws/event_dispatcher_delegate.h" +#include "components/mus/ws/user_id.h" +#include "components/mus/ws/window_server.h" + +namespace mus { +namespace ws { + +class DisplayManager; +class WindowTree; + +namespace test { +class WindowManagerStateTestApi; +} + +// Manages state specific to a WindowManager that is shared across displays. +// WindowManagerState is owned by the WindowTree the window manager is +// associated with. +class WindowManagerState : public EventDispatcherDelegate { + public: + explicit WindowManagerState(WindowTree* window_tree); + ~WindowManagerState() override; + + const UserId& user_id() const; + + WindowTree* window_tree() { return window_tree_; } + const WindowTree* window_tree() const { return window_tree_; } + + void OnWillDestroyTree(WindowTree* tree); + + void SetFrameDecorationValues(mojom::FrameDecorationValuesPtr values); + const mojom::FrameDecorationValues& frame_decoration_values() const { + return *frame_decoration_values_; + } + bool got_frame_decoration_values() const { + return got_frame_decoration_values_; + } + + bool SetCapture(ServerWindow* window, ClientSpecificId client_id); + ServerWindow* capture_window() { return event_dispatcher_.capture_window(); } + const ServerWindow* capture_window() const { + return event_dispatcher_.capture_window(); + } + + void ReleaseCaptureBlockedByModalWindow(const ServerWindow* modal_window); + void ReleaseCaptureBlockedByAnyModalWindow(); + + void AddSystemModalWindow(ServerWindow* window); + + // TODO(sky): EventDispatcher is really an implementation detail and should + // not be exposed. + EventDispatcher* event_dispatcher() { return &event_dispatcher_; } + + // Returns true if this is the WindowManager of the active user. + bool IsActive() const; + + void Activate(const gfx::Point& mouse_location_on_screen); + void Deactivate(); + + // Processes an event from PlatformDisplay. + void ProcessEvent(const ui::Event& event); + + // Called when the ack from an event dispatched to WindowTree |tree| is + // received. + // TODO(sky): make this private and use a callback. + void OnEventAck(mojom::WindowTree* tree, mojom::EventResult result); + + private: + class ProcessedEventTarget; + friend class Display; + friend class test::WindowManagerStateTestApi; + + // There are two types of events that may be queued, both occur only when + // waiting for an ack from a client. + // . We get an event from the PlatformDisplay. This results in |event| being + // set, but |processed_target| is null. + // . We get an event from the EventDispatcher. In this case both |event| and + // |processed_target| are valid. + // The second case happens if EventDispatcher generates more than one event + // at a time. + struct QueuedEvent { + QueuedEvent(); + ~QueuedEvent(); + + std::unique_ptr<ui::Event> event; + std::unique_ptr<ProcessedEventTarget> processed_target; + }; + + const WindowServer* window_server() const; + WindowServer* window_server(); + + DisplayManager* display_manager(); + const DisplayManager* display_manager() const; + + // Sets the visibility of all window manager roots windows to |value|. + void SetAllRootWindowsVisible(bool value); + + // Returns the ServerWindow that is the root of the WindowManager for + // |window|. |window| corresponds to the root of a Display. + ServerWindow* GetWindowManagerRoot(ServerWindow* window); + + void OnEventAckTimeout(ClientSpecificId client_id); + + // Schedules an event to be processed later. + void QueueEvent(const ui::Event& event, + std::unique_ptr<ProcessedEventTarget> processed_event_target); + + // Processes the next valid event in |event_queue_|. If the event has already + // been processed it is dispatched, otherwise the event is passed to the + // EventDispatcher for processing. + void ProcessNextEventFromQueue(); + + // Dispatches the event to the appropriate client and starts the ack timer. + void DispatchInputEventToWindowImpl(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + base::WeakPtr<Accelerator> accelerator); + + // Registers accelerators used internally for debugging. + void AddDebugAccelerators(); + + // Returns true if the accelerator was handled. + bool HandleDebugAccelerator(uint32_t accelerator_id); + + // EventDispatcherDelegate: + void OnAccelerator(uint32_t accelerator_id, const ui::Event& event) override; + void SetFocusedWindowFromEventDispatcher(ServerWindow* window) override; + ServerWindow* GetFocusedWindowForEventDispatcher() override; + void SetNativeCapture(ServerWindow* window) override; + void ReleaseNativeCapture() override; + void OnServerWindowCaptureLost(ServerWindow* window) override; + void OnMouseCursorLocationChanged(const gfx::Point& point) override; + void DispatchInputEventToWindow(ServerWindow* target, + ClientSpecificId client_id, + const ui::Event& event, + Accelerator* accelerator) override; + ClientSpecificId GetEventTargetClientId(const ServerWindow* window, + bool in_nonclient_area) override; + ServerWindow* GetRootWindowContaining(const gfx::Point& location) override; + void OnEventTargetNotFound(const ui::Event& event) override; + + // The single WindowTree this WindowManagerState is associated with. + // |window_tree_| owns this. + WindowTree* window_tree_; + + // Set to true the first time SetFrameDecorationValues() is called. + bool got_frame_decoration_values_ = false; + mojom::FrameDecorationValuesPtr frame_decoration_values_; + + mojom::WindowTree* tree_awaiting_input_ack_ = nullptr; + std::unique_ptr<ui::Event> event_awaiting_input_ack_; + base::WeakPtr<Accelerator> post_target_accelerator_; + std::queue<std::unique_ptr<QueuedEvent>> event_queue_; + base::OneShotTimer event_ack_timer_; + + EventDispatcher event_dispatcher_; + + // PlatformDisplay that currently has capture. + PlatformDisplay* platform_display_with_capture_ = nullptr; + + base::WeakPtrFactory<WindowManagerState> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerState); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_STATE_H_ diff --git a/chromium/components/mus/ws/window_manager_state_unittest.cc b/chromium/components/mus/ws/window_manager_state_unittest.cc new file mode 100644 index 00000000000..20980f75b4c --- /dev/null +++ b/chromium/components/mus/ws/window_manager_state_unittest.cc @@ -0,0 +1,419 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_manager_state.h" + +#include <memory> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/test/test_simple_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "components/mus/common/event_matcher_util.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/accelerator.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/test_change_tracker.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "components/mus/ws/test_utils.h" +#include "components/mus/ws/window_manager_access_policy.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" +#include "services/shell/public/interfaces/connector.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" + +namespace mus { +namespace ws { +namespace test { + +class WindowManagerStateTest : public testing::Test { + public: + WindowManagerStateTest(); + ~WindowManagerStateTest() override {} + + std::unique_ptr<Accelerator> CreateAccelerator(); + + // Creates a child |server_window| with associataed |window_tree| and + // |test_client|. The window is setup for processing input. + void CreateSecondaryTree(TestWindowTreeClient** test_client, + WindowTree** window_tree, + ServerWindow** server_window); + + void DispatchInputEventToWindow(ServerWindow* target, + const ui::Event& event, + Accelerator* accelerator); + void OnEventAckTimeout(ClientSpecificId client_id); + + WindowTree* tree() { + return window_event_targeting_helper_.window_server()->GetTreeWithId(1); + } + WindowTree* window_tree() { return window_tree_; } + TestWindowTreeClient* window_tree_client() { return window_tree_client_; } + ServerWindow* window() { return window_; } + TestWindowManager* window_manager() { return &window_manager_; } + TestWindowTreeClient* wm_client() { + return window_event_targeting_helper_.wm_client(); + } + TestWindowTreeClient* last_tree_client() { + return window_event_targeting_helper_.last_window_tree_client(); + } + WindowTree* last_tree() { + return window_event_targeting_helper_.last_binding()->tree(); + } + WindowManagerState* window_manager_state() { return window_manager_state_; } + + void EmbedAt(WindowTree* tree, + const ClientWindowId& embed_window_id, + uint32_t embed_flags, + WindowTree** embed_tree, + TestWindowTreeClient** embed_client_proxy) { + mojom::WindowTreeClientPtr embed_client; + mojom::WindowTreeClientRequest client_request = GetProxy(&embed_client); + ASSERT_TRUE( + tree->Embed(embed_window_id, std::move(embed_client), embed_flags)); + TestWindowTreeClient* client = + window_event_targeting_helper_.last_window_tree_client(); + ASSERT_EQ(1u, client->tracker()->changes()->size()); + EXPECT_EQ(CHANGE_TYPE_EMBED, (*client->tracker()->changes())[0].type); + client->tracker()->changes()->clear(); + *embed_client_proxy = client; + *embed_tree = window_event_targeting_helper_.last_binding()->tree(); + } + + // testing::Test: + void SetUp() override; + + private: + WindowEventTargetingHelper window_event_targeting_helper_; + + WindowManagerState* window_manager_state_; + + // Handles WindowStateManager ack timeouts. + scoped_refptr<base::TestSimpleTaskRunner> task_runner_; + TestWindowManager window_manager_; + ServerWindow* window_ = nullptr; + WindowTree* window_tree_ = nullptr; + TestWindowTreeClient* window_tree_client_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerStateTest); +}; + +WindowManagerStateTest::WindowManagerStateTest() + : task_runner_(new base::TestSimpleTaskRunner) {} + +std::unique_ptr<Accelerator> WindowManagerStateTest::CreateAccelerator() { + mojom::EventMatcherPtr matcher = mus::CreateKeyMatcher( + ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown); + matcher->accelerator_phase = ui::mojom::AcceleratorPhase::POST_TARGET; + uint32_t accelerator_id = 1; + std::unique_ptr<Accelerator> accelerator( + new Accelerator(accelerator_id, *matcher)); + return accelerator; +} + +void WindowManagerStateTest::CreateSecondaryTree( + TestWindowTreeClient** test_client, + WindowTree** window_tree, + ServerWindow** server_window) { + window_event_targeting_helper_.CreateSecondaryTree( + window_, gfx::Rect(20, 20, 20, 20), test_client, window_tree, + server_window); +} + +void WindowManagerStateTest::DispatchInputEventToWindow( + ServerWindow* target, + const ui::Event& event, + Accelerator* accelerator) { + WindowManagerStateTestApi test_api(window_manager_state_); + ClientSpecificId client_id = test_api.GetEventTargetClientId(target, false); + test_api.DispatchInputEventToWindow(target, client_id, event, accelerator); +} + +void WindowManagerStateTest::OnEventAckTimeout( + ClientSpecificId client_id) { + WindowManagerStateTestApi test_api(window_manager_state_); + test_api.OnEventAckTimeout(client_id); +} + +void WindowManagerStateTest::SetUp() { + window_event_targeting_helper_.SetTaskRunner(task_runner_); + window_manager_state_ = window_event_targeting_helper_.display() + ->GetActiveWindowManagerDisplayRoot() + ->window_manager_state(); + window_ = window_event_targeting_helper_.CreatePrimaryTree( + gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 50, 50)); + window_tree_ = window_event_targeting_helper_.last_binding()->tree(); + window_tree_client_ = + window_event_targeting_helper_.last_window_tree_client(); + DCHECK(window_tree_->HasRoot(window_)); + + WindowTreeTestApi(tree()).set_window_manager_internal(&window_manager_); + wm_client()->tracker()->changes()->clear(); + window_tree_client_->tracker()->changes()->clear(); +} + +// Tests that when an event is dispatched with no accelerator, that post target +// accelerator is not triggered. +TEST_F(WindowManagerStateTest, NullAccelerator) { + WindowManagerState* state = window_manager_state(); + EXPECT_TRUE(state); + + ServerWindow* target = window(); + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + DispatchInputEventToWindow(target, key, nullptr); + WindowTree* target_tree = window_tree(); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + WindowTreeTestApi(target_tree).AckOldestEvent(); + EXPECT_FALSE(window_manager()->on_accelerator_called()); +} + +// Tests that when a post target accelerator is provided on an event, that it is +// called on ack. +TEST_F(WindowManagerStateTest, PostTargetAccelerator) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator = CreateAccelerator(); + + ServerWindow* target = window(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + WindowTreeTestApi(window_tree()).AckOldestEvent(); + EXPECT_TRUE(window_manager()->on_accelerator_called()); + EXPECT_EQ(accelerator->id(), window_manager()->on_accelerator_id()); +} + +// Tests that when a client handles an event that post target accelerators are +// not called. +TEST_F(WindowManagerStateTest, ClientHandlesEvent) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator = CreateAccelerator(); + + ServerWindow* target = window(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + window_manager_state()->OnEventAck(tree(), mojom::EventResult::HANDLED); + EXPECT_FALSE(window_manager()->on_accelerator_called()); +} + +// Tests that when an accelerator is deleted before an ack, that it is not +// called. +TEST_F(WindowManagerStateTest, AcceleratorDeleted) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator(CreateAccelerator()); + + ServerWindow* target = window(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + accelerator.reset(); + window_manager_state()->OnEventAck(tree(), mojom::EventResult::UNHANDLED); + EXPECT_FALSE(window_manager()->on_accelerator_called()); +} + +// Tests that a events arriving before an ack don't notify the tree until the +// ack arrives, and that the correct accelerator is called. +TEST_F(WindowManagerStateTest, EnqueuedAccelerators) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator(CreateAccelerator()); + + ServerWindow* target = window(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + tracker->changes()->clear(); + ui::KeyEvent key2(ui::ET_KEY_PRESSED, ui::VKEY_Y, ui::EF_CONTROL_DOWN); + mojom::EventMatcherPtr matcher = mus::CreateKeyMatcher( + ui::mojom::KeyboardCode::Y, ui::mojom::kEventFlagControlDown); + matcher->accelerator_phase = ui::mojom::AcceleratorPhase::POST_TARGET; + uint32_t accelerator_id = 2; + std::unique_ptr<Accelerator> accelerator2( + new Accelerator(accelerator_id, *matcher)); + DispatchInputEventToWindow(target, key2, accelerator2.get()); + EXPECT_TRUE(tracker->changes()->empty()); + + WindowTreeTestApi(window_tree()).AckOldestEvent(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + EXPECT_TRUE(window_manager()->on_accelerator_called()); + EXPECT_EQ(accelerator->id(), window_manager()->on_accelerator_id()); +} + +// Tests that the accelerator is not sent when the tree is dying. +TEST_F(WindowManagerStateTest, DeleteTree) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator = CreateAccelerator(); + + ServerWindow* target = window(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + window_manager_state()->OnWillDestroyTree(tree()); + EXPECT_FALSE(window_manager()->on_accelerator_called()); +} + +// Tests that if a tree is destroyed before acking, that the accelerator is +// still sent if it is not the root tree. +TEST_F(WindowManagerStateTest, DeleteNonRootTree) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* target_tree = nullptr; + ServerWindow* target = nullptr; + CreateSecondaryTree(&embed_connection, &target_tree, &target); + TestWindowManager target_window_manager; + WindowTreeTestApi(target_tree) + .set_window_manager_internal(&target_window_manager); + + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator = CreateAccelerator(); + DispatchInputEventToWindow(target, key, accelerator.get()); + TestChangeTracker* tracker = embed_connection->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=2,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + EXPECT_TRUE(wm_client()->tracker()->changes()->empty()); + + window_manager_state()->OnWillDestroyTree(target_tree); + EXPECT_FALSE(target_window_manager.on_accelerator_called()); + EXPECT_TRUE(window_manager()->on_accelerator_called()); +} + +// Tests that when an ack times out that the accelerator is notified. +TEST_F(WindowManagerStateTest, AckTimeout) { + ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN); + std::unique_ptr<Accelerator> accelerator = CreateAccelerator(); + DispatchInputEventToWindow(window(), key, accelerator.get()); + TestChangeTracker* tracker = window_tree_client()->tracker(); + ASSERT_EQ(1u, tracker->changes()->size()); + EXPECT_EQ("InputEvent window=1,1 event_action=7", + ChangesToDescription1(*tracker->changes())[0]); + + OnEventAckTimeout(window()->id().client_id); + EXPECT_TRUE(window_manager()->on_accelerator_called()); + EXPECT_EQ(accelerator->id(), window_manager()->on_accelerator_id()); +} + +TEST_F(WindowManagerStateTest, InterceptingEmbedderReceivesEvents) { + WindowTree* embedder_tree = tree(); + ServerWindow* embedder_root = window(); + const ClientWindowId embed_window_id( + WindowIdToTransportId(WindowId(embedder_tree->id(), 12))); + embedder_tree->NewWindow(embed_window_id, ServerWindow::Properties()); + ServerWindow* embedder_window = + embedder_tree->GetWindowByClientId(embed_window_id); + ASSERT_TRUE(embedder_tree->AddWindow( + ClientWindowId(WindowIdToTransportId(embedder_root->id())), + embed_window_id)); + + TestWindowTreeClient* embedder_client = wm_client(); + + { + // Do a normal embed. + const uint32_t embed_flags = 0; + WindowTree* embed_tree = nullptr; + TestWindowTreeClient* embed_client_proxy = nullptr; + EmbedAt(embedder_tree, embed_window_id, embed_flags, &embed_tree, + &embed_client_proxy); + ASSERT_TRUE(embed_client_proxy); + + // Send an event to the embed window. It should go to the embedded client. + ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), + base::TimeTicks(), 0, 0); + DispatchInputEventToWindow(embedder_window, mouse, nullptr); + ASSERT_EQ(1u, embed_client_proxy->tracker()->changes()->size()); + EXPECT_EQ(CHANGE_TYPE_INPUT_EVENT, + (*embed_client_proxy->tracker()->changes())[0].type); + WindowTreeTestApi(embed_tree).AckLastEvent(mojom::EventResult::UNHANDLED); + embed_client_proxy->tracker()->changes()->clear(); + } + + { + // Do an embed where the embedder wants to intercept events to the embedded + // tree. + const uint32_t embed_flags = mojom::kEmbedFlagEmbedderInterceptsEvents; + WindowTree* embed_tree = nullptr; + TestWindowTreeClient* embed_client_proxy = nullptr; + EmbedAt(embedder_tree, embed_window_id, embed_flags, &embed_tree, + &embed_client_proxy); + ASSERT_TRUE(embed_client_proxy); + embedder_client->tracker()->changes()->clear(); + + // Send an event to the embed window. But this time, it should reach the + // embedder. + ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), + base::TimeTicks(), 0, 0); + DispatchInputEventToWindow(embedder_window, mouse, nullptr); + ASSERT_EQ(0u, embed_client_proxy->tracker()->changes()->size()); + ASSERT_EQ(1u, embedder_client->tracker()->changes()->size()); + EXPECT_EQ(CHANGE_TYPE_INPUT_EVENT, + (*embedder_client->tracker()->changes())[0].type); + WindowTreeTestApi(embedder_tree) + .AckLastEvent(mojom::EventResult::UNHANDLED); + embedder_client->tracker()->changes()->clear(); + + // Embed another tree in the embedded tree. + const ClientWindowId nested_embed_window_id( + WindowIdToTransportId(WindowId(embed_tree->id(), 23))); + embed_tree->NewWindow(nested_embed_window_id, ServerWindow::Properties()); + const ClientWindowId embed_root_id( + WindowIdToTransportId((*embed_tree->roots().begin())->id())); + ASSERT_TRUE(embed_tree->AddWindow(embed_root_id, nested_embed_window_id)); + + WindowTree* nested_embed_tree = nullptr; + TestWindowTreeClient* nested_embed_client_proxy = nullptr; + EmbedAt(embed_tree, nested_embed_window_id, embed_flags, &nested_embed_tree, + &nested_embed_client_proxy); + ASSERT_TRUE(nested_embed_client_proxy); + embed_client_proxy->tracker()->changes()->clear(); + embedder_client->tracker()->changes()->clear(); + + // Send an event to the nested embed window. The event should still reach + // the outermost embedder. + ServerWindow* nested_embed_window = + embed_tree->GetWindowByClientId(nested_embed_window_id); + DCHECK(nested_embed_window->parent()); + mouse = ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(), + base::TimeTicks(), 0, 0); + DispatchInputEventToWindow(nested_embed_window, mouse, nullptr); + ASSERT_EQ(0u, nested_embed_client_proxy->tracker()->changes()->size()); + ASSERT_EQ(0u, embed_client_proxy->tracker()->changes()->size()); + + ASSERT_EQ(1u, embedder_client->tracker()->changes()->size()); + EXPECT_EQ(CHANGE_TYPE_INPUT_EVENT, + (*embedder_client->tracker()->changes())[0].type); + WindowTreeTestApi(embedder_tree) + .AckLastEvent(mojom::EventResult::UNHANDLED); + } +} + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_window_tree_factory.cc b/chromium/components/mus/ws/window_manager_window_tree_factory.cc new file mode 100644 index 00000000000..a851092bd48 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_window_tree_factory.cc @@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_manager_window_tree_factory.h" + +#include "base/bind.h" +#include "components/mus/ws/window_manager_window_tree_factory_set.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" + +namespace mus { +namespace ws { + +WindowManagerWindowTreeFactory::WindowManagerWindowTreeFactory( + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set, + const UserId& user_id, + mojo::InterfaceRequest<mojom::WindowManagerWindowTreeFactory> request) + : window_manager_window_tree_factory_set_( + window_manager_window_tree_factory_set), + user_id_(user_id), + binding_(this), + window_tree_(nullptr) { + if (request.is_pending()) + binding_.Bind(std::move(request)); +} + +WindowManagerWindowTreeFactory::~WindowManagerWindowTreeFactory() {} + +void WindowManagerWindowTreeFactory::CreateWindowTree( + mojom::WindowTreeRequest window_tree_request, + mojom::WindowTreeClientPtr window_tree_client) { + // CreateWindowTree() can only be called once, so there is no reason to keep + // the binding around. + if (binding_.is_bound()) + binding_.Close(); + + SetWindowTree(GetWindowServer()->CreateTreeForWindowManager( + user_id_, std::move(window_tree_request), std::move(window_tree_client))); +} + +WindowManagerWindowTreeFactory::WindowManagerWindowTreeFactory( + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set, + const UserId& user_id) + : window_manager_window_tree_factory_set_( + window_manager_window_tree_factory_set), + user_id_(user_id), + binding_(this), + window_tree_(nullptr) {} + +WindowServer* WindowManagerWindowTreeFactory::GetWindowServer() { + return window_manager_window_tree_factory_set_->window_server(); +} + +void WindowManagerWindowTreeFactory::SetWindowTree(WindowTree* window_tree) { + DCHECK(!window_tree_); + window_tree_ = window_tree; + + window_manager_window_tree_factory_set_ + ->OnWindowManagerWindowTreeFactoryReady(this); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_window_tree_factory.h b/chromium/components/mus/ws/window_manager_window_tree_factory.h new file mode 100644 index 00000000000..909e3391d7f --- /dev/null +++ b/chromium/components/mus/ws/window_manager_window_tree_factory.h @@ -0,0 +1,68 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_H_ + +#include <stdint.h> + +#include "components/mus/public/interfaces/window_manager_window_tree_factory.mojom.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace ws { + +class ServerWindow; +class WindowManagerWindowTreeFactorySet; +class WindowServer; +class WindowTree; + +namespace test { +class WindowManagerWindowTreeFactorySetTestApi; +} + +// Implementation of mojom::WindowManagerWindowTreeFactory. +class WindowManagerWindowTreeFactory + : public mojom::WindowManagerWindowTreeFactory { + public: + WindowManagerWindowTreeFactory( + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set, + const UserId& user_id, + mojo::InterfaceRequest<mojom::WindowManagerWindowTreeFactory> request); + ~WindowManagerWindowTreeFactory() override; + + const UserId& user_id() const { return user_id_; } + + WindowTree* window_tree() { return window_tree_; } + + // mojom::WindowManagerWindowTreeFactory: + void CreateWindowTree(mojom::WindowTreeRequest window_tree_request, + mojom::WindowTreeClientPtr window_tree_client) override; + + private: + friend class test::WindowManagerWindowTreeFactorySetTestApi; + + // Used by tests. + WindowManagerWindowTreeFactory(WindowManagerWindowTreeFactorySet* registry, + const UserId& user_id); + + WindowServer* GetWindowServer(); + + void SetWindowTree(WindowTree* window_tree); + + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set_; + const UserId user_id_; + mojo::Binding<mojom::WindowManagerWindowTreeFactory> binding_; + + // Owned by WindowServer. + WindowTree* window_tree_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerWindowTreeFactory); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_H_ diff --git a/chromium/components/mus/ws/window_manager_window_tree_factory_set.cc b/chromium/components/mus/ws/window_manager_window_tree_factory_set.cc new file mode 100644 index 00000000000..91d01a8ab47 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_window_tree_factory_set.cc @@ -0,0 +1,99 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_manager_window_tree_factory_set.h" + +#include "components/mus/ws/user_id_tracker_observer.h" +#include "components/mus/ws/window_manager_window_tree_factory.h" +#include "components/mus/ws/window_manager_window_tree_factory_set_observer.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" + +namespace mus { +namespace ws { + +WindowManagerWindowTreeFactorySet::WindowManagerWindowTreeFactorySet( + WindowServer* window_server, + UserIdTracker* id_tracker) + : id_tracker_(id_tracker), window_server_(window_server) { + id_tracker_->AddObserver(this); +} + +WindowManagerWindowTreeFactorySet::~WindowManagerWindowTreeFactorySet() { + id_tracker_->RemoveObserver(this); +} + +WindowManagerWindowTreeFactory* WindowManagerWindowTreeFactorySet::Add( + const UserId& user_id, + mojo::InterfaceRequest<mojom::WindowManagerWindowTreeFactory> request) { + if (factories_.count(user_id)) { + DVLOG(1) << "can only have one factory per user"; + return nullptr; + } + + std::unique_ptr<WindowManagerWindowTreeFactory> factory_ptr( + new WindowManagerWindowTreeFactory(this, user_id, std::move(request))); + WindowManagerWindowTreeFactory* factory = factory_ptr.get(); + factories_[user_id] = std::move(factory_ptr); + return factory; +} + +WindowManagerState* +WindowManagerWindowTreeFactorySet::GetWindowManagerStateForUser( + const UserId& user_id) { + auto it = factories_.find(user_id); + if (it == factories_.end()) + return nullptr; + return it->second->window_tree() + ? it->second->window_tree()->window_manager_state() + : nullptr; +} + +void WindowManagerWindowTreeFactorySet::DeleteFactoryAssociatedWithTree( + WindowTree* window_tree) { + for (auto it = factories_.begin(); it != factories_.end(); ++it) { + if (it->second->window_tree() == window_tree) { + factories_.erase(it); + return; + } + } +} + +std::vector<WindowManagerWindowTreeFactory*> +WindowManagerWindowTreeFactorySet::GetFactories() { + std::vector<WindowManagerWindowTreeFactory*> result; + for (auto& pair : factories_) + result.push_back(pair.second.get()); + return result; +} + +void WindowManagerWindowTreeFactorySet::AddObserver( + WindowManagerWindowTreeFactorySetObserver* observer) { + observers_.AddObserver(observer); +} + +void WindowManagerWindowTreeFactorySet::RemoveObserver( + WindowManagerWindowTreeFactorySetObserver* observer) { + observers_.RemoveObserver(observer); +} + +void WindowManagerWindowTreeFactorySet::OnWindowManagerWindowTreeFactoryReady( + WindowManagerWindowTreeFactory* factory) { + const bool is_first_valid_factory = !got_valid_factory_; + got_valid_factory_ = true; + FOR_EACH_OBSERVER(WindowManagerWindowTreeFactorySetObserver, observers_, + OnWindowManagerWindowTreeFactoryReady(factory)); + + // Notify after other observers as WindowServer triggers other + // observers being added, which will have already processed the add. + if (is_first_valid_factory) + window_server_->OnFirstWindowManagerWindowTreeFactoryReady(); +} + +void WindowManagerWindowTreeFactorySet::OnUserIdRemoved(const UserId& id) { + factories_.erase(id); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_manager_window_tree_factory_set.h b/chromium/components/mus/ws/window_manager_window_tree_factory_set.h new file mode 100644 index 00000000000..a118e6978bc --- /dev/null +++ b/chromium/components/mus/ws/window_manager_window_tree_factory_set.h @@ -0,0 +1,93 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "base/observer_list.h" +#include "components/mus/public/interfaces/window_manager_window_tree_factory.mojom.h" +#include "components/mus/ws/user_id_tracker_observer.h" + +namespace mus { +namespace ws { + +class UserIdTracker; +class WindowManagerState; +class WindowManagerWindowTreeFactory; +class WindowManagerWindowTreeFactorySetObserver; +class WindowServer; +class WindowTree; + +namespace test { +class WindowManagerWindowTreeFactorySetTestApi; +} + +// WindowManagerWindowTreeFactorySet tracks the set of registered +// WindowManagerWindowTreeHostFactories. +class WindowManagerWindowTreeFactorySet : public UserIdTrackerObserver { + public: + WindowManagerWindowTreeFactorySet(WindowServer* window_server, + UserIdTracker* tracker); + ~WindowManagerWindowTreeFactorySet() override; + + WindowServer* window_server() { return window_server_; } + + // Creates a new WindowManagerWindowTreeFactory for the specified user, + // unless one has been set, in which case the call is ignored. The newly + // created WindowManagerWindowTreeFactory does not immediately have a + // WindowTree associated with it. + WindowManagerWindowTreeFactory* Add( + const UserId& user_id, + mojo::InterfaceRequest<mojom::WindowManagerWindowTreeFactory> request); + + // Returns the WindowManagerState for the specified user, or null if + // not yet set. + WindowManagerState* GetWindowManagerStateForUser(const UserId& user_id); + + // Deletes the WindowManagerWindowTreeFactory associated with |tree|. Does + // nothing if there is no WindowManagerWindowTreeFactory associated with + // |tree|. + void DeleteFactoryAssociatedWithTree(WindowTree* tree); + + // Returns all the factories, even those that may not have a WindowTree + // associated with them. + std::vector<WindowManagerWindowTreeFactory*> GetFactories(); + + void AddObserver(WindowManagerWindowTreeFactorySetObserver* observer); + void RemoveObserver(WindowManagerWindowTreeFactorySetObserver* observer); + + private: + friend class WindowManagerWindowTreeFactory; + friend class test::WindowManagerWindowTreeFactorySetTestApi; + + // Called by WindowManagerWindowTreeFactory when CreateWindowTree() has + // been called. + void OnWindowManagerWindowTreeFactoryReady( + WindowManagerWindowTreeFactory* factory); + + // UserIdTrackerObserver: + void OnUserIdRemoved(const UserId& id) override; + + // Set to true the first time a valid factory has been found. + bool got_valid_factory_ = false; + UserIdTracker* id_tracker_; + WindowServer* window_server_; + + std::map<UserId, std::unique_ptr<WindowManagerWindowTreeFactory>> factories_; + + base::ObserverList<WindowManagerWindowTreeFactorySetObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerWindowTreeFactorySet); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_H_ diff --git a/chromium/components/mus/ws/window_manager_window_tree_factory_set_observer.h b/chromium/components/mus/ws/window_manager_window_tree_factory_set_observer.h new file mode 100644 index 00000000000..dd5f876d839 --- /dev/null +++ b/chromium/components/mus/ws/window_manager_window_tree_factory_set_observer.h @@ -0,0 +1,26 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_OBSERVER_H_ +#define COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_OBSERVER_H_ + +namespace mus { +namespace ws { + +class WindowManagerWindowTreeFactory; + +class WindowManagerWindowTreeFactorySetObserver { + public: + // Called when the WindowTree associated with |factory| has been set + virtual void OnWindowManagerWindowTreeFactoryReady( + WindowManagerWindowTreeFactory* factory) = 0; + + protected: + virtual ~WindowManagerWindowTreeFactorySetObserver() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_MANAGER_WINDOW_TREE_FACTORY_SET_OBSERVER_H_ diff --git a/chromium/components/mus/ws/window_server.cc b/chromium/components/mus/ws/window_server.cc new file mode 100644 index 00000000000..77ef0e2595c --- /dev/null +++ b/chromium/components/mus/ws/window_server.cc @@ -0,0 +1,707 @@ +// 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 "components/mus/ws/window_server.h" + +#include <set> +#include <string> + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/stl_util.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/operation.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/user_activity_monitor.h" +#include "components/mus/ws/window_coordinate_conversions.h" +#include "components/mus/ws/window_manager_access_policy.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_manager_window_tree_factory.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" +#include "services/shell/public/cpp/connection.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace mus { +namespace ws { + +WindowServer::WindowServer( + WindowServerDelegate* delegate, + const scoped_refptr<mus::SurfacesState>& surfaces_state) + : delegate_(delegate), + surfaces_state_(surfaces_state), + next_client_id_(1), + display_manager_(new DisplayManager(this, &user_id_tracker_)), + current_operation_(nullptr), + in_destructor_(false), + next_wm_change_id_(0), + window_manager_window_tree_factory_set_(this, &user_id_tracker_) { + user_id_tracker_.AddObserver(this); + OnUserIdAdded(user_id_tracker_.active_id()); +} + +WindowServer::~WindowServer() { + in_destructor_ = true; + + // Destroys the window trees results in querying for the display. Tear down + // the displays first so that the trees are notified of the display going + // away while the display is still valid. + display_manager_->DestroyAllDisplays(); + + while (!tree_map_.empty()) + DestroyTree(tree_map_.begin()->second.get()); + + display_manager_.reset(); +} + +ServerWindow* WindowServer::CreateServerWindow( + const WindowId& id, + const std::map<std::string, std::vector<uint8_t>>& properties) { + ServerWindow* window = new ServerWindow(this, id, properties); + window->AddObserver(this); + return window; +} + +ClientSpecificId WindowServer::GetAndAdvanceNextClientId() { + const ClientSpecificId id = next_client_id_++; + DCHECK_LT(id, next_client_id_); + return id; +} + +WindowTree* WindowServer::EmbedAtWindow( + ServerWindow* root, + const UserId& user_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + std::unique_ptr<AccessPolicy> access_policy) { + std::unique_ptr<WindowTree> tree_ptr( + new WindowTree(this, user_id, root, std::move(access_policy))); + WindowTree* tree = tree_ptr.get(); + if (flags & mojom::kEmbedFlagEmbedderInterceptsEvents) + tree->set_embedder_intercepts_events(); + + mojom::WindowTreePtr window_tree_ptr; + mojom::WindowTreeRequest window_tree_request = GetProxy(&window_tree_ptr); + std::unique_ptr<WindowTreeBinding> binding = + delegate_->CreateWindowTreeBinding( + WindowServerDelegate::BindingType::EMBED, this, tree, + &window_tree_request, &client); + if (!binding) { + binding.reset(new ws::DefaultWindowTreeBinding( + tree, this, std::move(window_tree_request), std::move(client))); + } + + AddTree(std::move(tree_ptr), std::move(binding), std::move(window_tree_ptr)); + OnTreeMessagedClient(tree->id()); + return tree; +} + +void WindowServer::AddTree(std::unique_ptr<WindowTree> tree_impl_ptr, + std::unique_ptr<WindowTreeBinding> binding, + mojom::WindowTreePtr tree_ptr) { + CHECK_EQ(0u, tree_map_.count(tree_impl_ptr->id())); + WindowTree* tree = tree_impl_ptr.get(); + tree_map_[tree->id()] = std::move(tree_impl_ptr); + tree->Init(std::move(binding), std::move(tree_ptr)); +} + +WindowTree* WindowServer::CreateTreeForWindowManager( + const UserId& user_id, + mojom::WindowTreeRequest window_tree_request, + mojom::WindowTreeClientPtr window_tree_client) { + std::unique_ptr<WindowTree> window_tree(new WindowTree( + this, user_id, nullptr, base::WrapUnique(new WindowManagerAccessPolicy))); + std::unique_ptr<WindowTreeBinding> window_tree_binding = + delegate_->CreateWindowTreeBinding( + WindowServerDelegate::BindingType::WINDOW_MANAGER, this, + window_tree.get(), &window_tree_request, &window_tree_client); + if (!window_tree_binding) { + window_tree_binding.reset(new DefaultWindowTreeBinding( + window_tree.get(), this, std::move(window_tree_request), + std::move(window_tree_client))); + } + WindowTree* window_tree_ptr = window_tree.get(); + AddTree(std::move(window_tree), std::move(window_tree_binding), nullptr); + window_tree_ptr->ConfigureWindowManager(); + return window_tree_ptr; +} + +void WindowServer::DestroyTree(WindowTree* tree) { + std::unique_ptr<WindowTree> tree_ptr; + { + auto iter = tree_map_.find(tree->id()); + DCHECK(iter != tree_map_.end()); + tree_ptr = std::move(iter->second); + tree_map_.erase(iter); + } + + // Notify remaining connections so that they can cleanup. + for (auto& pair : tree_map_) + pair.second->OnWindowDestroyingTreeImpl(tree); + + // Notify the hosts, taking care to only notify each host once. + std::set<Display*> displays_notified; + for (auto* root : tree->roots()) { + // WindowTree holds its roots as a const, which is right as WindowTree + // doesn't need to modify the window. OTOH we do. We could look up the + // window using the id to get non-const version, but instead we cast. + Display* display = + display_manager_->GetDisplayContaining(const_cast<ServerWindow*>(root)); + if (display && displays_notified.count(display) == 0) { + display->OnWillDestroyTree(tree); + displays_notified.insert(display); + } + } + + window_manager_window_tree_factory_set_.DeleteFactoryAssociatedWithTree(tree); + + // Remove any requests from the client that resulted in a call to the window + // manager and we haven't gotten a response back yet. + std::set<uint32_t> to_remove; + for (auto& pair : in_flight_wm_change_map_) { + if (pair.second.client_id == tree->id()) + to_remove.insert(pair.first); + } + for (uint32_t id : to_remove) + in_flight_wm_change_map_.erase(id); +} + +WindowTree* WindowServer::GetTreeWithId(ClientSpecificId client_id) { + auto iter = tree_map_.find(client_id); + return iter == tree_map_.end() ? nullptr : iter->second.get(); +} + +WindowTree* WindowServer::GetTreeWithClientName( + const std::string& client_name) { + for (const auto& entry : tree_map_) { + if (entry.second->name() == client_name) + return entry.second.get(); + } + return nullptr; +} + +ServerWindow* WindowServer::GetWindow(const WindowId& id) { + // kInvalidClientId is used for Display and WindowManager nodes. + if (id.client_id == kInvalidClientId) { + for (Display* display : display_manager_->displays()) { + ServerWindow* window = display->GetRootWithId(id); + if (window) + return window; + } + } + WindowTree* tree = GetTreeWithId(id.client_id); + return tree ? tree->GetWindow(id) : nullptr; +} + +void WindowServer::SchedulePaint(ServerWindow* window, + const gfx::Rect& bounds) { + Display* display = display_manager_->GetDisplayContaining(window); + if (display) + display->SchedulePaint(window, bounds); +} + +void WindowServer::OnTreeMessagedClient(ClientSpecificId id) { + if (current_operation_) + current_operation_->MarkTreeAsMessaged(id); +} + +bool WindowServer::DidTreeMessageClient(ClientSpecificId id) const { + return current_operation_ && current_operation_->DidMessageTree(id); +} + +const WindowTree* WindowServer::GetTreeWithRoot( + const ServerWindow* window) const { + if (!window) + return nullptr; + for (auto& pair : tree_map_) { + if (pair.second->HasRoot(window)) + return pair.second.get(); + } + return nullptr; +} + +void WindowServer::OnFirstWindowManagerWindowTreeFactoryReady() { + if (display_manager_->has_active_or_pending_displays()) + return; + + // We've been supplied a WindowManagerFactory and no displays have been + // created yet. Treat this as a signal to create a Display. + // TODO(sky): we need a better way to determine this, most likely a switch. + delegate_->CreateDefaultDisplays(); +} + +UserActivityMonitor* WindowServer::GetUserActivityMonitorForUser( + const UserId& user_id) { + DCHECK_GT(activity_monitor_map_.count(user_id), 0u); + return activity_monitor_map_[user_id].get(); +} + +bool WindowServer::SetFocusedWindow(ServerWindow* window) { + // TODO(sky): this should fail if there is modal dialog active and |window| + // is outside that. + ServerWindow* currently_focused = GetFocusedWindow(); + Display* focused_display = + currently_focused + ? display_manager_->GetDisplayContaining(currently_focused) + : nullptr; + if (!window) + return focused_display ? focused_display->SetFocusedWindow(nullptr) : true; + + Display* display = display_manager_->GetDisplayContaining(window); + DCHECK(display); // It's assumed callers do validation before calling this. + const bool result = display->SetFocusedWindow(window); + // If the focus actually changed, and focus was in another display, then we + // need to notify the previously focused display so that it cleans up state + // and notifies appropriately. + if (result && display->GetFocusedWindow() && display != focused_display && + focused_display) { + const bool cleared_focus = focused_display->SetFocusedWindow(nullptr); + DCHECK(cleared_focus); + } + return result; +} + +ServerWindow* WindowServer::GetFocusedWindow() { + for (Display* display : display_manager_->displays()) { + ServerWindow* focused_window = display->GetFocusedWindow(); + if (focused_window) + return focused_window; + } + return nullptr; +} + +uint32_t WindowServer::GenerateWindowManagerChangeId( + WindowTree* source, + uint32_t client_change_id) { + const uint32_t wm_change_id = next_wm_change_id_++; + in_flight_wm_change_map_[wm_change_id] = {source->id(), client_change_id}; + return wm_change_id; +} + +void WindowServer::WindowManagerChangeCompleted( + uint32_t window_manager_change_id, + bool success) { + InFlightWindowManagerChange change; + if (!GetAndClearInFlightWindowManagerChange(window_manager_change_id, + &change)) { + return; + } + + WindowTree* tree = GetTreeWithId(change.client_id); + tree->OnChangeCompleted(change.client_change_id, success); +} + +void WindowServer::WindowManagerCreatedTopLevelWindow( + WindowTree* wm_tree, + uint32_t window_manager_change_id, + const ServerWindow* window) { + InFlightWindowManagerChange change; + if (!GetAndClearInFlightWindowManagerChange(window_manager_change_id, + &change)) { + return; + } + if (!window) { + WindowManagerSentBogusMessage(); + return; + } + + WindowTree* tree = GetTreeWithId(change.client_id); + // The window manager should have created the window already, and it should + // be ready for embedding. + if (!tree->IsWaitingForNewTopLevelWindow(window_manager_change_id) || + !window || window->id().client_id != wm_tree->id() || + !window->children().empty() || GetTreeWithRoot(window)) { + WindowManagerSentBogusMessage(); + return; + } + + tree->OnWindowManagerCreatedTopLevelWindow(window_manager_change_id, + change.client_change_id, window); +} + +void WindowServer::ProcessWindowBoundsChanged(const ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + for (auto& pair : tree_map_) { + pair.second->ProcessWindowBoundsChanged(window, old_bounds, new_bounds, + IsOperationSource(pair.first)); + } +} + +void WindowServer::ProcessClientAreaChanged( + const ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas) { + for (auto& pair : tree_map_) { + pair.second->ProcessClientAreaChanged(window, new_client_area, + new_additional_client_areas, + IsOperationSource(pair.first)); + } +} + +void WindowServer::ProcessLostCapture(const ServerWindow* window) { + for (auto& pair : tree_map_) + pair.second->ProcessLostCapture(window, IsOperationSource(pair.first)); +} + +void WindowServer::ProcessWillChangeWindowHierarchy( + const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent) { + for (auto& pair : tree_map_) { + pair.second->ProcessWillChangeWindowHierarchy( + window, new_parent, old_parent, IsOperationSource(pair.first)); + } +} + +void WindowServer::ProcessWindowHierarchyChanged( + const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent) { + for (auto& pair : tree_map_) { + pair.second->ProcessWindowHierarchyChanged(window, new_parent, old_parent, + IsOperationSource(pair.first)); + } +} + +void WindowServer::ProcessWindowReorder(const ServerWindow* window, + const ServerWindow* relative_window, + const mojom::OrderDirection direction) { + // We'll probably do a bit of reshuffling when we add a transient window. + if ((current_operation_type() == OperationType::ADD_TRANSIENT_WINDOW) || + (current_operation_type() == + OperationType::REMOVE_TRANSIENT_WINDOW_FROM_PARENT)) { + return; + } + for (auto& pair : tree_map_) { + pair.second->ProcessWindowReorder(window, relative_window, direction, + IsOperationSource(pair.first)); + } +} + +void WindowServer::ProcessWindowDeleted(const ServerWindow* window) { + for (auto& pair : tree_map_) + pair.second->ProcessWindowDeleted(window, IsOperationSource(pair.first)); +} + +void WindowServer::ProcessWillChangeWindowPredefinedCursor(ServerWindow* window, + int32_t cursor_id) { + for (auto& pair : tree_map_) { + pair.second->ProcessCursorChanged(window, cursor_id, + IsOperationSource(pair.first)); + } +} + +void WindowServer::SendToEventObservers(const ui::Event& event, + const UserId& user_id, + WindowTree* ignore_tree) { + for (auto& pair : tree_map_) { + WindowTree* tree = pair.second.get(); + if (tree->user_id() == user_id && tree != ignore_tree) + tree->SendToEventObserver(event); + } +} + +void WindowServer::SetPaintCallback( + const base::Callback<void(ServerWindow*)>& callback) { + DCHECK(delegate_->IsTestConfig()) << "Paint callbacks are expensive, and " + << "allowed only in tests."; + DCHECK(window_paint_callback_.is_null() || callback.is_null()); + window_paint_callback_ = callback; +} + +bool WindowServer::GetAndClearInFlightWindowManagerChange( + uint32_t window_manager_change_id, + InFlightWindowManagerChange* change) { + // There are valid reasons as to why we wouldn't know about the id. The + // most likely is the client disconnected before the response from the window + // manager came back. + auto iter = in_flight_wm_change_map_.find(window_manager_change_id); + if (iter == in_flight_wm_change_map_.end()) + return false; + + *change = iter->second; + in_flight_wm_change_map_.erase(iter); + return true; +} + +void WindowServer::PrepareForOperation(Operation* op) { + // Should only ever have one change in flight. + CHECK(!current_operation_); + current_operation_ = op; +} + +void WindowServer::FinishOperation() { + // PrepareForOperation/FinishOperation should be balanced. + CHECK(current_operation_); + current_operation_ = nullptr; +} + +void WindowServer::UpdateNativeCursorFromMouseLocation(ServerWindow* window) { + WindowManagerDisplayRoot* display_root = + display_manager_->GetWindowManagerDisplayRoot(window); + if (display_root) { + EventDispatcher* event_dispatcher = + display_root->window_manager_state()->event_dispatcher(); + event_dispatcher->UpdateCursorProviderByLastKnownLocation(); + int32_t cursor_id = 0; + if (event_dispatcher->GetCurrentMouseCursor(&cursor_id)) + display_root->display()->UpdateNativeCursor(cursor_id); + } +} + +void WindowServer::UpdateNativeCursorIfOver(ServerWindow* window) { + WindowManagerDisplayRoot* display_root = + display_manager_->GetWindowManagerDisplayRoot(window); + if (!display_root) + return; + + EventDispatcher* event_dispatcher = + display_root->window_manager_state()->event_dispatcher(); + if (window != event_dispatcher->mouse_cursor_source_window()) + return; + + event_dispatcher->UpdateNonClientAreaForCurrentWindow(); + int32_t cursor_id = 0; + if (event_dispatcher->GetCurrentMouseCursor(&cursor_id)) + display_root->display()->UpdateNativeCursor(cursor_id); +} + +mus::SurfacesState* WindowServer::GetSurfacesState() { + return surfaces_state_.get(); +} + +void WindowServer::OnScheduleWindowPaint(ServerWindow* window) { + if (in_destructor_) + return; + + SchedulePaint(window, gfx::Rect(window->bounds().size())); + if (!window_paint_callback_.is_null()) + window_paint_callback_.Run(window); +} + +const ServerWindow* WindowServer::GetRootWindow( + const ServerWindow* window) const { + const Display* display = display_manager_->GetDisplayContaining(window); + return display ? display->root_window() : nullptr; +} + +void WindowServer::ScheduleSurfaceDestruction(ServerWindow* window) { + Display* display = display_manager_->GetDisplayContaining(window); + if (display) + display->ScheduleSurfaceDestruction(window); +} + +void WindowServer::OnWindowDestroyed(ServerWindow* window) { + ProcessWindowDeleted(window); +} + +void WindowServer::OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) { + if (in_destructor_) + return; + + ProcessWillChangeWindowHierarchy(window, new_parent, old_parent); +} + +void WindowServer::OnWindowHierarchyChanged(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) { + if (in_destructor_) + return; + + WindowManagerDisplayRoot* display_root = + display_manager_->GetWindowManagerDisplayRoot(window); + if (display_root) + display_root->window_manager_state() + ->ReleaseCaptureBlockedByAnyModalWindow(); + + ProcessWindowHierarchyChanged(window, new_parent, old_parent); + + // TODO(beng): optimize. + if (old_parent) + SchedulePaint(old_parent, gfx::Rect(old_parent->bounds().size())); + if (new_parent) + SchedulePaint(new_parent, gfx::Rect(new_parent->bounds().size())); + + UpdateNativeCursorFromMouseLocation(window); +} + +void WindowServer::OnWindowBoundsChanged(ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + if (in_destructor_) + return; + + ProcessWindowBoundsChanged(window, old_bounds, new_bounds); + if (!window->parent()) + return; + + SchedulePaint(window->parent(), old_bounds); + SchedulePaint(window->parent(), new_bounds); + + UpdateNativeCursorFromMouseLocation(window); +} + +void WindowServer::OnWindowClientAreaChanged( + ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas) { + if (in_destructor_) + return; + + ProcessClientAreaChanged(window, new_client_area, + new_additional_client_areas); + + UpdateNativeCursorIfOver(window); +} + +void WindowServer::OnWindowReordered(ServerWindow* window, + ServerWindow* relative, + mojom::OrderDirection direction) { + ProcessWindowReorder(window, relative, direction); + if (!in_destructor_) + SchedulePaint(window, gfx::Rect(window->bounds().size())); + UpdateNativeCursorFromMouseLocation(window); +} + +void WindowServer::OnWillChangeWindowVisibility(ServerWindow* window) { + if (in_destructor_) + return; + + // Need to repaint if the window was drawn (which means it's in the process of + // hiding) or the window is transitioning to drawn. + if (window->parent() && + (window->IsDrawn() || + (!window->visible() && window->parent()->IsDrawn()))) { + SchedulePaint(window->parent(), window->bounds()); + } + + for (auto& pair : tree_map_) { + pair.second->ProcessWillChangeWindowVisibility( + window, IsOperationSource(pair.first)); + } +} + +void WindowServer::OnWindowOpacityChanged(ServerWindow* window, + float old_opacity, + float new_opacity) { + DCHECK(!in_destructor_); + + for (auto& pair : tree_map_) { + pair.second->ProcessWindowOpacityChanged(window, old_opacity, new_opacity, + IsOperationSource(pair.first)); + } +} + +void WindowServer::OnWindowVisibilityChanged(ServerWindow* window) { + if (in_destructor_) + return; + + WindowManagerDisplayRoot* display_root = + display_manager_->GetWindowManagerDisplayRoot(window); + if (display_root) + display_root->window_manager_state()->ReleaseCaptureBlockedByModalWindow( + window); +} + +void WindowServer::OnWindowPredefinedCursorChanged(ServerWindow* window, + int32_t cursor_id) { + if (in_destructor_) + return; + + ProcessWillChangeWindowPredefinedCursor(window, cursor_id); + + UpdateNativeCursorIfOver(window); +} + +void WindowServer::OnWindowNonClientCursorChanged(ServerWindow* window, + int32_t cursor_id) { + if (in_destructor_) + return; + + UpdateNativeCursorIfOver(window); +} + +void WindowServer::OnWindowSharedPropertyChanged( + ServerWindow* window, + const std::string& name, + const std::vector<uint8_t>* new_data) { + for (auto& pair : tree_map_) { + pair.second->ProcessWindowPropertyChanged(window, name, new_data, + IsOperationSource(pair.first)); + } +} + +void WindowServer::OnWindowTextInputStateChanged( + ServerWindow* window, + const ui::TextInputState& state) { + Display* display = display_manager_->GetDisplayContaining(window); + display->UpdateTextInputState(window, state); +} + +void WindowServer::OnTransientWindowAdded(ServerWindow* window, + ServerWindow* transient_child) { + for (auto& pair : tree_map_) { + pair.second->ProcessTransientWindowAdded(window, transient_child, + IsOperationSource(pair.first)); + } +} + +void WindowServer::OnTransientWindowRemoved(ServerWindow* window, + ServerWindow* transient_child) { + // If we're deleting a window, then this is a superfluous message. + if (current_operation_type() == OperationType::DELETE_WINDOW) + return; + for (auto& pair : tree_map_) { + pair.second->ProcessTransientWindowRemoved(window, transient_child, + IsOperationSource(pair.first)); + } +} + +void WindowServer::OnFirstDisplayReady() { + delegate_->OnFirstDisplayReady(); +} + +void WindowServer::OnNoMoreDisplays() { + delegate_->OnNoMoreDisplays(); +} + +bool WindowServer::GetFrameDecorationsForUser( + const UserId& user_id, + mojom::FrameDecorationValuesPtr* values) { + WindowManagerState* window_manager_state = + window_manager_window_tree_factory_set_.GetWindowManagerStateForUser( + user_id); + if (!window_manager_state) + return false; + if (values && window_manager_state->got_frame_decoration_values()) + *values = window_manager_state->frame_decoration_values().Clone(); + return window_manager_state->got_frame_decoration_values(); +} + +WindowManagerState* WindowServer::GetWindowManagerStateForUser( + const UserId& user_id) { + return window_manager_window_tree_factory_set_.GetWindowManagerStateForUser( + user_id); +} + +void WindowServer::OnActiveUserIdChanged(const UserId& previously_active_id, + const UserId& active_id) {} + +void WindowServer::OnUserIdAdded(const UserId& id) { + activity_monitor_map_[id] = base::MakeUnique<UserActivityMonitor>(nullptr); +} + +void WindowServer::OnUserIdRemoved(const UserId& id) { + activity_monitor_map_.erase(id); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_server.h b/chromium/components/mus/ws/window_server.h new file mode 100644 index 00000000000..0ecbc76ee4c --- /dev/null +++ b/chromium/components/mus/ws/window_server.h @@ -0,0 +1,342 @@ +// 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 COMPONENTS_MUS_WS_WINDOW_SERVER_H_ +#define COMPONENTS_MUS_WS_WINDOW_SERVER_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "components/mus/public/interfaces/window_manager_window_tree_factory.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_manager_delegate.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/operation.h" +#include "components/mus/ws/server_window_delegate.h" +#include "components/mus/ws/server_window_observer.h" +#include "components/mus/ws/user_id_tracker.h" +#include "components/mus/ws/user_id_tracker_observer.h" +#include "components/mus/ws/window_manager_window_tree_factory_set.h" +#include "mojo/public/cpp/bindings/array.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace ws { + +class AccessPolicy; +class DisplayManager; +class ServerWindow; +class UserActivityMonitor; +class WindowManagerState; +class WindowServerDelegate; +class WindowTree; +class WindowTreeBinding; + +// WindowServer manages the set of clients of the window server (all the +// WindowTrees) as well as providing the root of the hierarchy. +class WindowServer : public ServerWindowDelegate, + public ServerWindowObserver, + public DisplayManagerDelegate, + public UserIdTrackerObserver { + public: + WindowServer(WindowServerDelegate* delegate, + const scoped_refptr<mus::SurfacesState>& surfaces_state); + ~WindowServer() override; + + WindowServerDelegate* delegate() { return delegate_; } + + UserIdTracker* user_id_tracker() { return &user_id_tracker_; } + const UserIdTracker* user_id_tracker() const { return &user_id_tracker_; } + + DisplayManager* display_manager() { return display_manager_.get(); } + const DisplayManager* display_manager() const { + return display_manager_.get(); + } + + // Creates a new ServerWindow. The return value is owned by the caller, but + // must be destroyed before WindowServer. + ServerWindow* CreateServerWindow( + const WindowId& id, + const std::map<std::string, std::vector<uint8_t>>& properties); + + // Returns the id for the next WindowTree. + ClientSpecificId GetAndAdvanceNextClientId(); + + // See description of WindowTree::Embed() for details. This assumes + // |transport_window_id| is valid. + WindowTree* EmbedAtWindow(ServerWindow* root, + const UserId& user_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + std::unique_ptr<AccessPolicy> access_policy); + + // Adds |tree_impl_ptr| to the set of known trees. Use DestroyTree() to + // destroy the tree. + void AddTree(std::unique_ptr<WindowTree> tree_impl_ptr, + std::unique_ptr<WindowTreeBinding> binding, + mojom::WindowTreePtr tree_ptr); + WindowTree* CreateTreeForWindowManager( + const UserId& user_id, + mojom::WindowTreeRequest window_tree_request, + mojom::WindowTreeClientPtr window_tree_client); + // Invoked when a WindowTree's connection encounters an error. + void DestroyTree(WindowTree* tree); + + // Returns the tree by client id. + WindowTree* GetTreeWithId(ClientSpecificId client_id); + + WindowTree* GetTreeWithClientName(const std::string& client_name); + + size_t num_trees() const { return tree_map_.size(); } + + // Returns the Window identified by |id|. + ServerWindow* GetWindow(const WindowId& id); + + // Schedules a paint for the specified region in the coordinates of |window|. + void SchedulePaint(ServerWindow* window, const gfx::Rect& bounds); + + OperationType current_operation_type() const { + return current_operation_ ? current_operation_->type() + : OperationType::NONE; + } + + // Returns true if the specified client issued the current operation. + bool IsOperationSource(ClientSpecificId client_id) const { + return current_operation_ && + current_operation_->source_tree_id() == client_id; + } + + // Invoked when a client messages a client about the change. This is used + // to avoid sending ServerChangeIdAdvanced() unnecessarily. + void OnTreeMessagedClient(ClientSpecificId id); + + // Returns true if OnTreeMessagedClient() was invoked for id. + bool DidTreeMessageClient(ClientSpecificId id) const; + + // Returns the WindowTree that has |id| as a root. + WindowTree* GetTreeWithRoot(const ServerWindow* window) { + return const_cast<WindowTree*>( + const_cast<const WindowServer*>(this)->GetTreeWithRoot(window)); + } + const WindowTree* GetTreeWithRoot(const ServerWindow* window) const; + + void OnFirstWindowManagerWindowTreeFactoryReady(); + + UserActivityMonitor* GetUserActivityMonitorForUser(const UserId& user_id); + + WindowManagerWindowTreeFactorySet* window_manager_window_tree_factory_set() { + return &window_manager_window_tree_factory_set_; + } + + // Sets focus to |window|. Returns true if |window| already has focus, or + // focus was successfully changed. Returns |false| if |window| is not a valid + // window to receive focus. + bool SetFocusedWindow(ServerWindow* window); + ServerWindow* GetFocusedWindow(); + + // Returns a change id for the window manager that is associated with + // |source| and |client_change_id|. When the window manager replies + // WindowManagerChangeCompleted() is called to obtain the original source + // and client supplied change_id that initiated the called. + uint32_t GenerateWindowManagerChangeId(WindowTree* source, + uint32_t client_change_id); + + // Called when a response from the window manager is obtained. Calls to + // the client that initiated the change with the change id originally + // supplied by the client. + void WindowManagerChangeCompleted(uint32_t window_manager_change_id, + bool success); + void WindowManagerCreatedTopLevelWindow(WindowTree* wm_tree, + uint32_t window_manager_change_id, + const ServerWindow* window); + + // Called when we get an unexpected message from the WindowManager. + // TODO(sky): decide what we want to do here. + void WindowManagerSentBogusMessage() {} + + // These functions trivially delegate to all WindowTrees, which in + // term notify their clients. + void ProcessWindowBoundsChanged(const ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds); + void ProcessClientAreaChanged( + const ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas); + void ProcessLostCapture(const ServerWindow* window); + void ProcessWillChangeWindowHierarchy(const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent); + void ProcessWindowHierarchyChanged(const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent); + void ProcessWindowReorder(const ServerWindow* window, + const ServerWindow* relative_window, + const mojom::OrderDirection direction); + void ProcessWindowDeleted(const ServerWindow* window); + void ProcessWillChangeWindowPredefinedCursor(ServerWindow* window, + int32_t cursor_id); + + // Sends an |event| to all WindowTrees belonging to |user_id| that might be + // observing events. Skips |ignore_tree| if it is non-null. + void SendToEventObservers(const ui::Event& event, + const UserId& user_id, + WindowTree* ignore_tree); + + // Sets a callback to be called whenever a ServerWindow is scheduled for + // a [re]paint. This should only be called in a test configuration. + void SetPaintCallback(const base::Callback<void(ServerWindow*)>& callback); + + private: + friend class Operation; + + using WindowTreeMap = + std::map<ClientSpecificId, std::unique_ptr<WindowTree>>; + using UserActivityMonitorMap = + std::map<UserId, std::unique_ptr<UserActivityMonitor>>; + + struct InFlightWindowManagerChange { + // Identifies the client that initiated the change. + ClientSpecificId client_id; + + // Change id supplied by the client. + uint32_t client_change_id; + }; + + using InFlightWindowManagerChangeMap = + std::map<uint32_t, InFlightWindowManagerChange>; + + bool GetAndClearInFlightWindowManagerChange( + uint32_t window_manager_change_id, + InFlightWindowManagerChange* change); + + // Invoked when a client is about to execute a window server operation. + // Subsequently followed by FinishOperation() once the change is done. + // + // Changes should never nest, meaning each PrepareForOperation() must be + // balanced with a call to FinishOperation() with no PrepareForOperation() + // in between. + void PrepareForOperation(Operation* op); + + // Balances a call to PrepareForOperation(). + void FinishOperation(); + + // Updates the native cursor by figuring out what window is under the mouse + // cursor. This is run in response to events that change the bounds or window + // hierarchy. + void UpdateNativeCursorFromMouseLocation(ServerWindow* window); + + // Updates the native cursor if the cursor is currently inside |window|. This + // is run in response to events that change the mouse cursor properties of + // |window|. + void UpdateNativeCursorIfOver(ServerWindow* window); + + // Overridden from ServerWindowDelegate: + mus::SurfacesState* GetSurfacesState() override; + void OnScheduleWindowPaint(ServerWindow* window) override; + const ServerWindow* GetRootWindow(const ServerWindow* window) const override; + void ScheduleSurfaceDestruction(ServerWindow* window) override; + + // Overridden from ServerWindowObserver: + void OnWindowDestroyed(ServerWindow* window) override; + void OnWillChangeWindowHierarchy(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) override; + void OnWindowHierarchyChanged(ServerWindow* window, + ServerWindow* new_parent, + ServerWindow* old_parent) override; + void OnWindowBoundsChanged(ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnWindowClientAreaChanged( + ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas) override; + void OnWindowReordered(ServerWindow* window, + ServerWindow* relative, + mojom::OrderDirection direction) override; + void OnWillChangeWindowVisibility(ServerWindow* window) override; + void OnWindowVisibilityChanged(ServerWindow* window) override; + void OnWindowOpacityChanged(ServerWindow* window, + float old_opacity, + float new_opacity) override; + void OnWindowSharedPropertyChanged( + ServerWindow* window, + const std::string& name, + const std::vector<uint8_t>* new_data) override; + void OnWindowPredefinedCursorChanged(ServerWindow* window, + int32_t cursor_id) override; + void OnWindowNonClientCursorChanged(ServerWindow* window, + int32_t cursor_id) override; + void OnWindowTextInputStateChanged(ServerWindow* window, + const ui::TextInputState& state) override; + void OnTransientWindowAdded(ServerWindow* window, + ServerWindow* transient_child) override; + void OnTransientWindowRemoved(ServerWindow* window, + ServerWindow* transient_child) override; + + // DisplayManagerDelegate: + void OnFirstDisplayReady() override; + void OnNoMoreDisplays() override; + bool GetFrameDecorationsForUser( + const UserId& user_id, + mojom::FrameDecorationValuesPtr* values) override; + WindowManagerState* GetWindowManagerStateForUser( + const UserId& user_id) override; + + // UserIdTrackerObserver: + void OnActiveUserIdChanged(const UserId& previously_active_id, + const UserId& active_id) override; + void OnUserIdAdded(const UserId& id) override; + void OnUserIdRemoved(const UserId& id) override; + + UserIdTracker user_id_tracker_; + + WindowServerDelegate* delegate_; + + // State for rendering into a Surface. + scoped_refptr<mus::SurfacesState> surfaces_state_; + + // ID to use for next WindowTree. + ClientSpecificId next_client_id_; + + std::unique_ptr<DisplayManager> display_manager_; + + // Set of WindowTrees. + WindowTreeMap tree_map_; + + // If non-null then we're processing a client operation. The Operation is + // not owned by us (it's created on the stack by WindowTree). + Operation* current_operation_; + + bool in_destructor_; + + // Maps from window manager change id to the client that initiated the + // request. + InFlightWindowManagerChangeMap in_flight_wm_change_map_; + + // Next id supplied to the window manager. + uint32_t next_wm_change_id_; + + base::Callback<void(ServerWindow*)> window_paint_callback_; + + UserActivityMonitorMap activity_monitor_map_; + + WindowManagerWindowTreeFactorySet window_manager_window_tree_factory_set_; + + DISALLOW_COPY_AND_ASSIGN(WindowServer); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_SERVER_H_ diff --git a/chromium/components/mus/ws/window_server_delegate.cc b/chromium/components/mus/ws/window_server_delegate.cc new file mode 100644 index 00000000000..1d9a1045f3c --- /dev/null +++ b/chromium/components/mus/ws/window_server_delegate.cc @@ -0,0 +1,25 @@ +// 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 "components/mus/ws/window_server_delegate.h" + +#include "components/mus/ws/window_tree_binding.h" + +namespace mus { +namespace ws { + +void WindowServerDelegate::OnFirstDisplayReady() {} + +std::unique_ptr<WindowTreeBinding> +WindowServerDelegate::CreateWindowTreeBinding( + BindingType type, + ws::WindowServer* window_server, + ws::WindowTree* tree, + mojom::WindowTreeRequest* tree_request, + mojom::WindowTreeClientPtr* client) { + return nullptr; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_server_delegate.h b/chromium/components/mus/ws/window_server_delegate.h new file mode 100644 index 00000000000..62a8db4b07a --- /dev/null +++ b/chromium/components/mus/ws/window_server_delegate.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 COMPONENTS_MUS_WS_WINDOW_SERVER_DELEGATE_H_ +#define COMPONENTS_MUS_WS_WINDOW_SERVER_DELEGATE_H_ + +#include <stdint.h> + +#include <memory> +#include <string> + +#include "components/mus/common/types.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace mus { + +namespace mojom { +class Display; +class WindowManagerFactory; +class WindowTree; +} + +namespace ws { + +class Display; +class ServerWindow; +class WindowServer; +class WindowTree; +class WindowTreeBinding; + +class WindowServerDelegate { + public: + enum BindingType { + EMBED, + WINDOW_MANAGER, + }; + + // Called if no Displays have been created, but a WindowManagerFactory has + // been set. + virtual void CreateDefaultDisplays() = 0; + + // Called once when the AcceleratedWidget of a Display is available. + virtual void OnFirstDisplayReady(); + + virtual void OnNoMoreDisplays() = 0; + + virtual bool IsTestConfig() const = 0; + + // Creates a WindowTreeBinding. Default implementation returns null, which + // creates DefaultBinding. + virtual std::unique_ptr<WindowTreeBinding> CreateWindowTreeBinding( + BindingType type, + ws::WindowServer* window_server, + ws::WindowTree* tree, + mojom::WindowTreeRequest* tree_request, + mojom::WindowTreeClientPtr* client); + + protected: + virtual ~WindowServerDelegate() {} +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_SERVER_DELEGATE_H_ diff --git a/chromium/components/mus/ws/window_server_test_impl.cc b/chromium/components/mus/ws/window_server_test_impl.cc new file mode 100644 index 00000000000..a61eb2d2bc9 --- /dev/null +++ b/chromium/components/mus/ws/window_server_test_impl.cc @@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_server_test_impl.h" + +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface_manager.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" + +namespace mus { +namespace ws { + +namespace { + +bool WindowHasValidFrame(const ServerWindow* window) { + const ServerWindowSurfaceManager* manager = window->surface_manager(); + return manager && + !manager->GetDefaultSurface()->last_submitted_frame_size().IsEmpty(); +} + +} // namespace + +WindowServerTestImpl::WindowServerTestImpl( + WindowServer* window_server, + mojo::InterfaceRequest<WindowServerTest> request) + : window_server_(window_server), binding_(this, std::move(request)) {} + +WindowServerTestImpl::~WindowServerTestImpl() {} + +void WindowServerTestImpl::OnWindowPaint( + const std::string& name, + const EnsureClientHasDrawnWindowCallback& cb, + ServerWindow* window) { + WindowTree* tree = window_server_->GetTreeWithClientName(name); + if (!tree) + return; + if (tree->HasRoot(window) && WindowHasValidFrame(window)) { + cb.Run(true); + window_server_->SetPaintCallback(base::Callback<void(ServerWindow*)>()); + } +} + +void WindowServerTestImpl::EnsureClientHasDrawnWindow( + const mojo::String& client_name, + const EnsureClientHasDrawnWindowCallback& callback) { + std::string name = client_name.To<std::string>(); + WindowTree* tree = window_server_->GetTreeWithClientName(name); + if (tree) { + for (const ServerWindow* window : tree->roots()) { + if (WindowHasValidFrame(window)) { + callback.Run(true); + return; + } + } + } + + window_server_->SetPaintCallback( + base::Bind(&WindowServerTestImpl::OnWindowPaint, base::Unretained(this), + name, std::move(callback))); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_server_test_impl.h b/chromium/components/mus/ws/window_server_test_impl.h new file mode 100644 index 00000000000..deacb773c61 --- /dev/null +++ b/chromium/components/mus/ws/window_server_test_impl.h @@ -0,0 +1,45 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_SERVER_TEST_IMPL_H_ +#define COMPONENTS_MUS_WS_WINDOW_SERVER_TEST_IMPL_H_ + +#include "components/mus/public/interfaces/window_server_test.mojom.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mus { +namespace ws { + +class ServerWindow; +class WindowServer; +class WindowTree; +struct WindowId; + +class WindowServerTestImpl : public mojom::WindowServerTest { + public: + WindowServerTestImpl(WindowServer* server, + mojo::InterfaceRequest<WindowServerTest> request); + + private: + ~WindowServerTestImpl() override; + + void OnWindowPaint(const std::string& name, + const EnsureClientHasDrawnWindowCallback& cb, + ServerWindow* window); + + // mojom::WindowServerTest: + void EnsureClientHasDrawnWindow( + const mojo::String& client_name, + const EnsureClientHasDrawnWindowCallback& callback) override; + + WindowServer* window_server_; + mojo::StrongBinding<WindowServerTest> binding_; + + DISALLOW_COPY_AND_ASSIGN(WindowServerTestImpl); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_SERVER_TEST_IMPL_H_ diff --git a/chromium/components/mus/ws/window_tree.cc b/chromium/components/mus/ws/window_tree.cc new file mode 100644 index 00000000000..aafdfce96ec --- /dev/null +++ b/chromium/components/mus/ws/window_tree.cc @@ -0,0 +1,1539 @@ +// 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 "components/mus/ws/window_tree.h" + +#include <stddef.h> + +#include <utility> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/stl_util.h" +#include "components/mus/ws/default_access_policy.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_manager.h" +#include "components/mus/ws/event_matcher.h" +#include "components/mus/ws/focus_controller.h" +#include "components/mus/ws/operation.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_observer.h" +#include "components/mus/ws/user_display_manager.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_manager_state.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree_binding.h" +#include "ui/display/display.h" +#include "ui/platform_window/mojo/ime_type_converters.h" +#include "ui/platform_window/text_input_state.h" + +using mojo::Array; +using mojo::InterfaceRequest; +using mojo::String; + +namespace mus { +namespace ws { + +class TargetedEvent : public ServerWindowObserver { + public: + TargetedEvent(ServerWindow* target, const ui::Event& event) + : target_(target), event_(ui::Event::Clone(event)) { + target_->AddObserver(this); + } + ~TargetedEvent() override { + if (target_) + target_->RemoveObserver(this); + } + + ServerWindow* target() { return target_; } + std::unique_ptr<ui::Event> TakeEvent() { return std::move(event_); } + + private: + // ServerWindowObserver: + void OnWindowDestroyed(ServerWindow* window) override { + DCHECK_EQ(target_, window); + target_->RemoveObserver(this); + target_ = nullptr; + } + + ServerWindow* target_; + std::unique_ptr<ui::Event> event_; + + DISALLOW_COPY_AND_ASSIGN(TargetedEvent); +}; + +WindowTree::WindowTree(WindowServer* window_server, + const UserId& user_id, + ServerWindow* root, + std::unique_ptr<AccessPolicy> access_policy) + : window_server_(window_server), + user_id_(user_id), + id_(window_server_->GetAndAdvanceNextClientId()), + next_window_id_(1), + access_policy_(std::move(access_policy)), + event_ack_id_(0), + window_manager_internal_(nullptr) { + if (root) + roots_.insert(root); + access_policy_->Init(id_, this); +} + +WindowTree::~WindowTree() { + DestroyWindows(); +} + +void WindowTree::Init(std::unique_ptr<WindowTreeBinding> binding, + mojom::WindowTreePtr tree) { + DCHECK(!binding_); + binding_ = std::move(binding); + + if (roots_.empty()) + return; + + std::vector<const ServerWindow*> to_send; + CHECK_EQ(1u, roots_.size()); + const ServerWindow* root = *roots_.begin(); + GetUnknownWindowsFrom(root, &to_send); + + Display* display = GetDisplay(root); + int64_t display_id = + display ? display->id() : display::Display::kInvalidDisplayID; + const ServerWindow* focused_window = + display ? display->GetFocusedWindow() : nullptr; + if (focused_window) + focused_window = access_policy_->GetWindowForFocusChange(focused_window); + ClientWindowId focused_window_id; + if (focused_window) + IsWindowKnown(focused_window, &focused_window_id); + + const bool drawn = root->parent() && root->parent()->IsDrawn(); + client()->OnEmbed(id_, WindowToWindowData(to_send.front()), std::move(tree), + display_id, focused_window_id.id, drawn); +} + +void WindowTree::ConfigureWindowManager() { + DCHECK(!window_manager_internal_); + window_manager_internal_ = binding_->GetWindowManager(); + window_manager_internal_->OnConnect(id_); + window_manager_state_.reset(new WindowManagerState(this)); +} + +const ServerWindow* WindowTree::GetWindow(const WindowId& id) const { + if (id_ == id.client_id) { + auto iter = created_window_map_.find(id); + return iter == created_window_map_.end() ? nullptr : iter->second; + } + return window_server_->GetWindow(id); +} + +bool WindowTree::IsWindowKnown(const ServerWindow* window, + ClientWindowId* id) const { + if (!window) + return false; + auto iter = window_id_to_client_id_map_.find(window->id()); + if (iter == window_id_to_client_id_map_.end()) + return false; + if (id) + *id = iter->second; + return true; +} + +bool WindowTree::HasRoot(const ServerWindow* window) const { + return roots_.count(window) > 0; +} + +const ServerWindow* WindowTree::GetWindowByClientId( + const ClientWindowId& id) const { + auto iter = client_id_to_window_id_map_.find(id); + return iter == client_id_to_window_id_map_.end() ? nullptr + : GetWindow(iter->second); +} + +const Display* WindowTree::GetDisplay(const ServerWindow* window) const { + return window ? display_manager()->GetDisplayContaining(window) : nullptr; +} + +const WindowManagerDisplayRoot* WindowTree::GetWindowManagerDisplayRoot( + const ServerWindow* window) const { + return window ? display_manager()->GetWindowManagerDisplayRoot(window) + : nullptr; +} + +DisplayManager* WindowTree::display_manager() { + return window_server_->display_manager(); +} + +const DisplayManager* WindowTree::display_manager() const { + return window_server_->display_manager(); +} + +void WindowTree::AddRootForWindowManager(const ServerWindow* root) { + DCHECK(window_manager_internal_); + const ClientWindowId client_window_id(WindowIdToTransportId(root->id())); + DCHECK_EQ(0u, client_id_to_window_id_map_.count(client_window_id)); + client_id_to_window_id_map_[client_window_id] = root->id(); + window_id_to_client_id_map_[root->id()] = client_window_id; + roots_.insert(root); + + Display* display = GetDisplay(root); + DCHECK(display); + + window_manager_internal_->WmNewDisplayAdded(display->ToMojomDisplay(), + WindowToWindowData(root), + root->parent()->IsDrawn()); +} + +void WindowTree::OnWindowDestroyingTreeImpl(WindowTree* tree) { + if (window_manager_state_) + window_manager_state_->OnWillDestroyTree(tree); + + if (event_source_wms_ && event_source_wms_->window_tree() == tree) + event_source_wms_ = nullptr; + + // Notify our client if |tree| was embedded in any of our views. + for (const auto* tree_root : tree->roots_) { + const bool owns_tree_root = tree_root->id().client_id == id_; + if (owns_tree_root) { + client()->OnEmbeddedAppDisconnected( + ClientWindowIdForWindow(tree_root).id); + } + } +} + +void WindowTree::NotifyChangeCompleted( + uint32_t change_id, + mojom::WindowManagerErrorCode error_code) { + client()->OnChangeCompleted( + change_id, error_code == mojom::WindowManagerErrorCode::SUCCESS); +} + +bool WindowTree::SetCapture(const ClientWindowId& client_window_id) { + ServerWindow* window = GetWindowByClientId(client_window_id); + WindowManagerDisplayRoot* display_root = GetWindowManagerDisplayRoot(window); + ServerWindow* current_capture_window = + display_root ? display_root->window_manager_state()->capture_window() + : nullptr; + if (window && window->IsDrawn() && display_root && + display_root->window_manager_state()->IsActive() && + access_policy_->CanSetCapture(window) && + (!current_capture_window || + access_policy_->CanSetCapture(current_capture_window))) { + return display_root->window_manager_state()->SetCapture(window, id_); + } + return false; +} + +bool WindowTree::NewWindow( + const ClientWindowId& client_window_id, + const std::map<std::string, std::vector<uint8_t>>& properties) { + if (!IsValidIdForNewWindow(client_window_id)) + return false; + const WindowId window_id = GenerateNewWindowId(); + DCHECK(!GetWindow(window_id)); + ServerWindow* window = + window_server_->CreateServerWindow(window_id, properties); + created_window_map_[window_id] = window; + client_id_to_window_id_map_[client_window_id] = window_id; + window_id_to_client_id_map_[window_id] = client_window_id; + return true; +} + +bool WindowTree::AddWindow(const ClientWindowId& parent_id, + const ClientWindowId& child_id) { + ServerWindow* parent = GetWindowByClientId(parent_id); + ServerWindow* child = GetWindowByClientId(child_id); + if (parent && child && child->parent() != parent && + !child->Contains(parent) && access_policy_->CanAddWindow(parent, child)) { + Operation op(this, window_server_, OperationType::ADD_WINDOW); + parent->Add(child); + return true; + } + return false; +} + +bool WindowTree::AddTransientWindow(const ClientWindowId& window_id, + const ClientWindowId& transient_window_id) { + ServerWindow* window = GetWindowByClientId(window_id); + ServerWindow* transient_window = GetWindowByClientId(transient_window_id); + if (window && transient_window && !transient_window->Contains(window) && + access_policy_->CanAddTransientWindow(window, transient_window)) { + Operation op(this, window_server_, OperationType::ADD_TRANSIENT_WINDOW); + return window->AddTransientWindow(transient_window); + } + return false; +} + +bool WindowTree::SetModal(const ClientWindowId& window_id) { + ServerWindow* window = GetWindowByClientId(window_id); + if (window && access_policy_->CanSetModal(window)) { + WindowManagerDisplayRoot* display_root = + GetWindowManagerDisplayRoot(window); + if (window->transient_parent()) { + window->SetModal(); + } else if (user_id_ != InvalidUserId()) { + if (display_root) + display_root->window_manager_state()->AddSystemModalWindow(window); + } else { + return false; + } + if (display_root) + display_root->window_manager_state()->ReleaseCaptureBlockedByModalWindow( + window); + return true; + } + return false; +} + +std::vector<const ServerWindow*> WindowTree::GetWindowTree( + const ClientWindowId& window_id) const { + const ServerWindow* window = GetWindowByClientId(window_id); + std::vector<const ServerWindow*> windows; + if (window) + GetWindowTreeImpl(window, &windows); + return windows; +} + +bool WindowTree::SetWindowVisibility(const ClientWindowId& window_id, + bool visible) { + ServerWindow* window = GetWindowByClientId(window_id); + if (!window || !access_policy_->CanChangeWindowVisibility(window)) + return false; + if (window->visible() == visible) + return true; + Operation op(this, window_server_, OperationType::SET_WINDOW_VISIBILITY); + window->SetVisible(visible); + return true; +} + +bool WindowTree::SetWindowOpacity(const ClientWindowId& window_id, + float opacity) { + ServerWindow* window = GetWindowByClientId(window_id); + if (!window || !access_policy_->CanChangeWindowOpacity(window)) + return false; + if (window->opacity() == opacity) + return true; + Operation op(this, window_server_, OperationType::SET_WINDOW_OPACITY); + window->SetOpacity(opacity); + return true; +} + +bool WindowTree::SetFocus(const ClientWindowId& window_id) { + ServerWindow* window = GetWindowByClientId(window_id); + ServerWindow* currently_focused = window_server_->GetFocusedWindow(); + if (!currently_focused && !window) { + DVLOG(1) << "SetFocus failure, no focused window to clear."; + return false; + } + + Display* display = GetDisplay(window); + if (window && (!display || !window->can_focus() || !window->IsDrawn())) { + DVLOG(1) << "SetFocus failure, window cannot be focused."; + return false; + } + + if (!access_policy_->CanSetFocus(window)) { + DVLOG(1) << "SetFocus failure, blocked by access policy."; + return false; + } + + Operation op(this, window_server_, OperationType::SET_FOCUS); + bool success = window_server_->SetFocusedWindow(window); + if (!success) { + DVLOG(1) << "SetFocus failure, could not SetFocusedWindow."; + } + return success; +} + +bool WindowTree::Embed(const ClientWindowId& window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags) { + if (!client || !CanEmbed(window_id)) + return false; + ServerWindow* window = GetWindowByClientId(window_id); + PrepareForEmbed(window); + // When embedding we don't know the user id of where the TreeClient came + // from. Use an invalid id, which limits what the client is able to do. + window_server_->EmbedAtWindow(window, InvalidUserId(), std::move(client), + flags, + base::WrapUnique(new DefaultAccessPolicy)); + return true; +} + +void WindowTree::DispatchInputEvent(ServerWindow* target, + const ui::Event& event) { + if (event_ack_id_) { + // This is currently waiting for an event ack. Add it to the queue. + event_queue_.push(base::WrapUnique(new TargetedEvent(target, event))); + // TODO(sad): If the |event_queue_| grows too large, then this should notify + // Display, so that it can stop sending events. + return; + } + + // If there are events in the queue, then store this new event in the queue, + // and dispatch the latest event from the queue instead that still has a live + // target. + if (!event_queue_.empty()) { + event_queue_.push(base::WrapUnique(new TargetedEvent(target, event))); + return; + } + + DispatchInputEventImpl(target, event); +} + +bool WindowTree::IsWaitingForNewTopLevelWindow(uint32_t wm_change_id) { + return waiting_for_top_level_window_info_ && + waiting_for_top_level_window_info_->wm_change_id == wm_change_id; +} + +void WindowTree::OnWindowManagerCreatedTopLevelWindow( + uint32_t wm_change_id, + uint32_t client_change_id, + const ServerWindow* window) { + DCHECK(IsWaitingForNewTopLevelWindow(wm_change_id)); + std::unique_ptr<WaitingForTopLevelWindowInfo> + waiting_for_top_level_window_info( + std::move(waiting_for_top_level_window_info_)); + binding_->SetIncomingMethodCallProcessingPaused(false); + // We were paused, so the id should still be valid. + DCHECK(IsValidIdForNewWindow( + waiting_for_top_level_window_info->client_window_id)); + client_id_to_window_id_map_[waiting_for_top_level_window_info + ->client_window_id] = window->id(); + window_id_to_client_id_map_[window->id()] = + waiting_for_top_level_window_info->client_window_id; + roots_.insert(window); + Display* display = GetDisplay(window); + int64_t display_id = + display ? display->id() : display::Display::kInvalidDisplayID; + const bool drawn = window->parent() && window->parent()->IsDrawn(); + client()->OnTopLevelCreated(client_change_id, WindowToWindowData(window), + display_id, drawn); +} + +void WindowTree::AddActivationParent(const ClientWindowId& window_id) { + ServerWindow* window = GetWindowByClientId(window_id); + if (window) { + Display* display = GetDisplay(window); + if (display) + display->AddActivationParent(window); + else + DVLOG(1) << "AddActivationParent window not associated with display"; + } else { + DVLOG(1) << "AddActivationParent supplied invalid window id"; + } +} + +void WindowTree::OnChangeCompleted(uint32_t change_id, bool success) { + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::OnAccelerator(uint32_t accelerator_id, + const ui::Event& event) { + DCHECK(window_manager_internal_); + // TODO(moshayedi): crbug.com/617167. Don't clone even once we map + // mojom::Event directly to ui::Event. + window_manager_internal_->OnAccelerator(accelerator_id, + ui::Event::Clone(event)); +} + +void WindowTree::ClientJankinessChanged(WindowTree* tree) { + tree->janky_ = !tree->janky_; + if (window_manager_internal_) { + window_manager_internal_->WmClientJankinessChanged( + tree->id(), tree->janky()); + } +} + +void WindowTree::ProcessWindowBoundsChanged(const ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change) { + ClientWindowId client_window_id; + if (originated_change || !IsWindowKnown(window, &client_window_id)) + return; + client()->OnWindowBoundsChanged(client_window_id.id, old_bounds, new_bounds); +} + +void WindowTree::ProcessClientAreaChanged( + const ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas, + bool originated_change) { + ClientWindowId client_window_id; + if (originated_change || !IsWindowKnown(window, &client_window_id)) + return; + client()->OnClientAreaChanged( + client_window_id.id, new_client_area, + std::vector<gfx::Rect>(new_additional_client_areas)); +} + +void WindowTree::ProcessWillChangeWindowHierarchy( + const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent, + bool originated_change) { + if (originated_change) + return; + + const bool old_drawn = window->IsDrawn(); + const bool new_drawn = + window->visible() && new_parent && new_parent->IsDrawn(); + if (old_drawn == new_drawn) + return; + + NotifyDrawnStateChanged(window, new_drawn); +} + +void WindowTree::ProcessWindowPropertyChanged( + const ServerWindow* window, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change) { + if (originated_change) + return; + + ClientWindowId client_window_id; + if (!IsWindowKnown(window, &client_window_id)) + return; + + Array<uint8_t> data(nullptr); + if (new_data) + data = Array<uint8_t>::From(*new_data); + + client()->OnWindowSharedPropertyChanged(client_window_id.id, String(name), + std::move(data)); +} + +void WindowTree::ProcessWindowHierarchyChanged(const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent, + bool originated_change) { + const bool knows_new = new_parent && IsWindowKnown(new_parent); + if (originated_change && !IsWindowKnown(window) && knows_new) { + std::vector<const ServerWindow*> unused; + GetUnknownWindowsFrom(window, &unused); + } + if (originated_change || (window_server_->current_operation_type() == + OperationType::DELETE_WINDOW) || + (window_server_->current_operation_type() == OperationType::EMBED) || + window_server_->DidTreeMessageClient(id_)) { + return; + } + + if (!access_policy_->ShouldNotifyOnHierarchyChange(window, &new_parent, + &old_parent)) { + return; + } + // Inform the client of any new windows and update the set of windows we know + // about. + std::vector<const ServerWindow*> to_send; + if (!IsWindowKnown(window)) + GetUnknownWindowsFrom(window, &to_send); + const bool knows_old = old_parent && IsWindowKnown(old_parent); + if (!knows_old && !knows_new) + return; + + const ClientWindowId new_parent_client_window_id = + knows_new ? ClientWindowIdForWindow(new_parent) : ClientWindowId(); + const ClientWindowId old_parent_client_window_id = + knows_old ? ClientWindowIdForWindow(old_parent) : ClientWindowId(); + const ClientWindowId client_window_id = + window ? ClientWindowIdForWindow(window) : ClientWindowId(); + client()->OnWindowHierarchyChanged( + client_window_id.id, old_parent_client_window_id.id, + new_parent_client_window_id.id, WindowsToWindowDatas(to_send)); + window_server_->OnTreeMessagedClient(id_); +} + +void WindowTree::ProcessWindowReorder(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction, + bool originated_change) { + DCHECK_EQ(window->parent(), relative_window->parent()); + ClientWindowId client_window_id, relative_client_window_id; + if (originated_change || !IsWindowKnown(window, &client_window_id) || + !IsWindowKnown(relative_window, &relative_client_window_id) || + window_server_->DidTreeMessageClient(id_)) + return; + + // Do not notify ordering changes of the root windows, since the client + // doesn't know about the ancestors of the roots, and so can't do anything + // about this ordering change of the root. + if (HasRoot(window) || HasRoot(relative_window)) + return; + + client()->OnWindowReordered(client_window_id.id, relative_client_window_id.id, + direction); + window_server_->OnTreeMessagedClient(id_); +} + +void WindowTree::ProcessWindowDeleted(const ServerWindow* window, + bool originated_change) { + if (window->id().client_id == id_) + created_window_map_.erase(window->id()); + + ClientWindowId client_window_id; + if (!IsWindowKnown(window, &client_window_id)) + return; + + if (HasRoot(window)) + RemoveRoot(window, RemoveRootReason::DELETED); + else + RemoveFromMaps(window); + + if (originated_change) + return; + + client()->OnWindowDeleted(client_window_id.id); + window_server_->OnTreeMessagedClient(id_); +} + +void WindowTree::ProcessWillChangeWindowVisibility(const ServerWindow* window, + bool originated_change) { + if (originated_change) + return; + + ClientWindowId client_window_id; + if (IsWindowKnown(window, &client_window_id)) { + client()->OnWindowVisibilityChanged(client_window_id.id, + !window->visible()); + return; + } + + bool window_target_drawn_state; + if (window->visible()) { + // Window is being hidden, won't be drawn. + window_target_drawn_state = false; + } else { + // Window is being shown. Window will be drawn if its parent is drawn. + window_target_drawn_state = window->parent() && window->parent()->IsDrawn(); + } + + NotifyDrawnStateChanged(window, window_target_drawn_state); +} + +void WindowTree::ProcessWindowOpacityChanged(const ServerWindow* window, + float old_opacity, + float new_opacity, + bool originated_change) { + if (originated_change) + return; + + ClientWindowId client_window_id; + if (IsWindowKnown(window, &client_window_id)) { + client()->OnWindowOpacityChanged(client_window_id.id, old_opacity, + new_opacity); + } +} + +void WindowTree::ProcessCursorChanged(const ServerWindow* window, + int32_t cursor_id, + bool originated_change) { + if (originated_change) + return; + ClientWindowId client_window_id; + if (!IsWindowKnown(window, &client_window_id)) + return; + + client()->OnWindowPredefinedCursorChanged(client_window_id.id, + mojom::Cursor(cursor_id)); +} + +void WindowTree::ProcessFocusChanged(const ServerWindow* old_focused_window, + const ServerWindow* new_focused_window) { + if (window_server_->current_operation_type() == OperationType::SET_FOCUS && + window_server_->IsOperationSource(id_)) { + return; + } + const ServerWindow* window = + new_focused_window + ? access_policy_->GetWindowForFocusChange(new_focused_window) + : nullptr; + ClientWindowId client_window_id; + // If the window isn't known we'll supply null, which is ok. + IsWindowKnown(window, &client_window_id); + client()->OnWindowFocused(client_window_id.id); +} + +void WindowTree::ProcessTransientWindowAdded( + const ServerWindow* window, + const ServerWindow* transient_window, + bool originated_change) { + if (originated_change) + return; + + ClientWindowId client_window_id, transient_client_window_id; + if (!IsWindowKnown(window, &client_window_id) || + !IsWindowKnown(transient_window, &transient_client_window_id)) { + return; + } + client()->OnTransientWindowAdded(client_window_id.id, + transient_client_window_id.id); +} + +void WindowTree::ProcessTransientWindowRemoved( + const ServerWindow* window, + const ServerWindow* transient_window, + bool originated_change) { + if (originated_change) + return; + ClientWindowId client_window_id, transient_client_window_id; + if (!IsWindowKnown(window, &client_window_id) || + !IsWindowKnown(transient_window, &transient_client_window_id)) { + return; + } + client()->OnTransientWindowRemoved(client_window_id.id, + transient_client_window_id.id); +} + +bool WindowTree::ShouldRouteToWindowManager(const ServerWindow* window) const { + if (window_manager_state_) + return false; // We are the window manager, don't route to ourself. + + // If the client created this window, then do not route it through the WM. + if (window->id().client_id == id_) + return false; + + // If the client did not create the window, then it must be the root of the + // client. If not, that means the client should not know about this window, + // and so do not route the request to the WM. + if (roots_.count(window) == 0) + return false; + + // The WindowManager is attached to the root of the Display, if there isn't a + // WindowManager attached no need to route to it. + const WindowManagerDisplayRoot* display_root = + GetWindowManagerDisplayRoot(window); + if (!display_root) + return false; + + // Route to the windowmanager if the windowmanager created the window. + return display_root->window_manager_state()->window_tree()->id() == + window->id().client_id; +} + +void WindowTree::ProcessLostCapture(const ServerWindow* old_capture_window, + bool originated_change) { + if ((originated_change && + window_server_->current_operation_type() == + OperationType::RELEASE_CAPTURE) || + !IsWindowKnown(old_capture_window)) { + return; + } + client()->OnLostCapture(WindowIdToTransportId(old_capture_window->id())); +} + +ClientWindowId WindowTree::ClientWindowIdForWindow( + const ServerWindow* window) const { + auto iter = window_id_to_client_id_map_.find(window->id()); + DCHECK(iter != window_id_to_client_id_map_.end()); + return iter->second; +} + +bool WindowTree::IsValidIdForNewWindow(const ClientWindowId& id) const { + // Reserve 0 to indicate a null window. + return client_id_to_window_id_map_.count(id) == 0u && + access_policy_->IsValidIdForNewWindow(id) && id != ClientWindowId(); +} + +WindowId WindowTree::GenerateNewWindowId() { + // TODO(sky): deal with wrapping and uniqueness. + return WindowId(id_, next_window_id_++); +} + +bool WindowTree::CanReorderWindow(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const { + if (!window || !relative_window) + return false; + + if (!window->parent() || window->parent() != relative_window->parent()) + return false; + + if (!access_policy_->CanReorderWindow(window, relative_window, direction)) + return false; + + std::vector<const ServerWindow*> children = window->parent()->GetChildren(); + const size_t child_i = + std::find(children.begin(), children.end(), window) - children.begin(); + const size_t target_i = + std::find(children.begin(), children.end(), relative_window) - + children.begin(); + if ((direction == mojom::OrderDirection::ABOVE && child_i == target_i + 1) || + (direction == mojom::OrderDirection::BELOW && child_i + 1 == target_i)) { + return false; + } + + return true; +} + +bool WindowTree::DeleteWindowImpl(WindowTree* source, ServerWindow* window) { + DCHECK(window); + DCHECK_EQ(window->id().client_id, id_); + Operation op(source, window_server_, OperationType::DELETE_WINDOW); + delete window; + return true; +} + +void WindowTree::GetUnknownWindowsFrom( + const ServerWindow* window, + std::vector<const ServerWindow*>* windows) { + if (IsWindowKnown(window) || !access_policy_->CanGetWindowTree(window)) + return; + windows->push_back(window); + // There are two cases where this gets hit: + // . During init, in which case using the window id as the client id is + // fine. + // . When a window is moved to a parent of a window we know about. This is + // only encountered for the WM or embed roots. We assume such clients want + // to see the real id of the window and are only created ClientWindowIds + // with the client_id. + const ClientWindowId client_window_id(WindowIdToTransportId(window->id())); + DCHECK_EQ(0u, client_id_to_window_id_map_.count(client_window_id)); + client_id_to_window_id_map_[client_window_id] = window->id(); + window_id_to_client_id_map_[window->id()] = client_window_id; + if (!access_policy_->CanDescendIntoWindowForWindowTree(window)) + return; + std::vector<const ServerWindow*> children(window->GetChildren()); + for (size_t i = 0; i < children.size(); ++i) + GetUnknownWindowsFrom(children[i], windows); +} + +bool WindowTree::RemoveFromMaps(const ServerWindow* window) { + auto iter = window_id_to_client_id_map_.find(window->id()); + if (iter == window_id_to_client_id_map_.end()) + return false; + + client_id_to_window_id_map_.erase(iter->second); + window_id_to_client_id_map_.erase(iter); + return true; +} + +void WindowTree::RemoveFromKnown(const ServerWindow* window, + std::vector<ServerWindow*>* local_windows) { + if (window->id().client_id == id_) { + if (local_windows) + local_windows->push_back(GetWindow(window->id())); + return; + } + + RemoveFromMaps(window); + + std::vector<const ServerWindow*> children = window->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + RemoveFromKnown(children[i], local_windows); +} + +void WindowTree::RemoveRoot(const ServerWindow* window, + RemoveRootReason reason) { + DCHECK(roots_.count(window) > 0); + roots_.erase(window); + + const ClientWindowId client_window_id(ClientWindowIdForWindow(window)); + + // No need to do anything if we created the window. + if (window->id().client_id == id_) + return; + + if (reason == RemoveRootReason::EMBED) { + client()->OnUnembed(client_window_id.id); + client()->OnWindowDeleted(client_window_id.id); + window_server_->OnTreeMessagedClient(id_); + } + + // This client no longer knows about the window. Unparent any windows that + // were parented to windows in the root. + std::vector<ServerWindow*> local_windows; + RemoveFromKnown(window, &local_windows); + for (size_t i = 0; i < local_windows.size(); ++i) + local_windows[i]->parent()->Remove(local_windows[i]); +} + +Array<mojom::WindowDataPtr> WindowTree::WindowsToWindowDatas( + const std::vector<const ServerWindow*>& windows) { + Array<mojom::WindowDataPtr> array(windows.size()); + for (size_t i = 0; i < windows.size(); ++i) + array[i] = WindowToWindowData(windows[i]); + return array; +} + +mojom::WindowDataPtr WindowTree::WindowToWindowData( + const ServerWindow* window) { + DCHECK(IsWindowKnown(window)); + const ServerWindow* parent = window->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 && !IsWindowKnown(parent)) + parent = nullptr; + mojom::WindowDataPtr window_data(mojom::WindowData::New()); + window_data->parent_id = + parent ? ClientWindowIdForWindow(parent).id : ClientWindowId().id; + window_data->window_id = + window ? ClientWindowIdForWindow(window).id : ClientWindowId().id; + window_data->bounds = window->bounds(); + window_data->properties = + mojo::Map<String, Array<uint8_t>>::From(window->properties()); + window_data->visible = window->visible(); + return window_data; +} + +void WindowTree::GetWindowTreeImpl( + const ServerWindow* window, + std::vector<const ServerWindow*>* windows) const { + DCHECK(window); + + if (!access_policy_->CanGetWindowTree(window)) + return; + + windows->push_back(window); + + if (!access_policy_->CanDescendIntoWindowForWindowTree(window)) + return; + + std::vector<const ServerWindow*> children(window->GetChildren()); + for (size_t i = 0; i < children.size(); ++i) + GetWindowTreeImpl(children[i], windows); +} + +void WindowTree::NotifyDrawnStateChanged(const ServerWindow* window, + bool new_drawn_value) { + // Even though we don't know about window, it may be an ancestor of our root, + // in which case the change may effect our roots drawn state. + if (roots_.empty()) + return; + + for (auto* root : roots_) { + if (window->Contains(root) && (new_drawn_value != root->IsDrawn())) { + client()->OnWindowParentDrawnStateChanged( + ClientWindowIdForWindow(root).id, new_drawn_value); + } + } +} + +void WindowTree::DestroyWindows() { + if (created_window_map_.empty()) + return; + + Operation op(this, window_server_, OperationType::DELETE_WINDOW); + // If we get here from the destructor we're not going to get + // ProcessWindowDeleted(). Copy the map and delete from the copy so that we + // don't have to worry about whether |created_window_map_| changes or not. + base::hash_map<WindowId, ServerWindow*> created_window_map_copy; + created_window_map_.swap(created_window_map_copy); + // A sibling can be a transient parent of another window so we detach windows + // from their transient parents to avoid double deletes. + for (auto& pair : created_window_map_copy) { + ServerWindow* transient_parent = pair.second->transient_parent(); + if (transient_parent) + transient_parent->RemoveTransientWindow(pair.second); + } + STLDeleteValues(&created_window_map_copy); +} + +bool WindowTree::CanEmbed(const ClientWindowId& window_id) const { + const ServerWindow* window = GetWindowByClientId(window_id); + return window && access_policy_->CanEmbed(window); +} + +void WindowTree::PrepareForEmbed(ServerWindow* window) { + DCHECK(window); + + // Only allow a node to be the root for one client. + WindowTree* existing_owner = window_server_->GetTreeWithRoot(window); + + Operation op(this, window_server_, OperationType::EMBED); + RemoveChildrenAsPartOfEmbed(window); + if (existing_owner) { + // Never message the originating client. + window_server_->OnTreeMessagedClient(id_); + existing_owner->RemoveRoot(window, RemoveRootReason::EMBED); + } +} + +void WindowTree::RemoveChildrenAsPartOfEmbed(ServerWindow* window) { + CHECK(window); + std::vector<ServerWindow*> children = window->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + window->Remove(children[i]); +} + +void WindowTree::DispatchInputEventImpl(ServerWindow* target, + const ui::Event& event) { + DCHECK(!event_ack_id_); + // We do not want to create a sequential id for each event, because that can + // leak some information to the client. So instead, manufacture the id + // randomly. + // TODO(moshayedi): Find a faster way to generate ids. + event_ack_id_ = 0x1000000 | (rand() & 0xffffff); + WindowManagerDisplayRoot* display_root = GetWindowManagerDisplayRoot(target); + DCHECK(display_root); + event_source_wms_ = display_root->window_manager_state(); + // Should only get events from windows attached to a host. + DCHECK(event_source_wms_); + bool matched_observer = + event_observer_matcher_ && event_observer_matcher_->MatchesEvent(event); + client()->OnWindowInputEvent( + event_ack_id_, ClientWindowIdForWindow(target).id, + ui::Event::Clone(event), matched_observer ? event_observer_id_ : 0); +} + +void WindowTree::SendToEventObserver(const ui::Event& event) { + if (event_observer_matcher_ && event_observer_matcher_->MatchesEvent(event)) + client()->OnEventObserved(ui::Event::Clone(event), event_observer_id_); +} + +void WindowTree::NewWindow( + uint32_t change_id, + Id transport_window_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> transport_properties) { + std::map<std::string, std::vector<uint8_t>> properties; + if (!transport_properties.is_null()) { + properties = + transport_properties.To<std::map<std::string, std::vector<uint8_t>>>(); + } + client()->OnChangeCompleted( + change_id, NewWindow(ClientWindowId(transport_window_id), properties)); +} + +void WindowTree::NewTopLevelWindow( + uint32_t change_id, + Id transport_window_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> transport_properties) { + DCHECK(!waiting_for_top_level_window_info_); + const ClientWindowId client_window_id(transport_window_id); + // TODO(sky): need a way for client to provide context to figure out display. + Display* display = display_manager()->displays().empty() + ? nullptr + : *(display_manager()->displays().begin()); + // TODO(sky): move checks to accesspolicy. + WindowManagerDisplayRoot* display_root = + display && user_id_ != InvalidUserId() + ? display->GetWindowManagerDisplayRootForUser(user_id_) + : nullptr; + if (!display_root || + display_root->window_manager_state()->window_tree() == this || + !IsValidIdForNewWindow(client_window_id)) { + client()->OnChangeCompleted(change_id, false); + return; + } + + // The server creates the real window. Any further messages from the client + // may try to alter the window. Pause incoming messages so that we know we + // can't get a message for a window before the window is created. Once the + // window is created we'll resume processing. + binding_->SetIncomingMethodCallProcessingPaused(true); + + const uint32_t wm_change_id = + window_server_->GenerateWindowManagerChangeId(this, change_id); + + waiting_for_top_level_window_info_.reset( + new WaitingForTopLevelWindowInfo(client_window_id, wm_change_id)); + + display_root->window_manager_state() + ->window_tree() + ->window_manager_internal_->WmCreateTopLevelWindow( + wm_change_id, id_, std::move(transport_properties)); +} + +void WindowTree::DeleteWindow(uint32_t change_id, Id transport_window_id) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + bool success = false; + bool should_close = window && (access_policy_->CanDeleteWindow(window) || + ShouldRouteToWindowManager(window)); + if (should_close) { + WindowTree* tree = + window_server_->GetTreeWithId(window->id().client_id); + success = tree && tree->DeleteWindowImpl(this, window); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::AddWindow(uint32_t change_id, Id parent_id, Id child_id) { + client()->OnChangeCompleted(change_id, AddWindow(ClientWindowId(parent_id), + ClientWindowId(child_id))); +} + +void WindowTree::RemoveWindowFromParent(uint32_t change_id, Id window_id) { + bool success = false; + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + if (window && window->parent() && + access_policy_->CanRemoveWindowFromParent(window)) { + success = true; + Operation op(this, window_server_, + OperationType::REMOVE_WINDOW_FROM_PARENT); + window->parent()->Remove(window); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::AddTransientWindow(uint32_t change_id, + Id window, + Id transient_window) { + client()->OnChangeCompleted( + change_id, AddTransientWindow(ClientWindowId(window), + ClientWindowId(transient_window))); +} + +void WindowTree::RemoveTransientWindowFromParent(uint32_t change_id, + Id transient_window_id) { + bool success = false; + ServerWindow* transient_window = + GetWindowByClientId(ClientWindowId(transient_window_id)); + if (transient_window && transient_window->transient_parent() && + access_policy_->CanRemoveTransientWindowFromParent(transient_window)) { + success = true; + Operation op(this, window_server_, + OperationType::REMOVE_TRANSIENT_WINDOW_FROM_PARENT); + transient_window->transient_parent()->RemoveTransientWindow( + transient_window); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::SetModal(uint32_t change_id, Id window_id) { + client()->OnChangeCompleted(change_id, SetModal(ClientWindowId(window_id))); +} + +void WindowTree::ReorderWindow(uint32_t change_id, + Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) { + bool success = false; + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + ServerWindow* relative_window = + GetWindowByClientId(ClientWindowId(relative_window_id)); + if (CanReorderWindow(window, relative_window, direction)) { + success = true; + Operation op(this, window_server_, OperationType::REORDER_WINDOW); + window->Reorder(relative_window, direction); + window_server_->ProcessWindowReorder(window, relative_window, direction); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::GetWindowTree( + Id window_id, + const base::Callback<void(Array<mojom::WindowDataPtr>)>& callback) { + std::vector<const ServerWindow*> windows( + GetWindowTree(ClientWindowId(window_id))); + callback.Run(WindowsToWindowDatas(windows)); +} + +void WindowTree::SetCapture(uint32_t change_id, Id window_id) { + client()->OnChangeCompleted(change_id, SetCapture(ClientWindowId(window_id))); +} + +void WindowTree::ReleaseCapture(uint32_t change_id, Id window_id) { + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + WindowManagerDisplayRoot* display_root = GetWindowManagerDisplayRoot(window); + ServerWindow* current_capture_window = + display_root ? display_root->window_manager_state()->capture_window() + : nullptr; + bool success = window && display_root && + display_root->window_manager_state()->IsActive() && + (!current_capture_window || + access_policy_->CanSetCapture(current_capture_window)) && + window == current_capture_window; + if (success) { + Operation op(this, window_server_, OperationType::RELEASE_CAPTURE); + success = display_root->window_manager_state()->SetCapture( + nullptr, kInvalidClientId); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::SetEventObserver(mojom::EventMatcherPtr matcher, + uint32_t observer_id) { + if (matcher.is_null() || observer_id == 0) { + // Clear any existing event observer. + event_observer_matcher_.reset(); + event_observer_id_ = 0; + return; + } + + // Do not allow key events to be observed, as a compromised app could register + // itself as an event observer and spy on keystrokes to another app. + if (!matcher->type_matcher) { + DVLOG(1) << "SetEventObserver must specify an event type."; + return; + } + const ui::mojom::EventType event_type_whitelist[] = { + ui::mojom::EventType::POINTER_CANCEL, ui::mojom::EventType::POINTER_DOWN, + ui::mojom::EventType::POINTER_MOVE, ui::mojom::EventType::POINTER_UP, + ui::mojom::EventType::MOUSE_EXIT, ui::mojom::EventType::WHEEL, + }; + bool allowed = false; + for (ui::mojom::EventType event_type : event_type_whitelist) { + if (matcher->type_matcher->type == event_type) { + allowed = true; + break; + } + } + if (!allowed) { + DVLOG(1) << "SetEventObserver event type not allowed"; + return; + } + + event_observer_matcher_.reset(new EventMatcher(*matcher)); + event_observer_id_ = observer_id; +} + +void WindowTree::SetWindowBounds(uint32_t change_id, + Id window_id, + const gfx::Rect& bounds) { + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + if (window && ShouldRouteToWindowManager(window)) { + const uint32_t wm_change_id = + window_server_->GenerateWindowManagerChangeId(this, change_id); + // |window_id| may be a client id, use the id from the window to ensure + // the windowmanager doesn't get an id it doesn't know about. + WindowManagerDisplayRoot* display_root = + GetWindowManagerDisplayRoot(window); + WindowTree* wm_tree = display_root->window_manager_state()->window_tree(); + wm_tree->window_manager_internal_->WmSetBounds( + wm_change_id, wm_tree->ClientWindowIdForWindow(window).id, + std::move(bounds)); + return; + } + + // Only the owner of the window can change the bounds. + bool success = window && access_policy_->CanSetWindowBounds(window); + if (success) { + Operation op(this, window_server_, OperationType::SET_WINDOW_BOUNDS); + window->SetBounds(bounds); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::SetWindowVisibility(uint32_t change_id, + Id transport_window_id, + bool visible) { + client()->OnChangeCompleted( + change_id, + SetWindowVisibility(ClientWindowId(transport_window_id), visible)); +} + +void WindowTree::SetWindowProperty(uint32_t change_id, + Id transport_window_id, + const mojo::String& name, + mojo::Array<uint8_t> value) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + if (window && ShouldRouteToWindowManager(window)) { + const uint32_t wm_change_id = + window_server_->GenerateWindowManagerChangeId(this, change_id); + WindowManagerDisplayRoot* display_root = + GetWindowManagerDisplayRoot(window); + WindowTree* wm_tree = display_root->window_manager_state()->window_tree(); + wm_tree->window_manager_internal_->WmSetProperty( + wm_change_id, wm_tree->ClientWindowIdForWindow(window).id, name, + std::move(value)); + return; + } + const bool success = window && access_policy_->CanSetWindowProperties(window); + if (success) { + Operation op(this, window_server_, OperationType::SET_WINDOW_PROPERTY); + if (value.is_null()) { + window->SetProperty(name, nullptr); + } else { + std::vector<uint8_t> data = value.To<std::vector<uint8_t>>(); + window->SetProperty(name, &data); + } + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::SetWindowOpacity(uint32_t change_id, + Id window_id, + float opacity) { + client()->OnChangeCompleted( + change_id, SetWindowOpacity(ClientWindowId(window_id), opacity)); +} + +void WindowTree::AttachSurface(Id transport_window_id, + mojom::SurfaceType type, + mojo::InterfaceRequest<mojom::Surface> surface, + mojom::SurfaceClientPtr client) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + const bool success = + window && access_policy_->CanSetWindowSurface(window, type); + if (!success) + return; + window->CreateSurface(type, std::move(surface), std::move(client)); +} + +void WindowTree::SetWindowTextInputState(Id transport_window_id, + mojo::TextInputStatePtr state) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + bool success = window && access_policy_->CanSetWindowTextInputState(window); + if (success) + window->SetTextInputState(state.To<ui::TextInputState>()); +} + +void WindowTree::SetImeVisibility(Id transport_window_id, + bool visible, + mojo::TextInputStatePtr state) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + bool success = window && access_policy_->CanSetWindowTextInputState(window); + if (success) { + if (!state.is_null()) + window->SetTextInputState(state.To<ui::TextInputState>()); + + Display* display = GetDisplay(window); + if (display) + display->SetImeVisibility(window, visible); + } +} + +void WindowTree::OnWindowInputEventAck(uint32_t event_id, + mojom::EventResult result) { + if (event_ack_id_ == 0 || event_id != event_ack_id_) { + // TODO(sad): Something bad happened. Kill the client? + NOTIMPLEMENTED() << "Wrong event acked."; + } + event_ack_id_ = 0; + + if (janky_) + event_source_wms_->window_tree()->ClientJankinessChanged(this); + + WindowManagerState* event_source_wms = event_source_wms_; + event_source_wms_ = nullptr; + if (event_source_wms) + event_source_wms->OnEventAck(this, result); + + if (!event_queue_.empty()) { + DCHECK(!event_ack_id_); + ServerWindow* target = nullptr; + std::unique_ptr<ui::Event> event; + do { + std::unique_ptr<TargetedEvent> targeted_event = + std::move(event_queue_.front()); + event_queue_.pop(); + target = targeted_event->target(); + event = targeted_event->TakeEvent(); + } while (!event_queue_.empty() && !GetDisplay(target)); + if (target) + DispatchInputEventImpl(target, *event); + } +} + +void WindowTree::SetClientArea( + Id transport_window_id, + const gfx::Insets& insets, + mojo::Array<gfx::Rect> transport_additional_client_areas) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + if (!window || !access_policy_->CanSetClientArea(window)) + return; + + std::vector<gfx::Rect> additional_client_areas = + transport_additional_client_areas.To<std::vector<gfx::Rect>>(); + window->SetClientArea(insets, additional_client_areas); +} + +void WindowTree::SetHitTestMask(Id transport_window_id, const gfx::Rect& mask) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + if (!window || !access_policy_->CanSetHitTestMask(window)) { + DVLOG(1) << "SetHitTestMask failed"; + return; + } + + if (!mask.IsEmpty()) + window->SetHitTestMask(mask); + else + window->ClearHitTestMask(); +} + +void WindowTree::Embed(Id transport_window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + const EmbedCallback& callback) { + callback.Run( + Embed(ClientWindowId(transport_window_id), std::move(client), flags)); +} + +void WindowTree::SetFocus(uint32_t change_id, Id transport_window_id) { + client()->OnChangeCompleted(change_id, + SetFocus(ClientWindowId(transport_window_id))); +} + +void WindowTree::SetCanFocus(Id transport_window_id, bool can_focus) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + // TODO(sky): there should be an else case (it shouldn't route to wm and + // policy allows, then set_can_focus). + if (window && ShouldRouteToWindowManager(window)) + window->set_can_focus(can_focus); +} + +void WindowTree::SetPredefinedCursor(uint32_t change_id, + Id transport_window_id, + mus::mojom::Cursor cursor_id) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + + // Only the owner of the window can change the bounds. + bool success = window && access_policy_->CanSetCursorProperties(window); + if (success) { + Operation op(this, window_server_, + OperationType::SET_WINDOW_PREDEFINED_CURSOR); + window->SetPredefinedCursor(cursor_id); + } + client()->OnChangeCompleted(change_id, success); +} + +void WindowTree::GetWindowManagerClient( + mojo::AssociatedInterfaceRequest<mojom::WindowManagerClient> internal) { + if (!access_policy_->CanSetWindowManager() || !window_manager_internal_ || + window_manager_internal_client_binding_) { + return; + } + window_manager_internal_client_binding_.reset( + new mojo::AssociatedBinding<mojom::WindowManagerClient>( + this, std::move(internal))); +} + +void WindowTree::GetCursorLocationMemory( + const GetCursorLocationMemoryCallback& callback) { + callback.Run( + window_server_->display_manager()->GetUserDisplayManager(user_id_)-> + GetCursorLocationMemory()); +} + +void WindowTree::AddAccelerator(uint32_t id, + mojom::EventMatcherPtr event_matcher, + const AddAcceleratorCallback& callback) { + DCHECK(window_manager_state_); + const bool success = + window_manager_state_->event_dispatcher()->AddAccelerator( + id, std::move(event_matcher)); + callback.Run(success); +} + +void WindowTree::RemoveAccelerator(uint32_t id) { + window_manager_state_->event_dispatcher()->RemoveAccelerator(id); +} + +void WindowTree::AddActivationParent(Id transport_window_id) { + AddActivationParent(ClientWindowId(transport_window_id)); +} + +void WindowTree::RemoveActivationParent(Id transport_window_id) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + if (window) { + Display* display = GetDisplay(window); + if (display) + display->RemoveActivationParent(window); + else + DVLOG(1) << "RemoveActivationParent window not associated with display"; + } else { + DVLOG(1) << "RemoveActivationParent supplied invalid window id"; + } +} + +void WindowTree::ActivateNextWindow() { + DCHECK(window_manager_state_); + if (window_server_->user_id_tracker()->active_id() != user_id_) + return; + + ServerWindow* focused_window = window_server_->GetFocusedWindow(); + if (focused_window) { + WindowManagerDisplayRoot* display_root = + GetWindowManagerDisplayRoot(focused_window); + if (display_root->window_manager_state() != window_manager_state_.get()) { + // We aren't active. + return; + } + display_root->display()->ActivateNextWindow(); + return; + } + // Use the first display. + std::set<Display*> displays = window_server_->display_manager()->displays(); + if (displays.empty()) + return; + + (*displays.begin())->ActivateNextWindow(); +} + +void WindowTree::SetUnderlaySurfaceOffsetAndExtendedHitArea( + Id window_id, + int32_t x_offset, + int32_t y_offset, + const gfx::Insets& hit_area) { + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + if (!window) + return; + + window->SetUnderlayOffset(gfx::Vector2d(x_offset, y_offset)); + window->set_extended_hit_test_region(hit_area); +} + +void WindowTree::WmResponse(uint32_t change_id, bool response) { + window_server_->WindowManagerChangeCompleted(change_id, response); +} + +void WindowTree::WmRequestClose(Id transport_window_id) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + WindowTree* tree = window_server_->GetTreeWithRoot(window); + if (tree && tree != this) { + tree->client()->RequestClose(tree->ClientWindowIdForWindow(window).id); + } + // TODO(sky): think about what else case means. +} + +void WindowTree::WmSetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) { + DCHECK(window_manager_state_); + window_manager_state_->SetFrameDecorationValues(std::move(values)); +} + +void WindowTree::WmSetNonClientCursor(uint32_t window_id, + mojom::Cursor cursor_id) { + DCHECK(window_manager_state_); + ServerWindow* window = GetWindowByClientId(ClientWindowId(window_id)); + if (window) { + window->SetNonClientCursor(cursor_id); + } else { + DVLOG(1) << "trying to update non-client cursor of invalid window"; + } +} + +void WindowTree::OnWmCreatedTopLevelWindow(uint32_t change_id, + Id transport_window_id) { + ServerWindow* window = + GetWindowByClientId(ClientWindowId(transport_window_id)); + if (window && window->id().client_id != id_) { + DVLOG(1) << "OnWmCreatedTopLevelWindow supplied invalid window id"; + window_server_->WindowManagerSentBogusMessage(); + window = nullptr; + } + window_server_->WindowManagerCreatedTopLevelWindow(this, change_id, window); +} + +bool WindowTree::HasRootForAccessPolicy(const ServerWindow* window) const { + return HasRoot(window); +} + +bool WindowTree::IsWindowKnownForAccessPolicy( + const ServerWindow* window) const { + return IsWindowKnown(window); +} + +bool WindowTree::IsWindowRootOfAnotherTreeForAccessPolicy( + const ServerWindow* window) const { + WindowTree* tree = window_server_->GetTreeWithRoot(window); + return tree && tree != this; +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_tree.h b/chromium/components/mus/ws/window_tree.h new file mode 100644 index 00000000000..0e804f355a1 --- /dev/null +++ b/chromium/components/mus/ws/window_tree.h @@ -0,0 +1,497 @@ +// 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 COMPONENTS_MUS_WS_WINDOW_TREE_H_ +#define COMPONENTS_MUS_WS_WINDOW_TREE_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <queue> +#include <set> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "cc/ipc/surface_id.mojom.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/access_policy_delegate.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/user_id.h" +#include "components/mus/ws/window_tree_binding.h" +#include "mojo/public/cpp/bindings/associated_binding.h" + +namespace gfx { +class Insets; +class Rect; +} + +namespace ui { +class Event; +} + +namespace mus { +namespace ws { + +class AccessPolicy; +class DisplayManager; +class Display; +class EventMatcher; +class ServerWindow; +class TargetedEvent; +class WindowManagerDisplayRoot; +class WindowManagerState; +class WindowServer; +class WindowTreeTest; + +namespace test { +class WindowTreeTestApi; +} + +// WindowTree represents a view onto portions of the window tree. The parts of +// the tree exposed to the client start at the root windows. A WindowTree may +// have any number of roots (including none). A WindowTree may not have +// visibility of all the descendants of its roots. +// +// WindowTree notifies its client as changes happen to windows exposed to the +// the client. +// +// See ids.h for details on how WindowTree handles identity of windows. +class WindowTree : public mojom::WindowTree, + public AccessPolicyDelegate, + public mojom::WindowManagerClient { + public: + WindowTree(WindowServer* window_server, + const UserId& user_id, + ServerWindow* root, + std::unique_ptr<AccessPolicy> access_policy); + ~WindowTree() override; + + void Init(std::unique_ptr<WindowTreeBinding> binding, + mojom::WindowTreePtr tree); + + // Called if this WindowTree hosts a WindowManager. + void ConfigureWindowManager(); + + ClientSpecificId id() const { return id_; } + + void set_embedder_intercepts_events() { embedder_intercepts_events_ = true; } + bool embedder_intercepts_events() const { + return embedder_intercepts_events_; + } + + const UserId& user_id() const { return user_id_; } + + mojom::WindowTreeClient* client() { return binding_->client(); } + + // Returns the Window with the specified id. + ServerWindow* GetWindow(const WindowId& id) { + return const_cast<ServerWindow*>( + const_cast<const WindowTree*>(this)->GetWindow(id)); + } + const ServerWindow* GetWindow(const WindowId& id) const; + + // Returns the Window with the specified client id *only* if known to this + // client, returns null if not known. + ServerWindow* GetWindowByClientId(const ClientWindowId& id) { + return const_cast<ServerWindow*>( + const_cast<const WindowTree*>(this)->GetWindowByClientId(id)); + } + const ServerWindow* GetWindowByClientId(const ClientWindowId& id) const; + + bool IsWindowKnown(const ServerWindow* window) const { + return IsWindowKnown(window, nullptr); + } + // Returns whether |window| is known to this tree. If |window| is known and + // |client_window_id| is non-null |client_window_id| is set to the + // ClientWindowId of the window. + bool IsWindowKnown(const ServerWindow* window, + ClientWindowId* client_window_id) const; + + // Returns true if |window| is one of this trees roots. + bool HasRoot(const ServerWindow* window) const; + + std::set<const ServerWindow*> roots() { return roots_; } + + void set_name(const std::string& name) { name_ = name; } + const std::string& name() const { return name_; } + + bool janky() const { return janky_; } + + const Display* GetDisplay(const ServerWindow* window) const; + Display* GetDisplay(const ServerWindow* window) { + return const_cast<Display*>( + const_cast<const WindowTree*>(this)->GetDisplay(window)); + } + + const WindowManagerDisplayRoot* GetWindowManagerDisplayRoot( + const ServerWindow* window) const; + WindowManagerDisplayRoot* GetWindowManagerDisplayRoot( + const ServerWindow* window) { + return const_cast<WindowManagerDisplayRoot*>( + const_cast<const WindowTree*>(this)->GetWindowManagerDisplayRoot( + window)); + } + WindowManagerState* window_manager_state() { + return window_manager_state_.get(); + } + + DisplayManager* display_manager(); + const DisplayManager* display_manager() const; + + WindowServer* window_server() { return window_server_; } + const WindowServer* window_server() const { return window_server_; } + + // Adds a new root to this tree. This is only valid for window managers. + void AddRootForWindowManager(const ServerWindow* root); + + // Invoked when a tree is about to be destroyed. + void OnWindowDestroyingTreeImpl(WindowTree* tree); + + // These functions are synchronous variants of those defined in the mojom. The + // WindowTree implementations all call into these. See the mojom for details. + bool SetCapture(const ClientWindowId& client_window_id); + bool NewWindow(const ClientWindowId& client_window_id, + const std::map<std::string, std::vector<uint8_t>>& properties); + bool AddWindow(const ClientWindowId& parent_id, + const ClientWindowId& child_id); + bool AddTransientWindow(const ClientWindowId& window_id, + const ClientWindowId& transient_window_id); + bool SetModal(const ClientWindowId& window_id); + std::vector<const ServerWindow*> GetWindowTree( + const ClientWindowId& window_id) const; + bool SetWindowVisibility(const ClientWindowId& window_id, bool visible); + bool SetWindowOpacity(const ClientWindowId& window_id, float opacity); + bool SetFocus(const ClientWindowId& window_id); + bool Embed(const ClientWindowId& window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags); + void DispatchInputEvent(ServerWindow* target, const ui::Event& event); + + bool IsWaitingForNewTopLevelWindow(uint32_t wm_change_id); + void OnWindowManagerCreatedTopLevelWindow(uint32_t wm_change_id, + uint32_t client_change_id, + const ServerWindow* window); + void AddActivationParent(const ClientWindowId& window_id); + + // Calls through to the client. + void OnChangeCompleted(uint32_t change_id, bool success); + void OnAccelerator(uint32_t accelerator_id, const ui::Event& event); + + // Called when |tree|'s jankiness changes (see janky_ for definition). + // Notifies the window manager client so it can update UI for the affected + // window(s). + void ClientJankinessChanged(WindowTree* tree); + + // The following methods are invoked after the corresponding change has been + // processed. They do the appropriate bookkeeping and update the client as + // necessary. + void ProcessWindowBoundsChanged(const ServerWindow* window, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change); + void ProcessClientAreaChanged( + const ServerWindow* window, + const gfx::Insets& new_client_area, + const std::vector<gfx::Rect>& new_additional_client_areas, + bool originated_change); + void ProcessWillChangeWindowHierarchy(const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent, + bool originated_change); + void ProcessWindowPropertyChanged(const ServerWindow* window, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change); + void ProcessWindowHierarchyChanged(const ServerWindow* window, + const ServerWindow* new_parent, + const ServerWindow* old_parent, + bool originated_change); + void ProcessWindowReorder(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction, + bool originated_change); + void ProcessWindowDeleted(const ServerWindow* window, bool originated_change); + void ProcessWillChangeWindowVisibility(const ServerWindow* window, + bool originated_change); + void ProcessWindowOpacityChanged(const ServerWindow* window, + float old_opacity, + float new_opacity, + bool originated_change); + void ProcessCursorChanged(const ServerWindow* window, + int32_t cursor_id, + bool originated_change); + void ProcessFocusChanged(const ServerWindow* old_focused_window, + const ServerWindow* new_focused_window); + void ProcessLostCapture(const ServerWindow* old_lost_capture, + bool originated_change); + void ProcessTransientWindowAdded(const ServerWindow* window, + const ServerWindow* transient_window, + bool originated_change); + void ProcessTransientWindowRemoved(const ServerWindow* window, + const ServerWindow* transient_window, + bool originated_change); + + // Sends this event to the client if it matches an active event observer. + void SendToEventObserver(const ui::Event& event); + + private: + friend class test::WindowTreeTestApi; + + struct WaitingForTopLevelWindowInfo { + WaitingForTopLevelWindowInfo(const ClientWindowId& client_window_id, + uint32_t wm_change_id) + : client_window_id(client_window_id), wm_change_id(wm_change_id) {} + ~WaitingForTopLevelWindowInfo() {} + + // Id supplied from the client. + ClientWindowId client_window_id; + + // Change id we created for the window manager. + uint32_t wm_change_id; + }; + + enum class RemoveRootReason { + // The window is being removed. + DELETED, + + // Another client is being embedded in the window. + EMBED, + }; + + bool ShouldRouteToWindowManager(const ServerWindow* window) const; + + ClientWindowId ClientWindowIdForWindow(const ServerWindow* window) const; + + // Returns true if |id| is a valid WindowId for a new window. + bool IsValidIdForNewWindow(const ClientWindowId& id) const; + + WindowId GenerateNewWindowId(); + + // These functions return true if the corresponding mojom function is allowed + // for this tree. + bool CanReorderWindow(const ServerWindow* window, + const ServerWindow* relative_window, + mojom::OrderDirection direction) const; + + // Deletes a window owned by this tree. Returns true on success. |source| is + // the tree that originated the change. + bool DeleteWindowImpl(WindowTree* source, ServerWindow* window); + + // If |window| is known does nothing. Otherwise adds |window| to |windows|, + // marks |window| as known and recurses. + void GetUnknownWindowsFrom(const ServerWindow* window, + std::vector<const ServerWindow*>* windows); + + // Removes |window| from the appropriate maps. If |window| is known to this + // client true is returned. + bool RemoveFromMaps(const ServerWindow* window); + + // Removes |window| and all its descendants from the necessary maps. This + // does not recurse through windows that were created by this tree. All + // windows owned by this tree are added to |local_windows|. + void RemoveFromKnown(const ServerWindow* window, + std::vector<ServerWindow*>* local_windows); + + // Removes a root from set of roots of this tree. This does not remove + // the window from the window tree, only from the set of roots. + void RemoveRoot(const ServerWindow* window, RemoveRootReason reason); + + // Converts Window(s) to WindowData(s) for transport. This assumes all the + // windows are valid for the client. The parent of windows the client is not + // allowed to see are set to NULL (in the returned WindowData(s)). + mojo::Array<mojom::WindowDataPtr> WindowsToWindowDatas( + const std::vector<const ServerWindow*>& windows); + mojom::WindowDataPtr WindowToWindowData(const ServerWindow* window); + + // Implementation of GetWindowTree(). Adds |window| to |windows| and recurses + // if CanDescendIntoWindowForWindowTree() returns true. + void GetWindowTreeImpl(const ServerWindow* window, + std::vector<const ServerWindow*>* windows) const; + + // Notify the client if the drawn state of any of the roots changes. + // |window| is the window that is changing to the drawn state + // |new_drawn_value|. + void NotifyDrawnStateChanged(const ServerWindow* window, + bool new_drawn_value); + + // Deletes all Windows we own. + void DestroyWindows(); + + bool CanEmbed(const ClientWindowId& window_id) const; + void PrepareForEmbed(ServerWindow* window); + void RemoveChildrenAsPartOfEmbed(ServerWindow* window); + + void DispatchInputEventImpl(ServerWindow* target, const ui::Event& event); + + // Calls OnChangeCompleted() on the client. + void NotifyChangeCompleted(uint32_t change_id, + mojom::WindowManagerErrorCode error_code); + + // WindowTree: + void NewWindow(uint32_t change_id, + Id transport_window_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> + transport_properties) override; + void NewTopLevelWindow(uint32_t change_id, + Id transport_window_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> + transport_properties) override; + void DeleteWindow(uint32_t change_id, Id transport_window_id) override; + void AddWindow(uint32_t change_id, Id parent_id, Id child_id) override; + void RemoveWindowFromParent(uint32_t change_id, Id window_id) override; + void AddTransientWindow(uint32_t change_id, + Id window, + Id transient_window) override; + void RemoveTransientWindowFromParent(uint32_t change_id, + Id transient_window_id) override; + void SetModal(uint32_t change_id, Id window_id) override; + void ReorderWindow(uint32_t change_Id, + Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) override; + void GetWindowTree( + Id window_id, + const base::Callback<void(mojo::Array<mojom::WindowDataPtr>)>& callback) + override; + void SetCapture(uint32_t change_id, Id window_id) override; + void ReleaseCapture(uint32_t change_id, Id window_id) override; + void SetEventObserver(mojom::EventMatcherPtr matcher, + uint32_t observer_id) override; + void SetWindowBounds(uint32_t change_id, + Id window_id, + const gfx::Rect& bounds) override; + void SetWindowVisibility(uint32_t change_id, + Id window_id, + bool visible) override; + void SetWindowProperty(uint32_t change_id, + Id transport_window_id, + const mojo::String& name, + mojo::Array<uint8_t> value) override; + void SetWindowOpacity(uint32_t change_id, + Id window_id, + float opacity) override; + void AttachSurface(Id transport_window_id, + mojom::SurfaceType type, + mojo::InterfaceRequest<mojom::Surface> surface, + mojom::SurfaceClientPtr client) override; + void Embed(Id transport_window_id, + mojom::WindowTreeClientPtr client, + uint32_t flags, + const EmbedCallback& callback) override; + void SetFocus(uint32_t change_id, Id transport_window_id) override; + void SetCanFocus(Id transport_window_id, bool can_focus) override; + void SetPredefinedCursor(uint32_t change_id, + Id transport_window_id, + mus::mojom::Cursor cursor_id) override; + void SetWindowTextInputState(Id transport_window_id, + mojo::TextInputStatePtr state) override; + void SetImeVisibility(Id transport_window_id, + bool visible, + mojo::TextInputStatePtr state) override; + void OnWindowInputEventAck(uint32_t event_id, + mojom::EventResult result) override; + void SetClientArea( + Id transport_window_id, + const gfx::Insets& insets, + mojo::Array<gfx::Rect> transport_additional_client_areas) override; + void SetHitTestMask(Id transport_window_id, const gfx::Rect& mask) override; + void GetWindowManagerClient( + mojo::AssociatedInterfaceRequest<mojom::WindowManagerClient> internal) + override; + void GetCursorLocationMemory(const GetCursorLocationMemoryCallback& callback) + override; + + // mojom::WindowManagerClient: + void AddAccelerator(uint32_t id, + mojom::EventMatcherPtr event_matcher, + const AddAcceleratorCallback& callback) override; + void RemoveAccelerator(uint32_t id) override; + void AddActivationParent(Id transport_window_id) override; + void RemoveActivationParent(Id transport_window_id) override; + void ActivateNextWindow() override; + void SetUnderlaySurfaceOffsetAndExtendedHitArea( + Id window_id, + int32_t x_offset, + int32_t y_offset, + const gfx::Insets& hit_area) override; + void WmResponse(uint32_t change_id, bool response) override; + void WmRequestClose(Id transport_window_id) override; + void WmSetFrameDecorationValues( + mojom::FrameDecorationValuesPtr values) override; + void WmSetNonClientCursor(uint32_t window_id, + mojom::Cursor cursor_id) override; + void OnWmCreatedTopLevelWindow(uint32_t change_id, + Id transport_window_id) override; + + // AccessPolicyDelegate: + bool HasRootForAccessPolicy(const ServerWindow* window) const override; + bool IsWindowKnownForAccessPolicy(const ServerWindow* window) const override; + bool IsWindowRootOfAnotherTreeForAccessPolicy( + const ServerWindow* window) const override; + + WindowServer* window_server_; + + UserId user_id_; + + // Id of this tree as assigned by WindowServer. + const ClientSpecificId id_; + std::string name_; + + ClientSpecificId next_window_id_; + + std::unique_ptr<WindowTreeBinding> binding_; + + std::unique_ptr<mus::ws::AccessPolicy> access_policy_; + + // The roots, or embed points, of this tree. A WindowTree may have any + // number of roots, including 0. + std::set<const ServerWindow*> roots_; + + // The windows created by this tree. This tree owns these objects. + base::hash_map<WindowId, ServerWindow*> created_window_map_; + + // The client is allowed to assign ids. These two maps providing the mapping + // from the ids native to the server (WindowId) to those understood by the + // client (ClientWindowId). + base::hash_map<ClientWindowId, WindowId> client_id_to_window_id_map_; + base::hash_map<WindowId, ClientWindowId> window_id_to_client_id_map_; + + uint32_t event_ack_id_; + + // A client is considered janky if it hasn't ACK'ed input events within a + // reasonable timeframe. + bool janky_ = false; + + // Set when the client is using SetEventObserver() to observe events, + // otherwise null. + std::unique_ptr<EventMatcher> event_observer_matcher_; + + // The ID supplied by the client for the current event observer. + uint32_t event_observer_id_ = 0; + + // WindowManager the current event came from. + WindowManagerState* event_source_wms_ = nullptr; + + std::queue<std::unique_ptr<TargetedEvent>> event_queue_; + + std::unique_ptr<mojo::AssociatedBinding<mojom::WindowManagerClient>> + window_manager_internal_client_binding_; + mojom::WindowManager* window_manager_internal_; + std::unique_ptr<WindowManagerState> window_manager_state_; + + std::unique_ptr<WaitingForTopLevelWindowInfo> + waiting_for_top_level_window_info_; + bool embedder_intercepts_events_ = false; + + DISALLOW_COPY_AND_ASSIGN(WindowTree); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_TREE_H_ diff --git a/chromium/components/mus/ws/window_tree_binding.cc b/chromium/components/mus/ws/window_tree_binding.cc new file mode 100644 index 00000000000..6f64a9f895f --- /dev/null +++ b/chromium/components/mus/ws/window_tree_binding.cc @@ -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. + +#include "components/mus/ws/window_tree_binding.h" + +#include "base/bind.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" + +namespace mus { +namespace ws { + +WindowTreeBinding::WindowTreeBinding(mojom::WindowTreeClient* client) + : client_(client) {} + +WindowTreeBinding::~WindowTreeBinding() {} + +DefaultWindowTreeBinding::DefaultWindowTreeBinding( + WindowTree* tree, + WindowServer* window_server, + mojom::WindowTreeRequest service_request, + mojom::WindowTreeClientPtr client) + : WindowTreeBinding(client.get()), + binding_(tree, std::move(service_request)), + client_(std::move(client)) { + // Both |window_server| and |tree| outlive us. + binding_.set_connection_error_handler( + base::Bind(&WindowServer::DestroyTree, base::Unretained(window_server), + base::Unretained(tree))); +} + +DefaultWindowTreeBinding::DefaultWindowTreeBinding( + WindowTree* tree, + mojom::WindowTreeClientPtr client) + : WindowTreeBinding(client.get()), + binding_(tree), + client_(std::move(client)) {} + +DefaultWindowTreeBinding::~DefaultWindowTreeBinding() {} + +void DefaultWindowTreeBinding::SetIncomingMethodCallProcessingPaused( + bool paused) { + if (paused) + binding_.PauseIncomingMethodCallProcessing(); + else + binding_.ResumeIncomingMethodCallProcessing(); +} + +mojom::WindowTreePtr DefaultWindowTreeBinding::CreateInterfacePtrAndBind() { + DCHECK(!binding_.is_bound()); + return binding_.CreateInterfacePtrAndBind(); +} + +mojom::WindowManager* DefaultWindowTreeBinding::GetWindowManager() { + client_->GetWindowManager( + GetProxy(&window_manager_internal_, client_.associated_group())); + return window_manager_internal_.get(); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_tree_binding.h b/chromium/components/mus/ws/window_tree_binding.h new file mode 100644 index 00000000000..a9472b3566c --- /dev/null +++ b/chromium/components/mus/ws/window_tree_binding.h @@ -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. + +#ifndef COMPONENTS_MUS_WS_WINDOW_TREE_BINDING_H_ +#define COMPONENTS_MUS_WS_WINDOW_TREE_BINDING_H_ + +#include <memory> + +#include "base/macros.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" + +namespace mus { +namespace ws { + +class WindowServer; +class WindowTree; + +// WindowTreeBinding manages the binding between a WindowTree and its +// WindowTreeClient. WindowTreeBinding exists so that a mock implementation +// of WindowTreeClient can be injected for tests. WindowTree owns its +// associated WindowTreeBinding. +class WindowTreeBinding { + public: + explicit WindowTreeBinding(mojom::WindowTreeClient* client); + virtual ~WindowTreeBinding(); + + mojom::WindowTreeClient* client() { return client_; } + + // Obtains a new WindowManager. This should only be called once. + virtual mojom::WindowManager* GetWindowManager() = 0; + + virtual void SetIncomingMethodCallProcessingPaused(bool paused) = 0; + + private: + mojom::WindowTreeClient* client_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeBinding); +}; + +// Bindings implementation of WindowTreeBinding. +class DefaultWindowTreeBinding : public WindowTreeBinding { + public: + DefaultWindowTreeBinding(WindowTree* tree, + WindowServer* window_server, + mojom::WindowTreeRequest service_request, + mojom::WindowTreeClientPtr client); + DefaultWindowTreeBinding(WindowTree* tree, + mojom::WindowTreeClientPtr client); + ~DefaultWindowTreeBinding() override; + + // Use when created with the constructor that does not take a + // WindowTreeRequest. + mojom::WindowTreePtr CreateInterfacePtrAndBind(); + + // WindowTreeBinding: + mojom::WindowManager* GetWindowManager() override; + void SetIncomingMethodCallProcessingPaused(bool paused) override; + + private: + mojo::Binding<mojom::WindowTree> binding_; + mojom::WindowTreeClientPtr client_; + mojom::WindowManagerAssociatedPtr window_manager_internal_; + + DISALLOW_COPY_AND_ASSIGN(DefaultWindowTreeBinding); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_TREE_BINDING_H_ diff --git a/chromium/components/mus/ws/window_tree_client_unittest.cc b/chromium/components/mus/ws/window_tree_client_unittest.cc new file mode 100644 index 00000000000..34169dd2152 --- /dev/null +++ b/chromium/components/mus/ws/window_tree_client_unittest.cc @@ -0,0 +1,2042 @@ +// 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 <stddef.h> +#include <stdint.h> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "components/mus/public/cpp/tests/window_server_shelltest_base.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/test_change_tracker.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "services/shell/public/cpp/shell_test.h" + +using mojo::Array; +using shell::Connection; +using mojo::InterfaceRequest; +using shell::ShellClient; +using mojo::String; +using mus::mojom::WindowDataPtr; +using mus::mojom::WindowTree; +using mus::mojom::WindowTreeClient; + +namespace mus { +namespace ws { +namespace test { + +namespace { + +// Creates an id used for transport from the specified parameters. +Id BuildWindowId(ClientSpecificId client_id, + ClientSpecificId window_id) { + return (client_id << 16) | window_id; +} + +// Callback function from WindowTree functions. +// ---------------------------------- + +void WindowTreeResultCallback(base::RunLoop* run_loop, + std::vector<TestWindow>* windows, + Array<WindowDataPtr> results) { + WindowDatasToTestWindows(results, windows); + run_loop->Quit(); +} + +void EmbedCallbackImpl(base::RunLoop* run_loop, + bool* result_cache, + bool result) { + *result_cache = result; + run_loop->Quit(); +} + +// ----------------------------------------------------------------------------- + +bool EmbedUrl(shell::Connector* connector, + WindowTree* tree, + const String& url, + Id root_id) { + bool result = false; + base::RunLoop run_loop; + { + mojom::WindowTreeClientPtr client; + connector->ConnectToInterface(url.get(), &client); + const uint32_t embed_flags = 0; + tree->Embed(root_id, std::move(client), embed_flags, + base::Bind(&EmbedCallbackImpl, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +bool Embed(WindowTree* tree, Id root_id, mojom::WindowTreeClientPtr client) { + bool result = false; + base::RunLoop run_loop; + { + const uint32_t embed_flags = 0; + tree->Embed(root_id, std::move(client), embed_flags, + base::Bind(&EmbedCallbackImpl, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +void GetWindowTree(WindowTree* tree, + Id window_id, + std::vector<TestWindow>* windows) { + base::RunLoop run_loop; + tree->GetWindowTree( + window_id, base::Bind(&WindowTreeResultCallback, &run_loop, windows)); + run_loop.Run(); +} + +// Utility functions ----------------------------------------------------------- + +const Id kNullParentId = 0; +std::string IdToString(Id id) { + return (id == kNullParentId) ? "null" : base::StringPrintf( + "%d,%d", HiWord(id), LoWord(id)); +} + +std::string WindowParentToString(Id window, Id parent) { + return base::StringPrintf("window=%s parent=%s", IdToString(window).c_str(), + IdToString(parent).c_str()); +} + +// ----------------------------------------------------------------------------- + +// A WindowTreeClient implementation that logs all changes to a tracker. +class TestWindowTreeClient : public mojom::WindowTreeClient, + public TestChangeTracker::Delegate, + public mojom::WindowManager { + public: + TestWindowTreeClient() + : binding_(this), + client_id_(0), + root_window_id_(0), + // Start with a random large number so tests can use lower ids if they + // want. + next_change_id_(10000), + waiting_change_id_(0), + on_change_completed_result_(false), + track_root_bounds_changes_(false) { + tracker_.set_delegate(this); + } + + void Bind(mojo::InterfaceRequest<mojom::WindowTreeClient> request) { + binding_.Bind(std::move(request)); + } + + mojom::WindowTree* tree() { return tree_.get(); } + TestChangeTracker* tracker() { return &tracker_; } + Id root_window_id() const { return root_window_id_; } + + // Sets whether changes to the bounds of the root should be tracked. Normally + // they are ignored (as during startup we often times get random size + // changes). + void set_track_root_bounds_changes(bool value) { + track_root_bounds_changes_ = value; + } + + // Runs a nested MessageLoop until |count| changes (calls to + // WindowTreeClient functions) have been received. + void WaitForChangeCount(size_t count) { + if (tracker_.changes()->size() >= count) + 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(); + } + + uint32_t GetAndAdvanceChangeId() { return next_change_id_++; } + + // Runs a nested MessageLoop until OnEmbed() has been encountered. + void WaitForOnEmbed() { + if (tree_) + return; + embed_run_loop_.reset(new base::RunLoop); + embed_run_loop_->Run(); + embed_run_loop_.reset(); + } + + bool WaitForChangeCompleted(uint32_t id) { + waiting_change_id_ = id; + change_completed_run_loop_.reset(new base::RunLoop); + change_completed_run_loop_->Run(); + return on_change_completed_result_; + } + + bool DeleteWindow(Id id) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->DeleteWindow(change_id, id); + return WaitForChangeCompleted(change_id); + } + + bool AddWindow(Id parent, Id child) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->AddWindow(change_id, parent, child); + return WaitForChangeCompleted(change_id); + } + + bool RemoveWindowFromParent(Id window_id) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->RemoveWindowFromParent(change_id, window_id); + return WaitForChangeCompleted(change_id); + } + + bool ReorderWindow(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->ReorderWindow(change_id, window_id, relative_window_id, direction); + return WaitForChangeCompleted(change_id); + } + + // Waits for all messages to be received by |ws|. This is done by attempting + // to create a bogus window. When we get the response we know all messages + // have been processed. + bool WaitForAllMessages() { + return NewWindowWithCompleteId(WindowIdToTransportId(InvalidWindowId())) == + 0; + } + + Id NewWindow(ClientSpecificId window_id) { + return NewWindowWithCompleteId(BuildWindowId(client_id_, window_id)); + } + + // Generally you want NewWindow(), but use this if you need to test given + // a complete window id (NewWindow() ors with the client id). + Id NewWindowWithCompleteId(Id id) { + mojo::Map<mojo::String, mojo::Array<uint8_t>> properties; + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->NewWindow(change_id, id, std::move(properties)); + return WaitForChangeCompleted(change_id) ? id : 0; + } + + bool SetWindowProperty(Id window_id, + const std::string& name, + const std::vector<uint8_t>* data) { + Array<uint8_t> mojo_data(nullptr); + if (data) + mojo_data = Array<uint8_t>::From(*data); + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->SetWindowProperty(change_id, window_id, name, std::move(mojo_data)); + return WaitForChangeCompleted(change_id); + } + + bool SetPredefinedCursor(Id window_id, mojom::Cursor cursor) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->SetPredefinedCursor(change_id, window_id, cursor); + return WaitForChangeCompleted(change_id); + } + + bool SetWindowVisibility(Id window_id, bool visible) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->SetWindowVisibility(change_id, window_id, visible); + return WaitForChangeCompleted(change_id); + } + + bool SetWindowOpacity(Id window_id, float opacity) { + const uint32_t change_id = GetAndAdvanceChangeId(); + tree()->SetWindowOpacity(change_id, window_id, opacity); + return WaitForChangeCompleted(change_id); + } + + 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() && + tracker_.changes()->size() >= wait_state_->change_count) { + wait_state_->run_loop.Quit(); + } + } + + // WindowTreeClient: + void OnEmbed(ClientSpecificId client_id, + WindowDataPtr root, + mojom::WindowTreePtr tree, + int64_t display_id, + Id focused_window_id, + bool drawn) override { + // TODO(sky): add coverage of |focused_window_id|. + ASSERT_TRUE(root); + root_window_id_ = root->window_id; + tree_ = std::move(tree); + client_id_ = client_id; + tracker()->OnEmbed(client_id, std::move(root), drawn); + if (embed_run_loop_) + embed_run_loop_->Quit(); + } + void OnEmbeddedAppDisconnected(Id window_id) override { + tracker()->OnEmbeddedAppDisconnected(window_id); + } + void OnUnembed(Id window_id) override { tracker()->OnUnembed(window_id); } + void OnLostCapture(Id window_id) override { + tracker()->OnLostCapture(window_id); + } + void OnTopLevelCreated(uint32_t change_id, + mojom::WindowDataPtr data, + int64_t display_id, + bool drawn) override { + tracker()->OnTopLevelCreated(change_id, std::move(data), drawn); + } + void OnWindowBoundsChanged(Id window_id, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override { + // The bounds of the root may change during startup on Android at random + // times. As this doesn't matter, and shouldn't impact test exepctations, + // it is ignored. + if (window_id == root_window_id_ && !track_root_bounds_changes_) + return; + tracker()->OnWindowBoundsChanged(window_id, old_bounds, new_bounds); + } + void OnClientAreaChanged( + uint32_t window_id, + const gfx::Insets& new_client_area, + mojo::Array<gfx::Rect> new_additional_client_areas) override {} + void OnTransientWindowAdded(uint32_t window_id, + uint32_t transient_window_id) override { + tracker()->OnTransientWindowAdded(window_id, transient_window_id); + } + void OnTransientWindowRemoved(uint32_t window_id, + uint32_t transient_window_id) override { + tracker()->OnTransientWindowRemoved(window_id, transient_window_id); + } + void OnWindowHierarchyChanged(Id window, + Id old_parent, + Id new_parent, + Array<WindowDataPtr> windows) override { + tracker()->OnWindowHierarchyChanged(window, old_parent, new_parent, + std::move(windows)); + } + void OnWindowReordered(Id window_id, + Id relative_window_id, + mojom::OrderDirection direction) override { + tracker()->OnWindowReordered(window_id, relative_window_id, direction); + } + void OnWindowDeleted(Id window) override { + tracker()->OnWindowDeleted(window); + } + void OnWindowVisibilityChanged(uint32_t window, bool visible) override { + tracker()->OnWindowVisibilityChanged(window, visible); + } + void OnWindowOpacityChanged(uint32_t window, + float old_opacity, + float new_opacity) override { + tracker()->OnWindowOpacityChanged(window, new_opacity); + } + void OnWindowParentDrawnStateChanged(uint32_t window, bool drawn) override { + tracker()->OnWindowParentDrawnStateChanged(window, drawn); + } + void OnWindowInputEvent(uint32_t event_id, + Id window_id, + std::unique_ptr<ui::Event> event, + uint32_t event_observer_id) override { + // Ack input events to clear the state on the server. These can be received + // during test startup. X11Window::DispatchEvent sends a synthetic move + // event to notify of entry. + tree()->OnWindowInputEventAck(event_id, mojom::EventResult::HANDLED); + // Don't log input events as none of the tests care about them and they + // may come in at random points. + } + void OnEventObserved(std::unique_ptr<ui::Event>, + uint32_t event_observer_id) override {} + void OnWindowSharedPropertyChanged(uint32_t window, + const String& name, + Array<uint8_t> new_data) override { + tracker_.OnWindowSharedPropertyChanged(window, name, std::move(new_data)); + } + // TODO(sky): add testing coverage. + void OnWindowFocused(uint32_t focused_window_id) override {} + void OnWindowPredefinedCursorChanged(uint32_t window_id, + mojom::Cursor cursor_id) override { + tracker_.OnWindowPredefinedCursorChanged(window_id, cursor_id); + } + void OnChangeCompleted(uint32_t change_id, bool success) override { + if (waiting_change_id_ == change_id && change_completed_run_loop_) { + on_change_completed_result_ = success; + change_completed_run_loop_->Quit(); + } + } + void RequestClose(uint32_t window_id) override {} + void GetWindowManager(mojo::AssociatedInterfaceRequest<mojom::WindowManager> + internal) override { + window_manager_binding_.reset( + new mojo::AssociatedBinding<mojom::WindowManager>(this, + std::move(internal))); + tree_->GetWindowManagerClient( + GetProxy(&window_manager_client_, tree_.associated_group())); + } + + // mojom::WindowManager: + void OnConnect(uint16_t client_id) override {} + void WmNewDisplayAdded(mojom::DisplayPtr display, + mojom::WindowDataPtr root_data, + bool drawn) override { + NOTIMPLEMENTED(); + } + void WmSetBounds(uint32_t change_id, + uint32_t window_id, + const gfx::Rect& bounds) override { + window_manager_client_->WmResponse(change_id, false); + } + void WmSetProperty(uint32_t change_id, + uint32_t window_id, + const mojo::String& name, + mojo::Array<uint8_t> value) override { + window_manager_client_->WmResponse(change_id, false); + } + void WmCreateTopLevelWindow( + uint32_t change_id, + ClientSpecificId requesting_client_id, + mojo::Map<mojo::String, mojo::Array<uint8_t>> properties) override { + NOTIMPLEMENTED(); + } + void WmClientJankinessChanged(ClientSpecificId client_id, + bool janky) override { + NOTIMPLEMENTED(); + } + void OnAccelerator(uint32_t id, std::unique_ptr<ui::Event> event) override { + NOTIMPLEMENTED(); + } + + TestChangeTracker tracker_; + + mojom::WindowTreePtr tree_; + + // If non-null we're waiting for OnEmbed() using this RunLoop. + std::unique_ptr<base::RunLoop> embed_run_loop_; + + // If non-null we're waiting for a certain number of change notifications to + // be encountered. + std::unique_ptr<WaitState> wait_state_; + + mojo::Binding<WindowTreeClient> binding_; + Id client_id_; + Id root_window_id_; + uint32_t next_change_id_; + uint32_t waiting_change_id_; + bool on_change_completed_result_; + bool track_root_bounds_changes_; + std::unique_ptr<base::RunLoop> change_completed_run_loop_; + + std::unique_ptr<mojo::AssociatedBinding<mojom::WindowManager>> + window_manager_binding_; + mojom::WindowManagerClientAssociatedPtr window_manager_client_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowTreeClient); +}; + +// ----------------------------------------------------------------------------- + +// InterfaceFactory for vending TestWindowTreeClients. +class WindowTreeClientFactory + : public shell::InterfaceFactory<WindowTreeClient> { + public: + WindowTreeClientFactory() {} + ~WindowTreeClientFactory() override {} + + // Runs a nested MessageLoop until a new instance has been created. + std::unique_ptr<TestWindowTreeClient> WaitForInstance() { + if (!client_impl_.get()) { + DCHECK(!run_loop_); + run_loop_.reset(new base::RunLoop); + run_loop_->Run(); + run_loop_.reset(); + } + return std::move(client_impl_); + } + + private: + // InterfaceFactory<WindowTreeClient>: + void Create(Connection* connection, + InterfaceRequest<WindowTreeClient> request) override { + client_impl_.reset(new TestWindowTreeClient()); + client_impl_->Bind(std::move(request)); + if (run_loop_.get()) + run_loop_->Quit(); + } + + std::unique_ptr<TestWindowTreeClient> client_impl_; + std::unique_ptr<base::RunLoop> run_loop_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeClientFactory); +}; + +} // namespace + +class WindowTreeClientTest : public WindowServerShellTestBase { + public: + WindowTreeClientTest() + : client_id_1_(0), client_id_2_(0), root_window_id_(0) {} + + ~WindowTreeClientTest() override {} + + protected: + // Returns the changes from the various clients. + std::vector<Change>* changes1() { return wt_client1_->tracker()->changes(); } + std::vector<Change>* changes2() { return wt_client2_->tracker()->changes(); } + std::vector<Change>* changes3() { return wt_client3_->tracker()->changes(); } + + // Various clients. |wt1()|, being the first client, has special permissions + // (it's treated as the window manager). + WindowTree* wt1() { return wt_client1_->tree(); } + WindowTree* wt2() { return wt_client2_->tree(); } + WindowTree* wt3() { return wt_client3_->tree(); } + + TestWindowTreeClient* wt_client1() { return wt_client1_.get(); } + TestWindowTreeClient* wt_client2() { return wt_client2_.get(); } + TestWindowTreeClient* wt_client3() { return wt_client3_.get(); } + + Id root_window_id() const { return root_window_id_; } + + int client_id_1() const { return client_id_1_; } + int client_id_2() const { return client_id_2_; } + + void EstablishSecondClientWithRoot(Id root_id) { + ASSERT_TRUE(wt_client2_.get() == nullptr); + wt_client2_ = + EstablishClientViaEmbed(wt1(), root_id, &client_id_2_); + ASSERT_GT(client_id_2_, 0); + ASSERT_TRUE(wt_client2_.get() != nullptr); + } + + void EstablishSecondClient(bool create_initial_window) { + Id window_1_1 = 0; + if (create_initial_window) { + window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + } + ASSERT_NO_FATAL_FAILURE( + EstablishSecondClientWithRoot(BuildWindowId(client_id_1(), 1))); + + if (create_initial_window) { + EXPECT_EQ("[" + WindowParentToString(window_1_1, kNullParentId) + "]", + ChangeWindowDescription(*changes2())); + } + } + + void EstablishThirdClient(WindowTree* owner, Id root_id) { + ASSERT_TRUE(wt_client3_.get() == nullptr); + wt_client3_ = EstablishClientViaEmbed(owner, root_id, nullptr); + ASSERT_TRUE(wt_client3_.get() != nullptr); + } + + std::unique_ptr<TestWindowTreeClient> WaitForWindowTreeClient() { + return client_factory_->WaitForInstance(); + } + + // Establishes a new client by way of Embed() on the specified WindowTree. + std::unique_ptr<TestWindowTreeClient> EstablishClientViaEmbed( + WindowTree* owner, + Id root_id, + int* client_id) { + return EstablishClientViaEmbedWithPolicyBitmask(owner, root_id, client_id); + } + + std::unique_ptr<TestWindowTreeClient> + EstablishClientViaEmbedWithPolicyBitmask(WindowTree* owner, + Id root_id, + int* client_id) { + if (!EmbedUrl(connector(), owner, test_name(), root_id)) { + ADD_FAILURE() << "Embed() failed"; + return nullptr; + } + std::unique_ptr<TestWindowTreeClient> client = + client_factory_->WaitForInstance(); + if (!client.get()) { + ADD_FAILURE() << "WaitForInstance failed"; + return nullptr; + } + client->WaitForOnEmbed(); + + EXPECT_EQ("OnEmbed", + SingleChangeToDescription(*client->tracker()->changes())); + if (client_id) + *client_id = (*client->tracker()->changes())[0].client_id; + return client; + } + + // WindowServerShellTestBase: + bool AcceptConnection(shell::Connection* connection) override { + connection->AddInterface(client_factory_.get()); + return true; + } + + void SetUp() override { + client_factory_.reset(new WindowTreeClientFactory()); + + WindowServerShellTestBase::SetUp(); + + mojom::WindowTreeHostFactoryPtr factory; + connector()->ConnectToInterface("mojo:mus", &factory); + + mojom::WindowTreeClientPtr tree_client_ptr; + wt_client1_.reset(new TestWindowTreeClient()); + wt_client1_->Bind(GetProxy(&tree_client_ptr)); + + factory->CreateWindowTreeHost(GetProxy(&host_), + std::move(tree_client_ptr)); + + // Next we should get an embed call on the "window manager" client. + wt_client1_->WaitForOnEmbed(); + + 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_GT((*changes1())[0].client_id, 0); + client_id_1_ = (*changes1())[0].client_id; + ASSERT_FALSE((*changes1())[0].windows.empty()); + root_window_id_ = (*changes1())[0].windows[0].window_id; + ASSERT_EQ(root_window_id_, wt_client1_->root_window_id()); + changes1()->clear(); + } + + void TearDown() override { + // Destroy these before the message loop is destroyed (happens in + // WindowServerShellTestBase::TearDown). + wt_client1_.reset(); + wt_client2_.reset(); + wt_client3_.reset(); + client_factory_.reset(); + WindowServerShellTestBase::TearDown(); + } + + std::unique_ptr<TestWindowTreeClient> wt_client1_; + std::unique_ptr<TestWindowTreeClient> wt_client2_; + std::unique_ptr<TestWindowTreeClient> wt_client3_; + + mojom::WindowTreeHostPtr host_; + + private: + std::unique_ptr<WindowTreeClientFactory> client_factory_; + int client_id_1_; + int client_id_2_; + Id root_window_id_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeClientTest); +}; + +// Verifies two clients get different ids. +TEST_F(WindowTreeClientTest, TwoClientsGetDifferentClientIds) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + ASSERT_EQ(1u, changes2()->size()); + ASSERT_NE(client_id_1(), client_id_2()); +} + +// Verifies when Embed() is invoked any child windows are removed. +TEST_F(WindowTreeClientTest, WindowsRemovedWhenEmbedding) { + // Two windows 1 and 2. 2 is parented to 1. + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + ASSERT_EQ(1u, changes2()->size()); + ASSERT_EQ(1u, (*changes2())[0].windows.size()); + EXPECT_EQ("[" + WindowParentToString(window_1_1, kNullParentId) + "]", + ChangeWindowDescription(*changes2())); + + // Embed() removed window 2. + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_2, &windows); + EXPECT_EQ(WindowParentToString(window_1_2, kNullParentId), + SingleWindowDescription(windows)); + } + + // ws2 should not see window 2. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_1_1, &windows); + EXPECT_EQ(WindowParentToString(window_1_1, kNullParentId), + SingleWindowDescription(windows)); + } + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_1_2, &windows); + EXPECT_TRUE(windows.empty()); + } + + // Windows 3 and 4 in client 2. + Id window_2_3 = wt_client2()->NewWindow(3); + Id window_2_4 = wt_client2()->NewWindow(4); + ASSERT_TRUE(window_2_3); + ASSERT_TRUE(window_2_4); + ASSERT_TRUE(wt_client2()->AddWindow(window_2_3, window_2_4)); + + // Client 3 rooted at 2. + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_3)); + + // Window 4 should no longer have a parent. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_2_3, &windows); + EXPECT_EQ(WindowParentToString(window_2_3, kNullParentId), + SingleWindowDescription(windows)); + + windows.clear(); + GetWindowTree(wt2(), window_2_4, &windows); + EXPECT_EQ(WindowParentToString(window_2_4, kNullParentId), + SingleWindowDescription(windows)); + } + + // And window 4 should not be visible to client 3. + { + std::vector<TestWindow> windows; + GetWindowTree(wt3(), window_2_3, &windows); + EXPECT_EQ("no windows", SingleWindowDescription(windows)); + } +} + +// Verifies once Embed() has been invoked the parent client can't see any +// children. +TEST_F(WindowTreeClientTest, CantAccessChildrenOfEmbeddedWindow) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_2 = wt_client2()->NewWindow(2); + ASSERT_TRUE(window_2_2); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_2)); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_2)); + + Id window_3_3 = wt_client3()->NewWindow(3); + ASSERT_TRUE(window_3_3); + ASSERT_TRUE( + wt_client3()->AddWindow(wt_client3()->root_window_id(), window_3_3)); + + // Even though 3 is a child of 2 client 2 can't see 3 as it's from a + // different client. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_2_2, &windows); + EXPECT_EQ(WindowParentToString(window_2_2, window_1_1), + SingleWindowDescription(windows)); + } + + // Client 2 shouldn't be able to get window 3 at all. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_3_3, &windows); + EXPECT_TRUE(windows.empty()); + } + + // Client 1 should be able to see it all (its the root). + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(3u, windows.size()); + EXPECT_EQ(WindowParentToString(window_1_1, kNullParentId), + windows[0].ToString()); + // NOTE: we expect a match of WindowParentToString(window_2_2, window_1_1), + // but the ids are in the id space of client2, which is not the same as + // the id space of wt1(). + EXPECT_EQ("window=2,1 parent=1,1", windows[1].ToString()); + // Same thing here, we really want to test for + // WindowParentToString(window_3_3, window_2_2). + EXPECT_EQ("window=3,1 parent=2,1", windows[2].ToString()); + } +} + +// Verifies once Embed() has been invoked the parent can't mutate the children. +TEST_F(WindowTreeClientTest, CantModifyChildrenOfEmbeddedWindow) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_1)); + + Id window_2_2 = wt_client2()->NewWindow(2); + ASSERT_TRUE(window_2_2); + // Client 2 shouldn't be able to add anything to the window anymore. + ASSERT_FALSE(wt_client2()->AddWindow(window_2_1, window_2_2)); + + // Create window 3 in client 3 and add it to window 3. + Id window_3_1 = wt_client3()->NewWindow(1); + ASSERT_TRUE(window_3_1); + ASSERT_TRUE(wt_client3()->AddWindow(window_2_1, window_3_1)); + + // Client 2 shouldn't be able to remove window 3. + ASSERT_FALSE(wt_client2()->RemoveWindowFromParent(window_3_1)); +} + +// Verifies client gets a valid id. +TEST_F(WindowTreeClientTest, NewWindow) { + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a window with the same id. + ASSERT_EQ(0u, wt_client1()->NewWindowWithCompleteId(window_1_1)); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a window with a bogus client id. + ASSERT_EQ(0u, wt_client1()->NewWindowWithCompleteId( + BuildWindowId(client_id_1() + 1, 1))); + EXPECT_TRUE(changes1()->empty()); +} + +// Verifies AddWindow fails when window is already in position. +TEST_F(WindowTreeClientTest, AddWindowWithNoChange) { + // Create the embed point now so that the ids line up. + ASSERT_TRUE(wt_client1()->NewWindow(1)); + Id window_1_2 = wt_client1()->NewWindow(2); + Id window_1_3 = wt_client1()->NewWindow(3); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(window_1_3); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + // Make 3 a child of 2. + ASSERT_TRUE(wt_client1()->AddWindow(window_1_2, window_1_3)); + + // Try again, this should fail. + EXPECT_FALSE(wt_client1()->AddWindow(window_1_2, window_1_3)); +} + +// Verifies AddWindow fails when window is already in position. +TEST_F(WindowTreeClientTest, AddAncestorFails) { + // Create the embed point now so that the ids line up. + ASSERT_TRUE(wt_client1()->NewWindow(1)); + Id window_1_2 = wt_client1()->NewWindow(2); + Id window_1_3 = wt_client1()->NewWindow(3); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(window_1_3); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + // Make 3 a child of 2. + ASSERT_TRUE(wt_client1()->AddWindow(window_1_2, window_1_3)); + + // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3. + EXPECT_FALSE(wt_client1()->AddWindow(window_1_3, window_1_2)); +} + +// Verifies adding to root sends right notifications. +TEST_F(WindowTreeClientTest, AddToRoot) { + // Create the embed point now so that the ids line up. + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + Id window_1_21 = wt_client1()->NewWindow(21); + Id window_1_3 = wt_client1()->NewWindow(3); + ASSERT_TRUE(window_1_21); + ASSERT_TRUE(window_1_3); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + changes2()->clear(); + + // Make 3 a child of 21. + ASSERT_TRUE(wt_client1()->AddWindow(window_1_21, window_1_3)); + + // Make 21 a child of 1. + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_21)); + + // Client 2 should not be told anything (because the window is from a + // different client). Create a window to ensure we got a response from + // the server. + ASSERT_TRUE(wt_client2()->NewWindow(100)); + EXPECT_TRUE(changes2()->empty()); +} + +// Verifies HierarchyChanged is correctly sent for various adds/removes. +TEST_F(WindowTreeClientTest, WindowHierarchyChangedWindows) { + // Create the embed point now so that the ids line up. + Id window_1_1 = wt_client1()->NewWindow(1); + // 1,2->1,11. + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, true)); + Id window_1_11 = wt_client1()->NewWindow(11); + ASSERT_TRUE(window_1_11); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_11, true)); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_2, window_1_11)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + + ASSERT_TRUE(wt_client2()->WaitForAllMessages()); + changes2()->clear(); + + // 1,1->1,2->1,11 + { + // Client 2 should not get anything (1,2 is from another client). + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + ASSERT_TRUE(wt_client2()->WaitForAllMessages()); + 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(wt_client1()->AddWindow(root_window_id(), window_1_1)); + wt_client2_->WaitForChangeCount(1u); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_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(wt_client1()->RemoveWindowFromParent(window_1_1)); + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_1) + " drawn=false", + SingleChangeToDescription(*changes2())); + } + + // 1,1->1,2->1,11->1,111. + Id window_1_111 = wt_client1()->NewWindow(111); + ASSERT_TRUE(window_1_111); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_111, true)); + { + changes2()->clear(); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_11, window_1_111)); + ASSERT_TRUE(wt_client2()->WaitForAllMessages()); + EXPECT_TRUE(changes2()->empty()); + } + + // 0,1->1,1->1,2->1,11->1,111 + { + changes2()->clear(); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_1) + " drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(WindowTreeClientTest, WindowHierarchyChangedAddingKnownToUnknown) { + // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no + // parent). + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + + Id window_2_11 = wt_client2()->NewWindow(11); + Id window_2_2 = wt_client2()->NewWindow(2); + Id window_2_21 = wt_client2()->NewWindow(21); + ASSERT_TRUE(window_2_11); + ASSERT_TRUE(window_2_2); + ASSERT_TRUE(window_2_21); + + // Set up the hierarchy. + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_11)); + ASSERT_TRUE(wt_client2()->AddWindow(window_2_2, window_2_21)); + + // Remove 11, should result in a hierarchy change for the root. + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->RemoveWindowFromParent(window_2_11)); + + wt_client1_->WaitForChangeCount(1); + // 2,1 should be IdToString(window_2_11), but window_2_11 is in the id + // space of client2, not client1. + EXPECT_EQ("HierarchyChanged window=2,1 old_parent=" + + IdToString(window_1_1) + " new_parent=null", + SingleChangeToDescription(*changes1())); + } + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_2)); + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_2) + + " old_parent=null new_parent=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + // "window=2,3 parent=2,2]" should be, + // WindowParentToString(window_2_21, window_2_2), but isn't because of + // differing id spaces. + EXPECT_EQ("[" + WindowParentToString(window_2_2, window_1_1) + + "],[window=2,3 parent=2,2]", + ChangeWindowDescription(*changes1())); + } +} + +TEST_F(WindowTreeClientTest, ReorderWindow) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + Id window_2_1 = wt_client2()->NewWindow(1); + Id window_2_2 = wt_client2()->NewWindow(2); + Id window_2_3 = wt_client2()->NewWindow(3); + Id window_1_4 = wt_client1()->NewWindow(4); // Peer to 1,1 + Id window_1_5 = wt_client1()->NewWindow(5); // Peer to 1,1 + Id window_2_6 = wt_client2()->NewWindow(6); // Child of 1,2. + Id window_2_7 = wt_client2()->NewWindow(7); // Unparented. + Id window_2_8 = wt_client2()->NewWindow(8); // Unparented. + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(window_2_2); + ASSERT_TRUE(window_2_3); + ASSERT_TRUE(window_1_4); + ASSERT_TRUE(window_1_5); + ASSERT_TRUE(window_2_6); + ASSERT_TRUE(window_2_7); + ASSERT_TRUE(window_2_8); + + ASSERT_TRUE(wt_client2()->AddWindow(window_2_1, window_2_2)); + ASSERT_TRUE(wt_client2()->AddWindow(window_2_2, window_2_6)); + ASSERT_TRUE(wt_client2()->AddWindow(window_2_1, window_2_3)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_4)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_5)); + ASSERT_TRUE( + wt_client2()->AddWindow(BuildWindowId(client_id_1(), 1), window_2_1)); + + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->ReorderWindow(window_2_2, window_2_3, + mojom::OrderDirection::ABOVE)); + + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("Reordered window=" + IdToString(window_2_2) + " relative=" + + IdToString(window_2_3) + " direction=above", + SingleChangeToDescription(*changes1())); + } + + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->ReorderWindow(window_2_2, window_2_3, + mojom::OrderDirection::BELOW)); + + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("Reordered window=" + IdToString(window_2_2) + " relative=" + + IdToString(window_2_3) + " direction=below", + SingleChangeToDescription(*changes1())); + } + + // view2 is already below view3. + EXPECT_FALSE(wt_client2()->ReorderWindow(window_2_2, window_2_3, + mojom::OrderDirection::BELOW)); + + // view4 & 5 are unknown to client 2. + EXPECT_FALSE(wt_client2()->ReorderWindow(window_1_4, window_1_5, + mojom::OrderDirection::ABOVE)); + + // view6 & view3 have different parents. + EXPECT_FALSE(wt_client1()->ReorderWindow(window_2_3, window_2_6, + mojom::OrderDirection::ABOVE)); + + // Non-existent window-ids + EXPECT_FALSE(wt_client1()->ReorderWindow(BuildWindowId(client_id_1(), 27), + BuildWindowId(client_id_1(), 28), + mojom::OrderDirection::ABOVE)); + + // view7 & view8 are un-parented. + EXPECT_FALSE(wt_client1()->ReorderWindow(window_2_7, window_2_8, + mojom::OrderDirection::ABOVE)); +} + +// Verifies DeleteWindow works. +TEST_F(WindowTreeClientTest, DeleteWindow) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + + // Make 2 a child of 1. + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_1) + + " old_parent=null new_parent=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + changes2()->clear(); + ASSERT_TRUE(wt_client2()->DeleteWindow(window_2_1)); + EXPECT_TRUE(changes2()->empty()); + + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_2_1), + SingleChangeToDescription(*changes1())); + } +} + +// Verifies DeleteWindow isn't allowed from a separate client. +TEST_F(WindowTreeClientTest, DeleteWindowFromAnotherClientDisallowed) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + EXPECT_FALSE(wt_client2()->DeleteWindow(BuildWindowId(client_id_1(), 1))); +} + +// Verifies if a window was deleted and then reused that other clients are +// properly notified. +TEST_F(WindowTreeClientTest, ReuseDeletedWindowId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_1) + + " old_parent=null new_parent=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[" + WindowParentToString(window_2_1, window_1_1) + "]", + ChangeWindowDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->DeleteWindow(window_2_1)); + + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_2_1), + SingleChangeToDescription(*changes1())); + } + + // Create 2 again, and add it back to 1. Should get the same notification. + window_2_1 = wt_client2()->NewWindow(2); + ASSERT_TRUE(window_2_1); + { + changes1()->clear(); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_1) + + " old_parent=null new_parent=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[" + WindowParentToString(window_2_1, window_1_1) + "]", + ChangeWindowDescription(*changes1())); + } +} + +// Assertions for GetWindowTree. +TEST_F(WindowTreeClientTest, GetWindowTree) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + + // Create 11 in first client and make it a child of 1. + Id window_1_11 = wt_client1()->NewWindow(11); + ASSERT_TRUE(window_1_11); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_11)); + + // Create two windows in second client, 2 and 3, both children of 1. + Id window_2_1 = wt_client2()->NewWindow(1); + Id window_2_2 = wt_client2()->NewWindow(2); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(window_2_2); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_2)); + + // Verifies GetWindowTree() on the root. The root client sees all. + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), root_window_id(), &windows); + ASSERT_EQ(5u, windows.size()); + EXPECT_EQ(WindowParentToString(root_window_id(), kNullParentId), + windows[0].ToString()); + EXPECT_EQ(WindowParentToString(window_1_1, root_window_id()), + windows[1].ToString()); + EXPECT_EQ(WindowParentToString(window_1_11, window_1_1), + windows[2].ToString()); + EXPECT_EQ(WindowParentToString(window_2_1, window_1_1), + windows[3].ToString()); + EXPECT_EQ(WindowParentToString(window_2_2, window_1_1), + windows[4].ToString()); + } + + // Verifies GetWindowTree() on the window 1,1 from wt2(). wt2() sees 1,1 as + // 1,1 + // is wt2()'s root and wt2() sees all the windows it created. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_1_1, &windows); + ASSERT_EQ(3u, windows.size()); + EXPECT_EQ(WindowParentToString(window_1_1, kNullParentId), + windows[0].ToString()); + EXPECT_EQ(WindowParentToString(window_2_1, window_1_1), + windows[1].ToString()); + EXPECT_EQ(WindowParentToString(window_2_2, window_1_1), + windows[2].ToString()); + } + + // Client 2 shouldn't be able to get the root tree. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), root_window_id(), &windows); + ASSERT_EQ(0u, windows.size()); + } +} + +TEST_F(WindowTreeClientTest, SetWindowBounds) { + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + changes2()->clear(); + + wt_client2_->set_track_root_bounds_changes(true); + + wt1()->SetWindowBounds(10, window_1_1, gfx::Rect(0, 0, 100, 100)); + ASSERT_TRUE(wt_client1()->WaitForChangeCompleted(10)); + + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ("BoundsChanged window=" + IdToString(window_1_1) + + " old_bounds=0,0 0x0 new_bounds=0,0 100x100", + SingleChangeToDescription(*changes2())); + + // Should not be possible to change the bounds of a window created by another + // client. + wt2()->SetWindowBounds(11, window_1_1, gfx::Rect(0, 0, 0, 0)); + ASSERT_FALSE(wt_client2()->WaitForChangeCompleted(11)); +} + +// Verify AddWindow fails when trying to manipulate windows in other roots. +TEST_F(WindowTreeClientTest, CantMoveWindowsFromOtherRoot) { + // Create 1 and 2 in the first client. + Id window_1_1 = wt_client1()->NewWindow(1); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(window_1_2); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + // Try to move 2 to be a child of 1 from client 2. This should fail as 2 + // should not be able to access 1. + ASSERT_FALSE(wt_client2()->AddWindow(window_1_1, window_1_2)); + + // Try to reparent 1 to the root. A client is not allowed to reparent its + // roots. + ASSERT_FALSE(wt_client2()->AddWindow(root_window_id(), window_1_1)); +} + +// Verify RemoveWindowFromParent fails for windows that are descendants of the +// roots. +TEST_F(WindowTreeClientTest, CantRemoveWindowsInOtherRoots) { + // Create 1 and 2 in the first client and parent both to the root. + Id window_1_1 = wt_client1()->NewWindow(1); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(window_1_2); + + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_2)); + + // Establish the second client and give it the root 1. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + // Client 2 should not be able to remove window 2 or 1 from its parent. + ASSERT_FALSE(wt_client2()->RemoveWindowFromParent(window_1_2)); + ASSERT_FALSE(wt_client2()->RemoveWindowFromParent(window_1_1)); + + // Create windows 10 and 11 in 2. + Id window_2_10 = wt_client2()->NewWindow(10); + Id window_2_11 = wt_client2()->NewWindow(11); + ASSERT_TRUE(window_2_10); + ASSERT_TRUE(window_2_11); + + // Parent 11 to 10. + ASSERT_TRUE(wt_client2()->AddWindow(window_2_10, window_2_11)); + // Remove 11 from 10. + ASSERT_TRUE(wt_client2()->RemoveWindowFromParent(window_2_11)); + + // Verify nothing was actually removed. + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), root_window_id(), &windows); + ASSERT_EQ(3u, windows.size()); + EXPECT_EQ(WindowParentToString(root_window_id(), kNullParentId), + windows[0].ToString()); + EXPECT_EQ(WindowParentToString(window_1_1, root_window_id()), + windows[1].ToString()); + EXPECT_EQ(WindowParentToString(window_1_2, root_window_id()), + windows[2].ToString()); + } +} + +// Verify GetWindowTree fails for windows that are not descendants of the roots. +TEST_F(WindowTreeClientTest, CantGetWindowTreeOfOtherRoots) { + // Create 1 and 2 in the first client and parent both to the root. + Id window_1_1 = wt_client1()->NewWindow(1); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(window_1_2); + + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_2)); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + + std::vector<TestWindow> windows; + + // Should get nothing for the root. + GetWindowTree(wt2(), root_window_id(), &windows); + ASSERT_TRUE(windows.empty()); + + // Should get nothing for window 2. + GetWindowTree(wt2(), window_1_2, &windows); + ASSERT_TRUE(windows.empty()); + + // Should get window 1 if asked for. + GetWindowTree(wt2(), window_1_1, &windows); + ASSERT_EQ(1u, windows.size()); + EXPECT_EQ(WindowParentToString(window_1_1, kNullParentId), + windows[0].ToString()); +} + +TEST_F(WindowTreeClientTest, EmbedWithSameWindowId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + changes2()->clear(); + + Id window_1_1 = BuildWindowId(client_id_1(), 1); + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt1(), window_1_1)); + + // Client 2 should have been told of the unembed and delete. + { + wt_client2_->WaitForChangeCount(2); + EXPECT_EQ("OnUnembed window=" + IdToString(window_1_1), + ChangesToDescription1(*changes2())[0]); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_1_1), + ChangesToDescription1(*changes2())[1]); + } + + // Client 2 has no root. Verify it can't see window 1,1 anymore. + { + std::vector<TestWindow> windows; + GetWindowTree(wt2(), window_1_1, &windows); + EXPECT_TRUE(windows.empty()); + } +} + +TEST_F(WindowTreeClientTest, EmbedWithSameWindowId2) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + changes2()->clear(); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt1(), window_1_1)); + + // Client 2 should have been told about the unembed and delete. + wt_client2_->WaitForChangeCount(2); + changes2()->clear(); + + // Create a window in the third client and parent it to the root. + Id window_3_1 = wt_client3()->NewWindow(1); + ASSERT_TRUE(window_3_1); + ASSERT_TRUE(wt_client3()->AddWindow(window_1_1, window_3_1)); + + // Client 1 should have been told about the add (it owns the window). + { + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_3_1) + + " old_parent=null new_parent=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + } + + // Embed 1,1 again. + { + changes3()->clear(); + + // We should get a new client for the new embedding. + std::unique_ptr<TestWindowTreeClient> client4( + EstablishClientViaEmbed(wt1(), window_1_1, nullptr)); + ASSERT_TRUE(client4.get()); + EXPECT_EQ("[" + WindowParentToString(window_1_1, kNullParentId) + "]", + ChangeWindowDescription(*client4->tracker()->changes())); + + // And 3 should get an unembed and delete. + wt_client3_->WaitForChangeCount(2); + EXPECT_EQ("OnUnembed window=" + IdToString(window_1_1), + ChangesToDescription1(*changes3())[0]); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_1_1), + ChangesToDescription1(*changes3())[1]); + } + + // wt3() has no root. Verify it can't see window 1,1 anymore. + { + std::vector<TestWindow> windows; + GetWindowTree(wt3(), window_1_1, &windows); + EXPECT_TRUE(windows.empty()); + } + + // Verify 3,1 is no longer parented to 1,1. We have to do this from 1,1 as + // wt3() can no longer see 1,1. + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(1u, windows.size()); + EXPECT_EQ(WindowParentToString(window_1_1, kNullParentId), + windows[0].ToString()); + } + + // Verify wt3() can still see the window it created 3,1. + { + std::vector<TestWindow> windows; + GetWindowTree(wt3(), window_3_1, &windows); + ASSERT_EQ(1u, windows.size()); + EXPECT_EQ(WindowParentToString(window_3_1, kNullParentId), + windows[0].ToString()); + } +} + +// Assertions for SetWindowVisibility. +TEST_F(WindowTreeClientTest, SetWindowVisibility) { + // Create 1 and 2 in the first client and parent both to the root. + Id window_1_1 = wt_client1()->NewWindow(1); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(window_1_2); + + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), root_window_id(), &windows); + ASSERT_EQ(2u, windows.size()); + EXPECT_EQ( + WindowParentToString(root_window_id(), kNullParentId) + " visible=true", + windows[0].ToString2()); + EXPECT_EQ( + WindowParentToString(window_1_1, root_window_id()) + " visible=false", + windows[1].ToString2()); + } + + // Show all the windows. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, true)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), root_window_id(), &windows); + ASSERT_EQ(2u, windows.size()); + EXPECT_EQ( + WindowParentToString(root_window_id(), kNullParentId) + " visible=true", + windows[0].ToString2()); + EXPECT_EQ( + WindowParentToString(window_1_1, root_window_id()) + " visible=true", + windows[1].ToString2()); + } + + // Hide 1. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, false)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(1u, windows.size()); + EXPECT_EQ( + WindowParentToString(window_1_1, root_window_id()) + " visible=false", + windows[0].ToString2()); + } + + // Attach 2 to 1. + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(2u, windows.size()); + EXPECT_EQ( + WindowParentToString(window_1_1, root_window_id()) + " visible=false", + windows[0].ToString2()); + EXPECT_EQ(WindowParentToString(window_1_2, window_1_1) + " visible=true", + windows[1].ToString2()); + } + + // Show 1. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(2u, windows.size()); + EXPECT_EQ( + WindowParentToString(window_1_1, root_window_id()) + " visible=true", + windows[0].ToString2()); + EXPECT_EQ(WindowParentToString(window_1_2, window_1_1) + " visible=true", + windows[1].ToString2()); + } +} + +// Test that we hear the cursor change in other clients. +TEST_F(WindowTreeClientTest, SetCursor) { + // Get a second client to listen in. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + changes2()->clear(); + + ASSERT_TRUE( + wt_client1()->SetPredefinedCursor(window_1_1, mojom::Cursor::IBEAM)); + wt_client2_->WaitForChangeCount(1u); + + EXPECT_EQ("CursorChanged id=" + IdToString(window_1_1) + " cursor_id=4", + SingleChangeToDescription(*changes2())); +} + +// Assertions for SetWindowVisibility sending notifications. +TEST_F(WindowTreeClientTest, SetWindowVisibilityNotifications) { + // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root. + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + // Setting to the same value should return true. + EXPECT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, true)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + + // Establish the second client at 1,2. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClientWithRoot(window_1_2)); + + // Add 2,3 as a child of 1,2. + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->SetWindowVisibility(window_2_1, true)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_2, window_2_1)); + ASSERT_TRUE(wt_client1()->WaitForAllMessages()); + + changes2()->clear(); + // Hide 1,2 from client 1. Client 2 should see this. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, false)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "VisibilityChanged window=" + IdToString(window_1_2) + " visible=false", + SingleChangeToDescription(*changes2())); + } + + changes1()->clear(); + // Show 1,2 from client 2, client 1 should be notified. + ASSERT_TRUE(wt_client2()->SetWindowVisibility(window_1_2, true)); + { + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ( + "VisibilityChanged window=" + IdToString(window_1_2) + " visible=true", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Hide 1,1, client 2 should be told the draw state changed. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, false)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_2) + " drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Show 1,1 from client 1. Client 2 should see this. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_2) + " drawn=true", + SingleChangeToDescription(*changes2())); + } + + // Change visibility of 2,3, client 1 should see this. + changes1()->clear(); + ASSERT_TRUE(wt_client2()->SetWindowVisibility(window_2_1, false)); + { + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ( + "VisibilityChanged window=" + IdToString(window_2_1) + " visible=false", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Remove 1,1 from the root, client 2 should see drawn state changed. + ASSERT_TRUE(wt_client1()->RemoveWindowFromParent(window_1_1)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_2) + " drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Add 1,1 back to the root, client 2 should see drawn state changed. + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_2) + " drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +// Assertions for SetWindowVisibility sending notifications. +TEST_F(WindowTreeClientTest, SetWindowVisibilityNotifications2) { + // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root. + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + + // Establish the second client at 1,2. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClientWithRoot(window_1_2)); + EXPECT_EQ("OnEmbed drawn=true", SingleChangeToDescription2(*changes2())); + changes2()->clear(); + + // Show 1,2 from client 1. Client 2 should see this. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, true)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "VisibilityChanged window=" + IdToString(window_1_2) + " visible=true", + SingleChangeToDescription(*changes2())); + } +} + +// Assertions for SetWindowVisibility sending notifications. +TEST_F(WindowTreeClientTest, SetWindowVisibilityNotifications3) { + // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root. + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(window_1_1, window_1_2)); + + // Establish the second client at 1,2. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClientWithRoot(window_1_2)); + EXPECT_EQ("OnEmbed drawn=false", SingleChangeToDescription2(*changes2())); + changes2()->clear(); + + // Show 1,1, drawn should be true for 1,2 (as that is all the child sees). + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_1, true)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "DrawnStateChanged window=" + IdToString(window_1_2) + " drawn=true", + SingleChangeToDescription(*changes2())); + } + changes2()->clear(); + + // Show 1,2, visible should be true. + ASSERT_TRUE(wt_client1()->SetWindowVisibility(window_1_2, true)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "VisibilityChanged window=" + IdToString(window_1_2) + " visible=true", + SingleChangeToDescription(*changes2())); + } +} + +// Tests that when opacity is set on a window, that the calling client is not +// notified, however children are. Also that setting the same opacity is +// rejected and no on eis notifiyed. +TEST_F(WindowTreeClientTest, SetOpacityNotifications) { + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClientWithRoot(window_1_1)); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + ASSERT_TRUE(wt_client1()->WaitForAllMessages()); + + changes1()->clear(); + changes2()->clear(); + // Change opacity, no notification for calling client. + ASSERT_TRUE(wt_client1()->SetWindowOpacity(window_1_1, 0.5f)); + EXPECT_TRUE(changes1()->empty()); + wt_client2()->WaitForChangeCount(1); + EXPECT_EQ( + "OpacityChanged window_id=" + IdToString(window_1_1) + " opacity=0.50", + SingleChangeToDescription(*changes2())); + + changes2()->clear(); + // Attempting to set the same opacity should succeed, but no notification as + // there was no actual change. + ASSERT_TRUE(wt_client1()->SetWindowOpacity(window_1_1, 0.5f)); + EXPECT_TRUE(changes1()->empty()); + wt_client2()->WaitForAllMessages(); + EXPECT_TRUE(changes2()->empty()); +} + +TEST_F(WindowTreeClientTest, SetWindowProperty) { + Id window_1_1 = wt_client1()->NewWindow(1); + ASSERT_TRUE(window_1_1); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(false)); + changes2()->clear(); + + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), root_window_id(), &windows); + ASSERT_EQ(2u, windows.size()); + EXPECT_EQ(root_window_id(), windows[0].window_id); + EXPECT_EQ(window_1_1, windows[1].window_id); + ASSERT_EQ(0u, windows[1].properties.size()); + } + + // Set properties on 1. + changes2()->clear(); + std::vector<uint8_t> one(1, '1'); + ASSERT_TRUE(wt_client1()->SetWindowProperty(window_1_1, "one", &one)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ( + "PropertyChanged window=" + IdToString(window_1_1) + " key=one value=1", + SingleChangeToDescription(*changes2())); + } + + // Test that our properties exist in the window tree + { + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + ASSERT_EQ(1u, windows.size()); + ASSERT_EQ(1u, windows[0].properties.size()); + EXPECT_EQ(one, windows[0].properties["one"]); + } + + changes2()->clear(); + // Set back to null. + ASSERT_TRUE(wt_client1()->SetWindowProperty(window_1_1, "one", NULL)); + { + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ("PropertyChanged window=" + IdToString(window_1_1) + + " key=one value=NULL", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(WindowTreeClientTest, OnEmbeddedAppDisconnected) { + // Create client 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_1)); + + // Client 1 should get a hierarchy change for window_2_1. + wt_client1_->WaitForChangeCount(1); + changes1()->clear(); + + // Close client 3. Client 2 (which had previously embedded 3) should + // be notified of this. + wt_client3_.reset(); + wt_client2_->WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected window=" + IdToString(window_2_1), + SingleChangeToDescription(*changes2())); + + // The closing is only interesting to the root that did the embedding. Other + // clients should not be notified of this. + wt_client1_->WaitForAllMessages(); + EXPECT_TRUE(changes1()->empty()); +} + +// Verifies when the parent of an Embed() is destroyed the embedded app gets +// a WindowDeleted (and doesn't trigger a DCHECK). +TEST_F(WindowTreeClientTest, OnParentOfEmbedDisconnects) { + // Create client 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + Id window_2_1 = wt_client2()->NewWindow(1); + Id window_2_2 = wt_client2()->NewWindow(2); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(window_2_2); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + ASSERT_TRUE(wt_client2()->AddWindow(window_2_1, window_2_2)); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_2)); + changes3()->clear(); + + // Close client 2. Client 3 should get a delete (for its root). + wt_client2_.reset(); + wt_client3_->WaitForChangeCount(1); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_2_2), + SingleChangeToDescription(*changes3())); +} + +// Verifies WindowTreeImpl doesn't incorrectly erase from its internal +// map when a window from another client with the same window_id is removed. +TEST_F(WindowTreeClientTest, DontCleanMapOnDestroy) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + ASSERT_TRUE(wt_client2()->NewWindow(1)); + changes1()->clear(); + wt_client2_.reset(); + wt_client1_->WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected window=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + std::vector<TestWindow> windows; + GetWindowTree(wt1(), window_1_1, &windows); + EXPECT_FALSE(windows.empty()); +} + +// Verifies Embed() works when supplying a WindowTreeClient. +TEST_F(WindowTreeClientTest, EmbedSupplyingWindowTreeClient) { + ASSERT_TRUE(wt_client1()->NewWindow(1)); + + TestWindowTreeClient client2; + mojom::WindowTreeClientPtr client2_ptr; + mojo::Binding<WindowTreeClient> client2_binding(&client2, &client2_ptr); + ASSERT_TRUE(Embed(wt1(), BuildWindowId(client_id_1(), 1), + std::move(client2_ptr))); + client2.WaitForOnEmbed(); + EXPECT_EQ("OnEmbed", + SingleChangeToDescription(*client2.tracker()->changes())); +} + +TEST_F(WindowTreeClientTest, EmbedFailsFromOtherClient) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt2(), window_2_1)); + + Id window_3_3 = wt_client3()->NewWindow(3); + ASSERT_TRUE(window_3_3); + ASSERT_TRUE(wt_client3()->AddWindow(window_2_1, window_3_3)); + + // 2 should not be able to embed in window_3_3 as window_3_3 was not created + // by + // 2. + EXPECT_FALSE(EmbedUrl(connector(), wt2(), test_name(), window_3_3)); +} + +// Verifies Embed() from window manager on another clients window works. +TEST_F(WindowTreeClientTest, EmbedFromOtherClient) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + + Id window_1_1 = BuildWindowId(client_id_1(), 1); + Id window_2_1 = wt_client2()->NewWindow(1); + ASSERT_TRUE(window_2_1); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + + changes2()->clear(); + + // Establish a third client in window_2_1. + ASSERT_NO_FATAL_FAILURE(EstablishThirdClient(wt1(), window_2_1)); + + ASSERT_TRUE(wt_client2()->WaitForAllMessages()); + EXPECT_EQ(std::string(), SingleChangeToDescription(*changes2())); +} + +TEST_F(WindowTreeClientTest, CantEmbedFromClientRoot) { + // Shouldn't be able to embed into the root. + ASSERT_FALSE(EmbedUrl(connector(), wt1(), test_name(), root_window_id())); + + // Even though the call above failed a WindowTreeClient was obtained. We need + // to + // wait for it else we throw off the next connect. + WaitForWindowTreeClient(); + + // Don't allow a client to embed into its own root. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + EXPECT_FALSE(EmbedUrl(connector(), wt2(), test_name(), + BuildWindowId(client_id_1(), 1))); + + // Need to wait for a WindowTreeClient for same reason as above. + WaitForWindowTreeClient(); + + Id window_1_2 = wt_client1()->NewWindow(2); + ASSERT_TRUE(window_1_2); + ASSERT_TRUE( + wt_client1()->AddWindow(BuildWindowId(client_id_1(), 1), window_1_2)); + ASSERT_TRUE(wt_client3_.get() == nullptr); + wt_client3_ = + EstablishClientViaEmbedWithPolicyBitmask(wt1(), window_1_2, nullptr); + ASSERT_TRUE(wt_client3_.get() != nullptr); + + // window_1_2 is ws3's root, so even though v3 is an embed root it should not + // be able to Embed into itself. + ASSERT_FALSE(EmbedUrl(connector(), wt3(), test_name(), window_1_2)); +} + +// Verifies that a transient window tracks its parent's lifetime. +TEST_F(WindowTreeClientTest, TransientWindowTracksTransientParentLifetime) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondClient(true)); + Id window_1_1 = BuildWindowId(client_id_1(), 1); + + Id window_2_1 = wt_client2()->NewWindow(1); + Id window_2_2 = wt_client2()->NewWindow(2); + Id window_2_3 = wt_client2()->NewWindow(3); + ASSERT_TRUE(window_2_1); + + // root -> window_1_1 -> window_2_1 + // root -> window_1_1 -> window_2_2 + // root -> window_1_1 -> window_2_3 + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_1)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_2)); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_1, window_2_3)); + + // window_2_2 and window_2_3 now track the lifetime of window_2_1. + changes1()->clear(); + wt2()->AddTransientWindow(10, window_2_1, window_2_2); + wt2()->AddTransientWindow(11, window_2_1, window_2_3); + wt_client1()->WaitForChangeCount(2); + EXPECT_EQ("AddTransientWindow parent = " + IdToString(window_2_1) + + " child = " + IdToString(window_2_2), + ChangesToDescription1(*changes1())[0]); + EXPECT_EQ("AddTransientWindow parent = " + IdToString(window_2_1) + + " child = " + IdToString(window_2_3), + ChangesToDescription1(*changes1())[1]); + + changes1()->clear(); + wt2()->RemoveTransientWindowFromParent(12, window_2_3); + wt_client1()->WaitForChangeCount(1); + EXPECT_EQ("RemoveTransientWindowFromParent parent = " + + IdToString(window_2_1) + " child = " + IdToString(window_2_3), + SingleChangeToDescription(*changes1())); + + changes1()->clear(); + ASSERT_TRUE(wt_client2()->DeleteWindow(window_2_1)); + wt_client1()->WaitForChangeCount(2); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_2_2), + ChangesToDescription1(*changes1())[0]); + EXPECT_EQ("WindowDeleted window=" + IdToString(window_2_1), + ChangesToDescription1(*changes1())[1]); +} + +TEST_F(WindowTreeClientTest, Ids) { + const Id window_1_100 = wt_client1()->NewWindow(100); + ASSERT_TRUE(window_1_100); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_100)); + + // Establish the second client at 1,100. + ASSERT_NO_FATAL_FAILURE(EstablishSecondClientWithRoot(window_1_100)); + + // 1,100 is the id in the wt_client1's id space. The new client should see + // 2,1 (the server id). + const Id window_1_100_in_ws2 = BuildWindowId(client_id_1(), 1); + EXPECT_EQ(window_1_100_in_ws2, wt_client2()->root_window_id()); + + // The first window created in the second client gets a server id of 2,1 + // regardless of the id the client uses. + const Id window_2_101 = wt_client2()->NewWindow(101); + ASSERT_TRUE(wt_client2()->AddWindow(window_1_100_in_ws2, window_2_101)); + const Id window_2_101_in_ws1 = BuildWindowId(client_id_2(), 1); + wt_client1()->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_101_in_ws1) + + " old_parent=null new_parent=" + IdToString(window_1_100), + SingleChangeToDescription(*changes1())); + changes1()->clear(); + + // Change the bounds of window_2_101 and make sure server gets it. + wt2()->SetWindowBounds(11, window_2_101, gfx::Rect(1, 2, 3, 4)); + ASSERT_TRUE(wt_client2()->WaitForChangeCompleted(11)); + wt_client1()->WaitForChangeCount(1); + EXPECT_EQ("BoundsChanged window=" + IdToString(window_2_101_in_ws1) + + " old_bounds=0,0 0x0 new_bounds=1,2 3x4", + SingleChangeToDescription(*changes1())); + changes2()->clear(); + + // Remove 2_101 from wm, client1 should see the change. + wt1()->RemoveWindowFromParent(12, window_2_101_in_ws1); + ASSERT_TRUE(wt_client1()->WaitForChangeCompleted(12)); + wt_client2()->WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged window=" + IdToString(window_2_101) + + " old_parent=" + IdToString(window_1_100_in_ws2) + + " new_parent=null", + SingleChangeToDescription(*changes2())); +} + +// Tests that setting capture fails when no input event has occurred, and there +// is no notification of lost capture. +TEST_F(WindowTreeClientTest, ExplicitCaptureWithoutInput) { + Id window_1_1 = wt_client1()->NewWindow(1); + + // Add the window to the root, so that they have a Display to handle input + // capture. + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + changes1()->clear(); + + // Since there has been no input, capture should not succeed. No lost capture + // message is expected. + wt1()->SetCapture(1, window_1_1); + wt_client1_->WaitForAllMessages(); + EXPECT_TRUE(changes1()->empty()); + + // Since there is no window with capture, lost capture should not be notified. + wt1()->ReleaseCapture(3, window_1_1); + wt_client1_->WaitForAllMessages(); + EXPECT_TRUE(changes1()->empty()); +} + +// TODO(jonross): Enable this once apptests can send input events to the server. +// Enabling capture requires that the client be processing events. +TEST_F(WindowTreeClientTest, DISABLED_ExplicitCapturePropagation) { + Id window_1_1 = wt_client1()->NewWindow(1); + Id window_1_2 = wt_client1()->NewWindow(2); + + // Add the windows to the root, so that they have a Display to handle input + // capture. + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_1)); + ASSERT_TRUE(wt_client1()->AddWindow(root_window_id(), window_1_2)); + + changes1()->clear(); + // Window 1 takes capture then Window 2 takes capture. + // Verify that window 1 has lost capture. + wt1()->SetCapture(1, window_1_1); + wt1()->SetCapture(2, window_1_2); + wt_client1_->WaitForChangeCount(1); + + EXPECT_EQ("OnLostCapture window=" + IdToString(window_1_1), + SingleChangeToDescription(*changes1())); + + changes1()->clear(); + // Explicitly releasing capture should not notify of lost capture. + wt1()->ReleaseCapture(3, window_1_2); + wt_client1_->WaitForAllMessages(); + + EXPECT_TRUE(changes1()->empty()); +} + +// TODO(sky): need to better track changes to initial client. For example, +// that SetBounsdWindows/AddWindow and the like don't result in messages to the +// originating client. + +// TODO(sky): make sure coverage of what was +// WindowManagerTest.SecondEmbedRoot_InitService and +// WindowManagerTest.MultipleEmbedRootsBeforeWTHReady gets added to window +// manager +// tests. + +} // namespace test +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_tree_factory.cc b/chromium/components/mus/ws/window_tree_factory.cc new file mode 100644 index 00000000000..c8befb724ab --- /dev/null +++ b/chromium/components/mus/ws/window_tree_factory.cc @@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_tree_factory.h" + +#include "base/memory/ptr_util.h" +#include "components/mus/ws/default_access_policy.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_tree.h" +#include "components/mus/ws/window_tree_binding.h" + +namespace mus { +namespace ws { + +WindowTreeFactory::WindowTreeFactory(WindowServer* window_server, + const UserId& user_id, + const std::string& client_name, + mojom::WindowTreeFactoryRequest request) + : window_server_(window_server), + user_id_(user_id), + client_name_(client_name), + binding_(this, std::move(request)) {} + +WindowTreeFactory::~WindowTreeFactory() {} + +void WindowTreeFactory::CreateWindowTree( + mojo::InterfaceRequest<mojom::WindowTree> tree_request, + mojom::WindowTreeClientPtr client) { + std::unique_ptr<ws::WindowTree> service( + new ws::WindowTree(window_server_, user_id_, nullptr, + base::WrapUnique(new DefaultAccessPolicy))); + std::unique_ptr<ws::DefaultWindowTreeBinding> binding( + new ws::DefaultWindowTreeBinding(service.get(), window_server_, + std::move(tree_request), + std::move(client))); + service->set_name(client_name_); + window_server_->AddTree(std::move(service), std::move(binding), nullptr); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_tree_factory.h b/chromium/components/mus/ws/window_tree_factory.h new file mode 100644 index 00000000000..7a21ea94a23 --- /dev/null +++ b/chromium/components/mus/ws/window_tree_factory.h @@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_TREE_FACTORY_H_ +#define COMPONENTS_MUS_WS_WINDOW_TREE_FACTORY_H_ + +#include "base/macros.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/strong_binding.h" + +namespace mus { +namespace ws { + +class WindowServer; + +class WindowTreeFactory : public mus::mojom::WindowTreeFactory { + public: + WindowTreeFactory(WindowServer* window_server, + const UserId& user_id, + const std::string& client_name, + mojom::WindowTreeFactoryRequest request); + private: + ~WindowTreeFactory() override; + + // mus::mojom::WindowTreeFactory: + void CreateWindowTree(mojo::InterfaceRequest<mojom::WindowTree> tree_request, + mojom::WindowTreeClientPtr client) override; + + WindowServer* window_server_; + const UserId user_id_; + const std::string client_name_; + mojo::StrongBinding<mus::mojom::WindowTreeFactory> binding_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeFactory); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_TREE_FACTORY_H_ diff --git a/chromium/components/mus/ws/window_tree_host_factory.cc b/chromium/components/mus/ws/window_tree_host_factory.cc new file mode 100644 index 00000000000..c5c6cbc208b --- /dev/null +++ b/chromium/components/mus/ws/window_tree_host_factory.cc @@ -0,0 +1,42 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/mus/ws/window_tree_host_factory.h" + +#include "components/mus/gles2/gpu_state.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/display.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/window_server.h" + +namespace mus { +namespace ws { + +WindowTreeHostFactory::WindowTreeHostFactory( + WindowServer* window_server, + const UserId& user_id, + const PlatformDisplayInitParams& platform_display_init_params) + : window_server_(window_server), + user_id_(user_id), + platform_display_init_params_(platform_display_init_params) {} + +WindowTreeHostFactory::~WindowTreeHostFactory() {} + +void WindowTreeHostFactory::AddBinding( + mojom::WindowTreeHostFactoryRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +void WindowTreeHostFactory::CreateWindowTreeHost( + mojom::WindowTreeHostRequest host, + mojom::WindowTreeClientPtr tree_client) { + Display* display = new Display(window_server_, platform_display_init_params_); + std::unique_ptr<DisplayBindingImpl> display_binding( + new DisplayBindingImpl(std::move(host), display, user_id_, + std::move(tree_client), window_server_)); + display->Init(std::move(display_binding)); +} + +} // namespace ws +} // namespace mus diff --git a/chromium/components/mus/ws/window_tree_host_factory.h b/chromium/components/mus/ws/window_tree_host_factory.h new file mode 100644 index 00000000000..3df36ee7f85 --- /dev/null +++ b/chromium/components/mus/ws/window_tree_host_factory.h @@ -0,0 +1,46 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_MUS_WS_WINDOW_TREE_HOST_FACTORY_H_ +#define COMPONENTS_MUS_WS_WINDOW_TREE_HOST_FACTORY_H_ + +#include <stdint.h> + +#include "components/mus/public/interfaces/window_tree_host.mojom.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/user_id.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +namespace mus { +namespace ws { + +class WindowServer; + +class WindowTreeHostFactory : public mojom::WindowTreeHostFactory { + public: + WindowTreeHostFactory( + WindowServer* window_server, + const UserId& user_id, + const PlatformDisplayInitParams& platform_display_init_params); + ~WindowTreeHostFactory() override; + + void AddBinding(mojom::WindowTreeHostFactoryRequest request); + + private: + // mojom::WindowTreeHostFactory implementation. + void CreateWindowTreeHost(mojom::WindowTreeHostRequest host, + mojom::WindowTreeClientPtr tree_client) override; + + WindowServer* window_server_; + const UserId user_id_; + const PlatformDisplayInitParams platform_display_init_params_; + mojo::BindingSet<mojom::WindowTreeHostFactory> bindings_; + + DISALLOW_COPY_AND_ASSIGN(WindowTreeHostFactory); +}; + +} // namespace ws +} // namespace mus + +#endif // COMPONENTS_MUS_WS_WINDOW_TREE_HOST_FACTORY_H_ diff --git a/chromium/components/mus/ws/window_tree_unittest.cc b/chromium/components/mus/ws/window_tree_unittest.cc new file mode 100644 index 00000000000..15356763085 --- /dev/null +++ b/chromium/components/mus/ws/window_tree_unittest.cc @@ -0,0 +1,1017 @@ +// 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 "components/mus/ws/window_tree.h" + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" +#include "components/mus/common/types.h" +#include "components/mus/common/util.h" +#include "components/mus/public/interfaces/window_tree.mojom.h" +#include "components/mus/surfaces/surfaces_state.h" +#include "components/mus/ws/default_access_policy.h" +#include "components/mus/ws/display_binding.h" +#include "components/mus/ws/ids.h" +#include "components/mus/ws/platform_display.h" +#include "components/mus/ws/platform_display_factory.h" +#include "components/mus/ws/platform_display_init_params.h" +#include "components/mus/ws/server_window.h" +#include "components/mus/ws/server_window_surface_manager_test_api.h" +#include "components/mus/ws/test_change_tracker.h" +#include "components/mus/ws/test_server_window_delegate.h" +#include "components/mus/ws/test_utils.h" +#include "components/mus/ws/window_manager_access_policy.h" +#include "components/mus/ws/window_manager_display_root.h" +#include "components/mus/ws/window_server.h" +#include "components/mus/ws/window_server_delegate.h" +#include "components/mus/ws/window_tree_binding.h" +#include "services/shell/public/interfaces/connector.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/geometry/rect.h" + +namespace mus { +namespace ws { +namespace test { +namespace { + +std::string WindowIdToString(const WindowId& id) { + return base::StringPrintf("%d,%d", id.client_id, id.window_id); +} + +ClientWindowId BuildClientWindowId(WindowTree* tree, + ClientSpecificId window_id) { + return ClientWindowId(WindowIdToTransportId(WindowId(tree->id(), window_id))); +} + +// ----------------------------------------------------------------------------- + +ui::PointerEvent CreatePointerDownEvent(int x, int y) { + return ui::PointerEvent(ui::TouchEvent(ui::ET_TOUCH_PRESSED, gfx::Point(x, y), + 1, ui::EventTimeForNow())); +} + +ui::PointerEvent CreatePointerUpEvent(int x, int y) { + return ui::PointerEvent(ui::TouchEvent( + ui::ET_TOUCH_RELEASED, gfx::Point(x, y), 1, ui::EventTimeForNow())); +} + +ui::PointerEvent CreateMouseMoveEvent(int x, int y) { + return ui::PointerEvent( + ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(x, y), gfx::Point(x, y), + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE)); +} + +ui::PointerEvent CreateMouseDownEvent(int x, int y) { + return ui::PointerEvent( + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(x, y), gfx::Point(x, y), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); +} + +ui::PointerEvent CreateMouseUpEvent(int x, int y) { + return ui::PointerEvent( + ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(x, y), gfx::Point(x, y), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON)); +} + +ServerWindow* GetCaptureWindow(Display* display) { + return display->GetActiveWindowManagerDisplayRoot() + ->window_manager_state() + ->capture_window(); +} + +mojom::EventMatcherPtr CreateEventMatcher(ui::mojom::EventType type) { + mojom::EventMatcherPtr matcher = mojom::EventMatcher::New(); + matcher->type_matcher = mojom::EventTypeMatcher::New(); + matcher->type_matcher->type = type; + return matcher; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +class WindowTreeTest : public testing::Test { + public: + WindowTreeTest() {} + ~WindowTreeTest() override {} + + mus::mojom::Cursor cursor_id() { + return static_cast<mus::mojom::Cursor>( + window_event_targeting_helper_.cursor_id()); + } + Display* display() { return window_event_targeting_helper_.display(); } + TestWindowTreeBinding* last_binding() { + return window_event_targeting_helper_.last_binding(); + } + TestWindowTreeClient* last_window_tree_client() { + return window_event_targeting_helper_.last_window_tree_client(); + } + TestWindowTreeClient* wm_client() { + return window_event_targeting_helper_.wm_client(); + } + WindowServer* window_server() { + return window_event_targeting_helper_.window_server(); + } + WindowTree* wm_tree() { + return window_event_targeting_helper_.window_server()->GetTreeWithId(1); + } + + void DispatchEventWithoutAck(const ui::Event& event) { + DisplayTestApi(display()).OnEvent(event); + } + + void set_window_manager_internal(WindowTree* tree, + mojom::WindowManager* wm_internal) { + WindowTreeTestApi(tree).set_window_manager_internal(wm_internal); + } + + void AckPreviousEvent() { + WindowManagerStateTestApi test_api( + display()->GetActiveWindowManagerDisplayRoot()->window_manager_state()); + while (test_api.tree_awaiting_input_ack()) { + test_api.tree_awaiting_input_ack()->OnWindowInputEventAck( + 0, mojom::EventResult::HANDLED); + } + } + + void DispatchEventAndAckImmediately(const ui::Event& event) { + DispatchEventWithoutAck(event); + AckPreviousEvent(); + } + + // Creates a new window from wm_tree() and embeds a new client in it. + void SetupEventTargeting(TestWindowTreeClient** out_client, + WindowTree** window_tree, + ServerWindow** window); + + // Creates a new tree as the specified user. This does what creation via + // a WindowTreeFactory does. + WindowTree* CreateNewTree(const UserId& user_id, + TestWindowTreeBinding** binding) { + WindowTree* tree = + new WindowTree(window_server(), user_id, nullptr, + base::WrapUnique(new DefaultAccessPolicy)); + *binding = new TestWindowTreeBinding(tree); + window_server()->AddTree(base::WrapUnique(tree), base::WrapUnique(*binding), + nullptr); + return tree; + } + + protected: + WindowEventTargetingHelper window_event_targeting_helper_; + + private: + DISALLOW_COPY_AND_ASSIGN(WindowTreeTest); +}; + +// Creates a new window in wm_tree(), adds it to the root, embeds a +// new client in the window and creates a child of said window. |window| is +// set to the child of |window_tree| that is created. +void WindowTreeTest::SetupEventTargeting(TestWindowTreeClient** out_client, + WindowTree** window_tree, + ServerWindow** window) { + ServerWindow* embed_window = window_event_targeting_helper_.CreatePrimaryTree( + gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 50, 50)); + window_event_targeting_helper_.CreateSecondaryTree( + embed_window, gfx::Rect(20, 20, 20, 20), out_client, window_tree, window); +} + +// Verifies focus correctly changes on pointer events. +TEST_F(WindowTreeTest, FocusOnPointer) { + const ClientWindowId embed_window_id = BuildClientWindowId(wm_tree(), 1); + EXPECT_TRUE( + wm_tree()->NewWindow(embed_window_id, ServerWindow::Properties())); + ServerWindow* embed_window = wm_tree()->GetWindowByClientId(embed_window_id); + ASSERT_TRUE(embed_window); + EXPECT_TRUE(wm_tree()->SetWindowVisibility(embed_window_id, true)); + ASSERT_TRUE(FirstRoot(wm_tree())); + const ClientWindowId wm_root_id = FirstRootId(wm_tree()); + EXPECT_TRUE(wm_tree()->AddWindow(wm_root_id, embed_window_id)); + display()->root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + mojom::WindowTreeClientPtr client; + mojom::WindowTreeClientRequest client_request = GetProxy(&client); + wm_client()->Bind(std::move(client_request)); + const uint32_t embed_flags = 0; + wm_tree()->Embed(embed_window_id, std::move(client), embed_flags); + WindowTree* tree1 = window_server()->GetTreeWithRoot(embed_window); + ASSERT_TRUE(tree1 != nullptr); + ASSERT_NE(tree1, wm_tree()); + + embed_window->SetBounds(gfx::Rect(0, 0, 50, 50)); + + const ClientWindowId child1_id(BuildClientWindowId(tree1, 1)); + EXPECT_TRUE(tree1->NewWindow(child1_id, ServerWindow::Properties())); + EXPECT_TRUE(tree1->AddWindow(ClientWindowIdForWindow(tree1, embed_window), + child1_id)); + ServerWindow* child1 = tree1->GetWindowByClientId(child1_id); + ASSERT_TRUE(child1); + child1->SetVisible(true); + child1->SetBounds(gfx::Rect(20, 20, 20, 20)); + EnableHitTest(child1); + + TestWindowTreeClient* tree1_client = last_window_tree_client(); + tree1_client->tracker()->changes()->clear(); + wm_client()->tracker()->changes()->clear(); + + // Focus should not go to |child1| yet, since the parent still doesn't allow + // active children. + DispatchEventAndAckImmediately(CreatePointerDownEvent(21, 22)); + Display* display1 = tree1->GetDisplay(embed_window); + EXPECT_EQ(nullptr, display1->GetFocusedWindow()); + DispatchEventAndAckImmediately(CreatePointerUpEvent(21, 22)); + tree1_client->tracker()->changes()->clear(); + wm_client()->tracker()->changes()->clear(); + + display1->AddActivationParent(embed_window); + + // Focus should go to child1. This result in notifying both the window + // manager and client client being notified. + DispatchEventAndAckImmediately(CreatePointerDownEvent(21, 22)); + EXPECT_EQ(child1, display1->GetFocusedWindow()); + ASSERT_GE(wm_client()->tracker()->changes()->size(), 1u); + EXPECT_EQ("Focused id=2,1", + ChangesToDescription1(*wm_client()->tracker()->changes())[0]); + ASSERT_GE(tree1_client->tracker()->changes()->size(), 1u); + EXPECT_EQ("Focused id=2,1", + ChangesToDescription1(*tree1_client->tracker()->changes())[0]); + + DispatchEventAndAckImmediately(CreatePointerUpEvent(21, 22)); + wm_client()->tracker()->changes()->clear(); + tree1_client->tracker()->changes()->clear(); + + // Press outside of the embedded window. Note that root cannot be focused + // (because it cannot be activated). So the focus would not move in this case. + DispatchEventAndAckImmediately(CreatePointerDownEvent(61, 22)); + EXPECT_EQ(child1, display()->GetFocusedWindow()); + + DispatchEventAndAckImmediately(CreatePointerUpEvent(21, 22)); + wm_client()->tracker()->changes()->clear(); + tree1_client->tracker()->changes()->clear(); + + // Press in the same location. Should not get a focus change event (only input + // event). + DispatchEventAndAckImmediately(CreatePointerDownEvent(61, 22)); + EXPECT_EQ(child1, display()->GetFocusedWindow()); + ASSERT_EQ(wm_client()->tracker()->changes()->size(), 1u) + << SingleChangeToDescription(*wm_client()->tracker()->changes()); + EXPECT_EQ("InputEvent window=0,3 event_action=16", + ChangesToDescription1(*wm_client()->tracker()->changes())[0]); + EXPECT_TRUE(tree1_client->tracker()->changes()->empty()); +} + +TEST_F(WindowTreeTest, BasicInputEventTarget) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE( + SetupEventTargeting(&embed_client, &tree, &window)); + + // Send an event to |v1|. |embed_client| should get the event, not + // |wm_client|, since |v1| lives inside an embedded window. + DispatchEventAndAckImmediately(CreatePointerDownEvent(21, 22)); + ASSERT_EQ(1u, wm_client()->tracker()->changes()->size()); + EXPECT_EQ("Focused id=2,1", + ChangesToDescription1(*wm_client()->tracker()->changes())[0]); + ASSERT_EQ(2u, embed_client->tracker()->changes()->size()); + EXPECT_EQ("Focused id=2,1", + ChangesToDescription1(*embed_client->tracker()->changes())[0]); + EXPECT_EQ("InputEvent window=2,1 event_action=16", + ChangesToDescription1(*embed_client->tracker()->changes())[1]); +} + +// Tests that a client can observe events outside its bounds. +TEST_F(WindowTreeTest, SetEventObserver) { + // Create an embedded client. + TestWindowTreeClient* client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&client, &tree, &window)); + + // Create an event outside the bounds of the client. + ui::PointerEvent pointer_down = CreatePointerDownEvent(5, 5); + + // Events are not observed before setting an observer. + DispatchEventAndAckImmediately(pointer_down); + ASSERT_EQ(0u, client->tracker()->changes()->size()); + + // Create an observer for pointer-down events. + WindowTreeTestApi(tree).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::POINTER_DOWN), 111u); + + // Pointer-down events are sent to the client. + DispatchEventAndAckImmediately(pointer_down); + ASSERT_EQ(1u, client->tracker()->changes()->size()); + EXPECT_EQ("EventObserved event_action=16 event_observer_id=111", + ChangesToDescription1(*client->tracker()->changes())[0]); + client->tracker()->changes()->clear(); + + // Clearing the observer stops sending events to the client. + WindowTreeTestApi(tree).SetEventObserver(nullptr, 0u); + DispatchEventAndAckImmediately(pointer_down); + ASSERT_EQ(0u, client->tracker()->changes()->size()); +} + +// Tests that a client using an event observer does not receive events that +// don't match the EventMatcher spec. +TEST_F(WindowTreeTest, SetEventObserverNonMatching) { + // Create an embedded client. + TestWindowTreeClient* client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&client, &tree, &window)); + + // Create an observer for pointer-down events. + WindowTreeTestApi(tree).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::POINTER_DOWN), 111u); + + // Pointer-up events are not sent to the client, since they don't match. + DispatchEventAndAckImmediately(CreatePointerUpEvent(5, 5)); + ASSERT_EQ(0u, client->tracker()->changes()->size()); +} + +// Tests that an event that both hits a client window and matches an event +// observer is sent only once to the client. +TEST_F(WindowTreeTest, SetEventObserverSendsOnce) { + // Create an embedded client. + TestWindowTreeClient* client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&client, &tree, &window)); + + // Create an observer for pointer-up events (which do not cause focus + // changes). + WindowTreeTestApi(tree).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::POINTER_UP), 111u); + + // Create an event inside the bounds of the client. + ui::PointerEvent pointer_up = CreatePointerUpEvent(25, 25); + + // The event is dispatched once, with a flag set that it matched the event + // observer. + DispatchEventAndAckImmediately(pointer_up); + ASSERT_EQ(1u, client->tracker()->changes()->size()); + EXPECT_EQ("InputEvent window=2,1 event_action=18 event_observer_id=111", + SingleChangeToDescription(*client->tracker()->changes())); +} + +// Tests that events generated by user A are not observed by event observers for +// user B. +TEST_F(WindowTreeTest, SetEventObserverWrongUser) { + // Embed a window tree belonging to a different user. + TestWindowTreeBinding* other_binding; + WindowTree* other_tree = CreateNewTree("other_user", &other_binding); + other_binding->client()->tracker()->changes()->clear(); + + // Set event observers on both the wm tree and the other user's tree. + WindowTreeTestApi(wm_tree()).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::POINTER_UP), 111u); + WindowTreeTestApi(other_tree) + .SetEventObserver(CreateEventMatcher(ui::mojom::EventType::POINTER_UP), + 222u); + + // An event is observed by the wm tree, but not by the other user's tree. + DispatchEventAndAckImmediately(CreatePointerUpEvent(5, 5)); + ASSERT_EQ(1u, wm_client()->tracker()->changes()->size()); + EXPECT_EQ("InputEvent window=0,3 event_action=18 event_observer_id=111", + SingleChangeToDescription(*wm_client()->tracker()->changes())); + ASSERT_EQ(0u, other_binding->client()->tracker()->changes()->size()); +} + +// Tests that an event observer cannot observe keystrokes. +TEST_F(WindowTreeTest, SetEventObserverKeyEventsDisallowed) { + WindowTreeTestApi(wm_tree()).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::KEY_PRESSED), 111u); + ui::KeyEvent key_pressed(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE); + DispatchEventAndAckImmediately(key_pressed); + EXPECT_EQ(0u, wm_client()->tracker()->changes()->size()); + + WindowTreeTestApi(wm_tree()).SetEventObserver( + CreateEventMatcher(ui::mojom::EventType::KEY_RELEASED), 222u); + ui::KeyEvent key_released(ui::ET_KEY_RELEASED, ui::VKEY_A, ui::EF_NONE); + DispatchEventAndAckImmediately(key_released); + EXPECT_EQ(0u, wm_client()->tracker()->changes()->size()); +} + +TEST_F(WindowTreeTest, CursorChangesWhenMouseOverWindowAndWindowSetsCursor) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + // Like in BasicInputEventTarget, we send a pointer down event to be + // dispatched. This is only to place the mouse cursor over that window though. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22)); + + window->SetPredefinedCursor(mojom::Cursor::IBEAM); + + // Because the cursor is over the window when SetCursor was called, we should + // have immediately changed the cursor. + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); +} + +TEST_F(WindowTreeTest, CursorChangesWhenEnteringWindowWithDifferentCursor) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + // Let's create a pointer event outside the window and then move the pointer + // inside. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5)); + window->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); + + DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22)); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); +} + +TEST_F(WindowTreeTest, TouchesDontChangeCursor) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + // Let's create a pointer event outside the window and then move the pointer + // inside. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5)); + window->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); + + // With a touch event, we shouldn't update the cursor. + DispatchEventAndAckImmediately(CreatePointerDownEvent(21, 22)); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); +} + +TEST_F(WindowTreeTest, DragOutsideWindow) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + // Start with the cursor outside the window. Setting the cursor shouldn't + // change the cursor. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5)); + window->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); + + // Move the pointer to the inside of the window + DispatchEventAndAckImmediately(CreateMouseMoveEvent(21, 22)); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); + + // Start the drag. + DispatchEventAndAckImmediately(CreateMouseDownEvent(21, 22)); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); + + // Move the cursor (mouse is still down) outside the window. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(5, 5)); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); + + // Release the cursor. We should now adapt the cursor of the window + // underneath the pointer. + DispatchEventAndAckImmediately(CreateMouseUpEvent(5, 5)); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); +} + +TEST_F(WindowTreeTest, ChangingWindowBoundsChangesCursor) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + // Put the cursor just outside the bounds of the window. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(41, 41)); + window->SetPredefinedCursor(mojom::Cursor::IBEAM); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); + + // Expand the bounds of the window so they now include where the cursor now + // is. + window->SetBounds(gfx::Rect(20, 20, 25, 25)); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); + + // Contract the bounds again. + window->SetBounds(gfx::Rect(20, 20, 20, 20)); + EXPECT_EQ(mojom::Cursor::CURSOR_NULL, cursor_id()); +} + +TEST_F(WindowTreeTest, WindowReorderingChangesCursor) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window1)); + + // Create a second window right over the first. + const ClientWindowId embed_window_id(FirstRootId(tree)); + const ClientWindowId child2_id(BuildClientWindowId(tree, 2)); + EXPECT_TRUE(tree->NewWindow(child2_id, ServerWindow::Properties())); + ServerWindow* child2 = tree->GetWindowByClientId(child2_id); + ASSERT_TRUE(child2); + EXPECT_TRUE(tree->AddWindow(embed_window_id, child2_id)); + child2->SetVisible(true); + child2->SetBounds(gfx::Rect(20, 20, 20, 20)); + EnableHitTest(child2); + + // Give each window a different cursor. + window1->SetPredefinedCursor(mojom::Cursor::IBEAM); + child2->SetPredefinedCursor(mojom::Cursor::HAND); + + // We expect window2 to be over window1 now. + DispatchEventAndAckImmediately(CreateMouseMoveEvent(22, 22)); + EXPECT_EQ(mojom::Cursor::HAND, cursor_id()); + + // But when we put window2 at the bottom, we should adapt window1's cursor. + child2->parent()->StackChildAtBottom(child2); + EXPECT_EQ(mojom::Cursor::IBEAM, cursor_id()); +} + +TEST_F(WindowTreeTest, EventAck) { + const ClientWindowId embed_window_id = BuildClientWindowId(wm_tree(), 1); + EXPECT_TRUE( + wm_tree()->NewWindow(embed_window_id, ServerWindow::Properties())); + EXPECT_TRUE(wm_tree()->SetWindowVisibility(embed_window_id, true)); + ASSERT_TRUE(FirstRoot(wm_tree())); + EXPECT_TRUE(wm_tree()->AddWindow(FirstRootId(wm_tree()), embed_window_id)); + display()->root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + + wm_client()->tracker()->changes()->clear(); + DispatchEventWithoutAck(CreateMouseMoveEvent(21, 22)); + ASSERT_EQ(1u, wm_client()->tracker()->changes()->size()); + EXPECT_EQ("InputEvent window=0,3 event_action=17", + ChangesToDescription1(*wm_client()->tracker()->changes())[0]); + wm_client()->tracker()->changes()->clear(); + + // Send another event. This event shouldn't reach the client. + DispatchEventWithoutAck(CreateMouseMoveEvent(21, 22)); + ASSERT_EQ(0u, wm_client()->tracker()->changes()->size()); + + // Ack the first event. That should trigger the dispatch of the second event. + AckPreviousEvent(); + ASSERT_EQ(1u, wm_client()->tracker()->changes()->size()); + EXPECT_EQ("InputEvent window=0,3 event_action=17", + ChangesToDescription1(*wm_client()->tracker()->changes())[0]); +} + +// Establish client, call NewTopLevelWindow(), make sure get id, and make +// sure client paused. +TEST_F(WindowTreeTest, NewTopLevelWindow) { + TestWindowManager wm_internal; + set_window_manager_internal(wm_tree(), &wm_internal); + + TestWindowTreeBinding* child_binding; + WindowTree* child_tree = CreateNewTree(wm_tree()->user_id(), &child_binding); + child_binding->client()->tracker()->changes()->clear(); + child_binding->client()->set_record_on_change_completed(true); + + // Create a new top level window. + mojo::Map<mojo::String, mojo::Array<uint8_t>> properties; + const uint32_t initial_change_id = 17; + // Explicitly use an id that does not contain the client id. + const ClientWindowId embed_window_id2_in_child(45 << 16 | 27); + static_cast<mojom::WindowTree*>(child_tree) + ->NewTopLevelWindow(initial_change_id, embed_window_id2_in_child.id, + std::move(properties)); + + // The binding should be paused until the wm acks the change. + uint32_t wm_change_id = 0u; + ASSERT_TRUE(wm_internal.did_call_create_top_level_window(&wm_change_id)); + EXPECT_TRUE(child_binding->is_paused()); + + // Create the window for |embed_window_id2_in_child|. + const ClientWindowId embed_window_id2 = BuildClientWindowId(wm_tree(), 2); + EXPECT_TRUE( + wm_tree()->NewWindow(embed_window_id2, ServerWindow::Properties())); + EXPECT_TRUE(wm_tree()->SetWindowVisibility(embed_window_id2, true)); + EXPECT_TRUE(wm_tree()->AddWindow(FirstRootId(wm_tree()), embed_window_id2)); + + // Ack the change, which should resume the binding. + child_binding->client()->tracker()->changes()->clear(); + static_cast<mojom::WindowManagerClient*>(wm_tree()) + ->OnWmCreatedTopLevelWindow(wm_change_id, embed_window_id2.id); + EXPECT_FALSE(child_binding->is_paused()); + EXPECT_EQ("TopLevelCreated id=17 window_id=" + + WindowIdToString( + WindowIdFromTransportId(embed_window_id2_in_child.id)) + + " drawn=true", + SingleChangeToDescription( + *child_binding->client()->tracker()->changes())); + child_binding->client()->tracker()->changes()->clear(); + + // Change the visibility of the window from the owner and make sure the + // client sees the right id. + ServerWindow* embed_window = wm_tree()->GetWindowByClientId(embed_window_id2); + ASSERT_TRUE(embed_window); + EXPECT_TRUE(embed_window->visible()); + ASSERT_TRUE(wm_tree()->SetWindowVisibility( + ClientWindowIdForWindow(wm_tree(), embed_window), false)); + EXPECT_FALSE(embed_window->visible()); + EXPECT_EQ("VisibilityChanged window=" + + WindowIdToString( + WindowIdFromTransportId(embed_window_id2_in_child.id)) + + " visible=false", + SingleChangeToDescription( + *child_binding->client()->tracker()->changes())); + + // Set the visibility from the child using the client assigned id. + ASSERT_TRUE(child_tree->SetWindowVisibility(embed_window_id2_in_child, true)); + EXPECT_TRUE(embed_window->visible()); +} + +// Tests that only the capture window can release capture. +TEST_F(WindowTreeTest, ExplicitSetCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + const ServerWindow* root_window = *tree->roots().begin(); + tree->AddWindow(FirstRootId(tree), ClientWindowIdForWindow(tree, window)); + window->SetBounds(gfx::Rect(0, 0, 100, 100)); + ASSERT_TRUE(tree->GetDisplay(window)); + + // Set capture. + mojom::WindowTree* mojom_window_tree = static_cast<mojom::WindowTree*>(tree); + uint32_t change_id = 42; + mojom_window_tree->SetCapture(change_id, WindowIdToTransportId(window->id())); + Display* display = tree->GetDisplay(window); + EXPECT_EQ(window, GetCaptureWindow(display)); + + // Only the capture window should be able to release capture + mojom_window_tree->ReleaseCapture(++change_id, + WindowIdToTransportId(root_window->id())); + EXPECT_EQ(window, GetCaptureWindow(display)); + mojom_window_tree->ReleaseCapture(++change_id, + WindowIdToTransportId(window->id())); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that while a client is interacting with input, that capture is not +// allowed for invisible windows. +TEST_F(WindowTreeTest, CaptureWindowMustBeVisible) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + tree->AddWindow(FirstRootId(tree), ClientWindowIdForWindow(tree, window)); + window->SetBounds(gfx::Rect(0, 0, 100, 100)); + ASSERT_TRUE(tree->GetDisplay(window)); + + DispatchEventWithoutAck(CreatePointerDownEvent(10, 10)); + window->SetVisible(false); + EXPECT_FALSE(tree->SetCapture(ClientWindowIdForWindow(tree, window))); + EXPECT_NE(window, GetCaptureWindow(tree->GetDisplay(window))); +} + +// Tests that showing a modal window releases the capture if the capture is on a +// descendant of the modal parent. +TEST_F(WindowTreeTest, ShowModalWindowWithDescendantCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w11| as a child of |w1| and make it visible. + ClientWindowId w11_id = BuildClientWindowId(tree, 11); + ASSERT_TRUE(tree->NewWindow(w11_id, ServerWindow::Properties())); + ServerWindow* w11 = tree->GetWindowByClientId(w11_id); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(w1_id, w11_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w11_id, true)); + + // Create |w2| as a child of |root_window| and modal to |w1| and leave it + // hidden. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Set capture to |w11|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w11_id)); + EXPECT_EQ(w11, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| visible. This should release capture as capture is set to a + // descendant of the modal parent. + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that setting a visible window as modal releases the capture if the +// capture is on a descendant of the modal parent. +TEST_F(WindowTreeTest, VisibleWindowToModalWithDescendantCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w11| as a child of |w1| and make it visible. + ClientWindowId w11_id = BuildClientWindowId(tree, 11); + ASSERT_TRUE(tree->NewWindow(w11_id, ServerWindow::Properties())); + ServerWindow* w11 = tree->GetWindowByClientId(w11_id); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(w1_id, w11_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w11_id, true)); + + // Create |w2| as a child of |root_window| and make it visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + // Set capture to |w11|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w11_id)); + EXPECT_EQ(w11, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Set |w2| modal to |w1|. This should release the capture as the capture is + // set to a descendant of the modal parent. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that showing a modal window does not change capture if the capture is +// not on a descendant of the modal parent. +TEST_F(WindowTreeTest, ShowModalWindowWithNonDescendantCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| as a child of |root_window| and modal to |w1| and leave it + // hidden.. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Create |w3| as a child of |root_window| and make it visible. + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| visible. This should not change the capture as the capture is not + // set to a descendant of the modal parent. + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + EXPECT_EQ(w3, GetCaptureWindow(display)); +} + +// Tests that setting a visible window as modal does not change the capture if +// the capture is not set to a descendant of the modal parent. +TEST_F(WindowTreeTest, VisibleWindowToModalWithNonDescendantCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| and |w3| as children of |root_window| and make them visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Set |w2| modal to |w1|. This should not release the capture as the capture + // is not set to a descendant of the modal parent. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); +} + +// Tests that showing a system modal window releases the capture. +TEST_F(WindowTreeTest, ShowSystemModalWindowWithCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 10, 10)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create a system modal window |w2| as a child of |root_window| and leave it + // hidden. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(30, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Set capture to |w1|. + DispatchEventWithoutAck(CreatePointerDownEvent(15, 15)); + ASSERT_TRUE(tree->SetCapture(w1_id)); + EXPECT_EQ(w1, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| visible. This should release capture as it is system modal + // window. + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that setting a visible window as modal to system releases the capture. +TEST_F(WindowTreeTest, VisibleWindowToSystemModalWithCapture) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 10, 10)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| as a child of |root_window| and make it visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(30, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + // Set capture to |w1|. + DispatchEventWithoutAck(CreatePointerDownEvent(15, 15)); + ASSERT_TRUE(tree->SetCapture(w1_id)); + EXPECT_EQ(w1, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| modal to system. This should release capture. + ASSERT_TRUE(tree->SetModal(w2_id)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that moving the capture window to a modal parent releases the capture +// as capture cannot be blocked by a modal window. +TEST_F(WindowTreeTest, MoveCaptureWindowToModalParent) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| and |w3| as children of |root_window| and make them visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set |w2| modal to |w1|. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w3| child of |w1|. This should release capture as |w3| is now blocked + // by a modal window. + ASSERT_TRUE(tree->AddWindow(w1_id, w3_id)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that opacity can be set on a known window. +TEST_F(WindowTreeTest, SetOpacity) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + const float new_opacity = 0.5f; + EXPECT_NE(new_opacity, window->opacity()); + ASSERT_TRUE(tree->SetWindowOpacity(ClientWindowIdForWindow(tree, window), + new_opacity)); + EXPECT_EQ(new_opacity, window->opacity()); + + // Re-applying the same opacity will succeed. + EXPECT_TRUE(tree->SetWindowOpacity(ClientWindowIdForWindow(tree, window), + new_opacity)); +} + +// Tests that opacity requests for unknown windows are rejected. +TEST_F(WindowTreeTest, SetOpacityFailsOnUnknownWindow) { + TestWindowTreeClient* embed_client = nullptr; + WindowTree* tree = nullptr; + ServerWindow* window = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_client, &tree, &window)); + + TestServerWindowDelegate delegate; + WindowId window_id(42, 1337); + ServerWindow unknown_window(&delegate, window_id); + const float new_opacity = 0.5f; + ASSERT_NE(new_opacity, unknown_window.opacity()); + + EXPECT_FALSE(tree->SetWindowOpacity( + ClientWindowId(WindowIdToTransportId(window_id)), new_opacity)); + EXPECT_NE(new_opacity, unknown_window.opacity()); +} + +TEST_F(WindowTreeTest, SetCaptureTargetsRightConnection) { + ServerWindow* window = window_event_targeting_helper_.CreatePrimaryTree( + gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 50, 50)); + WindowTree* owning_tree = + window_server()->GetTreeWithId(window->id().client_id); + WindowTree* embed_tree = window_server()->GetTreeWithRoot(window); + ASSERT_NE(owning_tree, embed_tree); + ASSERT_TRUE( + owning_tree->SetCapture(ClientWindowIdForWindow(owning_tree, window))); + DispatchEventWithoutAck(CreateMouseMoveEvent(21, 22)); + WindowManagerStateTestApi wm_state_test_api( + display()->GetActiveWindowManagerDisplayRoot()->window_manager_state()); + EXPECT_EQ(owning_tree, wm_state_test_api.tree_awaiting_input_ack()); + AckPreviousEvent(); + + // Set capture from the embedded client and make sure it gets the event. + ASSERT_TRUE( + embed_tree->SetCapture(ClientWindowIdForWindow(embed_tree, window))); + DispatchEventWithoutAck(CreateMouseMoveEvent(22, 23)); + EXPECT_EQ(embed_tree, wm_state_test_api.tree_awaiting_input_ack()); +} + +} // namespace test +} // namespace ws +} // namespace mus |