diff options
Diffstat (limited to 'mojo/services/window_manager')
37 files changed, 4667 insertions, 0 deletions
diff --git a/mojo/services/window_manager/BUILD.gn b/mojo/services/window_manager/BUILD.gn new file mode 100644 index 0000000..c34ebe1 --- /dev/null +++ b/mojo/services/window_manager/BUILD.gn @@ -0,0 +1,129 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//testing/test.gni") + +mojo_native_application("window_manager") { + sources = [ + "main.cc", + ] + + public_deps = [ + ":lib", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/common:tracing_impl", + "//third_party/mojo_services/src/view_manager/public/cpp", + ] +} + +source_set("lib") { + sources = [ + "basic_focus_rules.cc", + "basic_focus_rules.h", + "capture_controller.cc", + "capture_controller.h", + "capture_controller_observer.h", + "focus_controller.cc", + "focus_controller.h", + "focus_controller_observer.h", + "focus_rules.h", + "native_viewport_event_dispatcher_impl.cc", + "native_viewport_event_dispatcher_impl.h", + "view_event_dispatcher.cc", + "view_event_dispatcher.h", + "view_target.cc", + "view_target.h", + "view_targeter.cc", + "view_targeter.h", + "window_manager_app.cc", + "window_manager_app.h", + "window_manager_app_android.cc", + "window_manager_app_linux.cc", + "window_manager_app_win.cc", + "window_manager_delegate.h", + "window_manager_impl.cc", + "window_manager_impl.h", + ] + + deps = [ + "//base", + "//ui/base", + "//ui/events", + "//ui/gfx", + "//ui/gfx/geometry", + "//mojo/application", + "//mojo/common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} + +test("window_manager_unittests") { + sources = [ + "focus_controller_unittest.cc", + "run_all_unittests.cc", + "view_target_unittest.cc", + "view_targeter_unittest.cc", + "window_manager_api_unittest.cc", + "window_manager_test_util.cc", + "window_manager_test_util.h", + ] + + public_deps = [ + ":lib", + ] + + deps = [ + "//base/test:test_support", + "//mojo/converters/geometry", + "//third_party/mojo/src/mojo/edk/system", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/application", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//mojo/shell/application_manager", + "//mojo/shell:test_support", + "//testing/gtest", + "//ui/events:test_support", + "//ui/gfx", + "//ui/gfx:test_support", + "//ui/gl", + ] + + if (use_x11) { + deps += [ "//ui/gfx/x" ] + } +} + +mojo_native_application("window_manager_apptests") { + testonly = true + + sources = [ + "window_manager_apptest.cc", + ] + + deps = [ + "//base", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/system:system", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] + + data_deps = [ ":window_manager($default_toolchain)" ] +} diff --git a/mojo/services/window_manager/DEPS b/mojo/services/window_manager/DEPS new file mode 100644 index 0000000..4a637c6 --- /dev/null +++ b/mojo/services/window_manager/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+mojo/application", + "+mojo/converters", + "+third_party/mojo_services", + "+ui", +] diff --git a/mojo/services/window_manager/basic_focus_rules.cc b/mojo/services/window_manager/basic_focus_rules.cc new file mode 100644 index 0000000..5ba49af --- /dev/null +++ b/mojo/services/window_manager/basic_focus_rules.cc @@ -0,0 +1,171 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/basic_focus_rules.h" + +#include "base/macros.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +using mojo::View; + +namespace window_manager { + +BasicFocusRules::BasicFocusRules(View* window_container) + : window_container_(window_container) { +} + +BasicFocusRules::~BasicFocusRules() {} + +bool BasicFocusRules::SupportsChildActivation(View* view) const { + return true; +} + +bool BasicFocusRules::IsToplevelView(View* view) const { + if (!IsViewParentedToWindowContainer(view)) + return false; + + // The window must exist within a container that supports activation. + // The window cannot be blocked by a modal transient. + return SupportsChildActivation(view->parent()); +} + +bool BasicFocusRules::CanActivateView(View* view) const { + if (!view) + return true; + + // Only toplevel windows can be activated + if (!IsToplevelView(view)) + return false; + + // The view must be visible. + if (!view->visible()) + return false; + + // TODO(erg): The aura version of this class asks the aura::Window's + // ActivationDelegate whether the window is activatable. + + // A window must be focusable to be activatable. We don't call + // CanFocusWindow() from here because it will call back to us via + // GetActivatableWindow(). + if (!CanFocusViewImpl(view)) + return false; + + // TODO(erg): In the aura version, we also check whether the window is + // blocked by a modal transient window. + + return true; +} + +bool BasicFocusRules::CanFocusView(View* view) const { + // It is possible to focus a NULL window, it is equivalent to clearing focus. + if (!view) + return true; + + // The focused view is always inside the active view, so views that aren't + // activatable can't contain the focused view. + View* activatable = GetActivatableView(view); + if (!activatable || !activatable->Contains(view)) + return false; + return CanFocusViewImpl(view); +} + +View* BasicFocusRules::GetToplevelView(View* view) const { + View* parent = view->parent(); + View* child = view; + while (parent) { + if (IsToplevelView(child)) + return child; + + parent = parent->parent(); + child = child->parent(); + } + + return nullptr; +} + +View* BasicFocusRules::GetActivatableView(View* view) const { + View* parent = view->parent(); + View* child = view; + while (parent) { + if (CanActivateView(child)) + return child; + + // TODO(erg): In the aura version of this class, we have a whole bunch of + // checks to support modal transient windows, and transient parents. + + parent = parent->parent(); + child = child->parent(); + } + + return nullptr; +} + +View* BasicFocusRules::GetFocusableView(View* view) const { + if (CanFocusView(view)) + return view; + + // |view| may be in a hierarchy that is non-activatable, in which case we + // need to cut over to the activatable hierarchy. + View* activatable = GetActivatableView(view); + if (!activatable) { + // There may not be a related activatable hierarchy to cut over to, in which + // case we try an unrelated one. + View* toplevel = GetToplevelView(view); + if (toplevel) + activatable = GetNextActivatableView(toplevel); + if (!activatable) + return nullptr; + } + + if (!activatable->Contains(view)) { + // If there's already a child window focused in the activatable hierarchy, + // just use that (i.e. don't shift focus), otherwise we need to at least cut + // over to the activatable hierarchy. + View* focused = GetFocusableView(activatable); + return activatable->Contains(focused) ? focused : activatable; + } + + while (view && !CanFocusView(view)) + view = view->parent(); + return view; +} + +View* BasicFocusRules::GetNextActivatableView(View* activatable) const { + DCHECK(activatable); + + // In the basic scenarios handled by BasicFocusRules, the pool of activatable + // windows is limited to the |ignore|'s siblings. + const View::Children& siblings = activatable->parent()->children(); + DCHECK(!siblings.empty()); + + for (auto rit = siblings.rbegin(); rit != siblings.rend(); ++rit) { + View* cur = *rit; + if (cur == activatable) + continue; + if (CanActivateView(cur)) + return cur; + } + return nullptr; +} + +// TODO(erg): aura::Window::CanFocus() exists. View::CanFocus() does +// not. This is a hack that does everything that Window::CanFocus() currently +// does that doesn't require a delegate or an EventClient. +bool BasicFocusRules::CanFocusViewImpl(View* view) const { + // TODO(erg): In unit tests, views will never be drawn, so we can't rely on + // IsDrawn() here. + if (IsViewParentedToWindowContainer(view)) + return view->visible(); + + // TODO(erg): Add the intermediary delegate and event client checks once we + // have those. + + return CanFocusViewImpl(view->parent()); +} + +bool BasicFocusRules::IsViewParentedToWindowContainer(View* view) const { + return view->parent() == window_container_; +} + +} // namespace mojo diff --git a/mojo/services/window_manager/basic_focus_rules.h b/mojo/services/window_manager/basic_focus_rules.h new file mode 100644 index 0000000..d14a9b0d --- /dev/null +++ b/mojo/services/window_manager/basic_focus_rules.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ +#define SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ + +#include "mojo/services/window_manager/focus_rules.h" + +namespace mojo { +class View; +} + +namespace window_manager { + +// The focusing rules used inside a window manager. +// +// This is intended to be a user supplyable, subclassable component passed to +// WindowManagerApp, allowing for the creation of other window managers. +class BasicFocusRules : public FocusRules { + public: + BasicFocusRules(mojo::View* window_container); + ~BasicFocusRules() override; + + protected: + // Overridden from mojo::FocusRules: + bool SupportsChildActivation(mojo::View* view) const override; + bool IsToplevelView(mojo::View* view) const override; + bool CanActivateView(mojo::View* view) const override; + bool CanFocusView(mojo::View* view) const override; + mojo::View* GetToplevelView(mojo::View* view) const override; + mojo::View* GetActivatableView(mojo::View* view) const override; + mojo::View* GetFocusableView(mojo::View* view) const override; + mojo::View* GetNextActivatableView(mojo::View* activatable) const override; + + private: + bool CanFocusViewImpl(mojo::View* view) const; + + // Tests to see if |view| is in |window_container_|. + bool IsViewParentedToWindowContainer(mojo::View* view) const; + + mojo::View* window_container_; + + DISALLOW_COPY_AND_ASSIGN(BasicFocusRules); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_BASIC_FOCUS_RULES_H_ diff --git a/mojo/services/window_manager/capture_controller.cc b/mojo/services/window_manager/capture_controller.cc new file mode 100644 index 0000000..ad6891c --- /dev/null +++ b/mojo/services/window_manager/capture_controller.cc @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/capture_controller.h" + +#include "mojo/services/window_manager/capture_controller_observer.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h" + +DECLARE_VIEW_PROPERTY_TYPE(window_manager::CaptureController*); + +namespace window_manager { + +namespace { +DEFINE_VIEW_PROPERTY_KEY(CaptureController*, + kRootViewCaptureController, + nullptr); +} // namespace + +CaptureController::CaptureController() + : capture_view_(nullptr) {} + +CaptureController::~CaptureController() {} + +void CaptureController::AddObserver(CaptureControllerObserver* observer) { + capture_controller_observers_.AddObserver(observer); +} + +void CaptureController::RemoveObserver(CaptureControllerObserver* observer) { + capture_controller_observers_.RemoveObserver(observer); +} + +void CaptureController::SetCapture(mojo::View* view) { + if (capture_view_ == view) + return; + + if (capture_view_) + capture_view_->RemoveObserver(this); + + mojo::View* old_capture_view = capture_view_; + capture_view_ = view; + + if (capture_view_) + capture_view_->AddObserver(this); + + NotifyCaptureChange(capture_view_, old_capture_view); +} + +void CaptureController::ReleaseCapture(mojo::View* view) { + if (capture_view_ != view) + return; + SetCapture(nullptr); +} + +mojo::View* CaptureController::GetCapture() { + return capture_view_; +} + +void CaptureController::NotifyCaptureChange(mojo::View* new_capture, + mojo::View* old_capture) { + mojo::ViewTracker view_tracker; + if (new_capture) + view_tracker.Add(new_capture); + if (old_capture) + view_tracker.Add(old_capture); + + FOR_EACH_OBSERVER( + CaptureControllerObserver, capture_controller_observers_, + OnCaptureChanged(view_tracker.Contains(new_capture) ? new_capture + : nullptr)); +} + +void CaptureController::OnViewDestroying(mojo::View* view) { + if (view == capture_view_) { + view->RemoveObserver(this); + NotifyCaptureChange(nullptr, view); + capture_view_ = nullptr; + } +} + +void SetCaptureController(mojo::View* root_view, + CaptureController* capture_controller) { + DCHECK_EQ(root_view->GetRoot(), root_view); + root_view->SetLocalProperty(kRootViewCaptureController, capture_controller); +} + +CaptureController* GetCaptureController(mojo::View* root_view) { + if (root_view) + DCHECK_EQ(root_view->GetRoot(), root_view); + return root_view ? + root_view->GetLocalProperty(kRootViewCaptureController) : nullptr; +} + +mojo::View* GetCaptureView(mojo::View* view) { + mojo::View* root = view->GetRoot(); + if (!root) + return nullptr; + CaptureController* controller = GetCaptureController(root); + return controller ? controller->GetCapture() : nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/capture_controller.h b/mojo/services/window_manager/capture_controller.h new file mode 100644 index 0000000..64a3339 --- /dev/null +++ b/mojo/services/window_manager/capture_controller.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ +#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ + +#include "base/observer_list.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" + +namespace window_manager { + +class CaptureControllerObserver; + +// Manages input capture. A view which has capture will take all input events. +class CaptureController : public mojo::ViewObserver { + public: + CaptureController(); + ~CaptureController() override; + + void AddObserver(CaptureControllerObserver* observer); + void RemoveObserver(CaptureControllerObserver* observer); + + void SetCapture(mojo::View* view); + void ReleaseCapture(mojo::View* view); + mojo::View* GetCapture(); + + private: + void NotifyCaptureChange(mojo::View* new_capture, mojo::View* old_capture); + + // Overridden from ViewObserver: + void OnViewDestroying(mojo::View* view) override; + + // The current capture view. Null if there is no capture view. + mojo::View* capture_view_; + + ObserverList<CaptureControllerObserver> capture_controller_observers_; + + DISALLOW_COPY_AND_ASSIGN(CaptureController); +}; + +void SetCaptureController(mojo::View* view, + CaptureController* capture_controller); +CaptureController* GetCaptureController(mojo::View* view); +mojo::View* GetCaptureView(mojo::View* view); + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_H_ diff --git a/mojo/services/window_manager/capture_controller_observer.h b/mojo/services/window_manager/capture_controller_observer.h new file mode 100644 index 0000000..fc62d60 --- /dev/null +++ b/mojo/services/window_manager/capture_controller_observer.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ +#define SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ + +namespace window_manager { + +class CaptureControllerObserver { + public: + virtual void OnCaptureChanged(mojo::View* gained_capture) = 0; + + protected: + virtual ~CaptureControllerObserver() {} +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_CAPTURE_CONTROLLER_OBSERVER_H_ diff --git a/mojo/services/window_manager/focus_controller.cc b/mojo/services/window_manager/focus_controller.cc new file mode 100644 index 0000000..25d60d6 --- /dev/null +++ b/mojo/services/window_manager/focus_controller.cc @@ -0,0 +1,317 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/focus_controller.h" + +#include "base/auto_reset.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/focus_rules.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_tracker.h" +#include "ui/events/event.h" + +DECLARE_VIEW_PROPERTY_TYPE(window_manager::FocusController*); + +using mojo::View; + +namespace window_manager { + +namespace { +DEFINE_VIEW_PROPERTY_KEY(FocusController*, kRootViewFocusController, nullptr); +} // namespace + +FocusController::FocusController(scoped_ptr<FocusRules> rules) + : active_view_(nullptr), + focused_view_(nullptr), + updating_focus_(false), + updating_activation_(false), + rules_(rules.Pass()), + observer_manager_(this) { + DCHECK(rules_); +} + +FocusController::~FocusController() {} + +void FocusController::AddObserver(FocusControllerObserver* observer) { + focus_controller_observers_.AddObserver(observer); +} + +void FocusController::RemoveObserver(FocusControllerObserver* observer) { + focus_controller_observers_.RemoveObserver(observer); +} + +void FocusController::ActivateView(View* view) { + FocusView(view); +} + +void FocusController::DeactivateView(View* view) { + if (view) + FocusView(rules_->GetNextActivatableView(view)); +} + +View* FocusController::GetActiveView() { + return active_view_; +} + +View* FocusController::GetActivatableView(View* view) { + return rules_->GetActivatableView(view); +} + +View* FocusController::GetToplevelView(View* view) { + return rules_->GetToplevelView(view); +} + +bool FocusController::CanActivateView(View* view) const { + return rules_->CanActivateView(view); +} + +void FocusController::FocusView(View* view) { + if (view && + (view->Contains(focused_view_) || view->Contains(active_view_))) { + return; + } + + // Focusing a window also activates its containing activatable window. Note + // that the rules could redirect activation activation and/or focus. + View* focusable = rules_->GetFocusableView(view); + View* activatable = + focusable ? rules_->GetActivatableView(focusable) : nullptr; + + // We need valid focusable/activatable windows in the event we're not clearing + // focus. "Clearing focus" is inferred by whether or not |window| passed to + // this function is non-null. + if (view && (!focusable || !activatable)) + return; + DCHECK((focusable && activatable) || !view); + + // Activation change observers may change the focused window. If this happens + // we must not adjust the focus below since this will clobber that change. + View* last_focused_view = focused_view_; + if (!updating_activation_) + SetActiveView(view, activatable); + + // If the window's ActivationChangeObserver shifted focus to a valid window, + // we don't want to focus the window we thought would be focused by default. + bool activation_changed_focus = last_focused_view != focused_view_; + if (!updating_focus_ && (!activation_changed_focus || !focused_view_)) { + if (active_view_ && focusable) + DCHECK(active_view_->Contains(focusable)); + SetFocusedView(focusable); + } +} + +void FocusController::ResetFocusWithinActiveView(View* view) { + DCHECK(view); + if (!active_view_) + return; + if (!active_view_->Contains(view)) + return; + SetFocusedView(view); +} + +View* FocusController::GetFocusedView() { + return focused_view_; +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, ui::EventHandler implementation: + +void FocusController::OnKeyEvent(ui::KeyEvent* event) { +} + +void FocusController::OnMouseEvent(ui::MouseEvent* event) { + if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +void FocusController::OnScrollEvent(ui::ScrollEvent* event) { +} + +void FocusController::OnTouchEvent(ui::TouchEvent* event) { + if (event->type() == ui::ET_TOUCH_PRESSED && !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +void FocusController::OnGestureEvent(ui::GestureEvent* event) { + if (event->type() == ui::ET_GESTURE_BEGIN && + event->details().touch_points() == 1 && + !event->handled()) { + View* view = static_cast<ViewTarget*>(event->target())->view(); + ViewFocusedFromInputEvent(view); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, mojo::ViewObserver implementation: + +void FocusController::OnViewVisibilityChanged(View* view) { + bool visible = view->visible(); + if (!visible) + ViewLostFocusFromDispositionChange(view, view->parent()); +} + +void FocusController::OnViewDestroying(View* view) { + ViewLostFocusFromDispositionChange(view, view->parent()); +} + +void FocusController::OnTreeChanging(const TreeChangeParams& params) { + // TODO(erg): In the aura version, you could get into a situation where you + // have different focus clients, so you had to check for that. Does that + // happen here? Could we get away with not checking if it does? + if (params.receiver == active_view_ && + params.target->Contains(params.receiver) && + (!params.new_parent || + /* different_focus_clients */ false)) { + ViewLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +void FocusController::OnTreeChanged(const TreeChangeParams& params) { + // TODO(erg): Same as Changing version. + if (params.receiver == focused_view_ && + params.target->Contains(params.receiver) && + (!params.new_parent || + /* different_focus_clients */ false)) { + ViewLostFocusFromDispositionChange(params.receiver, params.old_parent); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// FocusController, private: + +void FocusController::SetFocusedView(View* view) { + if (updating_focus_ || view == focused_view_) + return; + DCHECK(rules_->CanFocusView(view)); + if (view) + DCHECK_EQ(view, rules_->GetFocusableView(view)); + + base::AutoReset<bool> updating_focus(&updating_focus_, true); + View* lost_focus = focused_view_; + + // TODO(erg): In the aura version, we reset the text input client here. Do + // that if we bring in something like the TextInputClient. + + // Allow for the window losing focus to be deleted during dispatch. If it is + // deleted pass null to observers instead of a deleted window. + mojo::ViewTracker view_tracker; + if (lost_focus) + view_tracker.Add(lost_focus); + if (focused_view_ && observer_manager_.IsObserving(focused_view_) && + focused_view_ != active_view_) { + observer_manager_.Remove(focused_view_); + } + focused_view_ = view; + if (focused_view_ && !observer_manager_.IsObserving(focused_view_)) + observer_manager_.Add(focused_view_); + + FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_, + OnFocused(focused_view_)); + + // TODO(erg): In aura, there's a concept of a single FocusChangeObserver that + // is attached to an aura::Window. We don't currently have this in + // mojo::View, but if we add it later, we should make something analogous + // here. + + // TODO(erg): In the aura version, we reset the TextInputClient here, too. +} + +void FocusController::SetActiveView(View* requested_view, View* view) { + if (updating_activation_) + return; + + if (view == active_view_) { + if (requested_view) { + FOR_EACH_OBSERVER(FocusControllerObserver, + focus_controller_observers_, + OnAttemptToReactivateView(requested_view, + active_view_)); + } + return; + } + + DCHECK(rules_->CanActivateView(view)); + if (view) + DCHECK_EQ(view, rules_->GetActivatableView(view)); + + base::AutoReset<bool> updating_activation(&updating_activation_, true); + View* lost_activation = active_view_; + // Allow for the window losing activation to be deleted during dispatch. If + // it is deleted pass null to observers instead of a deleted window. + mojo::ViewTracker view_tracker; + if (lost_activation) + view_tracker.Add(lost_activation); + if (active_view_ && observer_manager_.IsObserving(active_view_) && + focused_view_ != active_view_) { + observer_manager_.Remove(active_view_); + } + active_view_ = view; + if (active_view_ && !observer_manager_.IsObserving(active_view_)) + observer_manager_.Add(active_view_); + + if (active_view_) { + // TODO(erg): Reenable this when we have modal windows. + // StackTransientParentsBelowModalWindow(active_view_); + + active_view_->MoveToFront(); + } + + // TODO(erg): Individual windows can have a single ActivationChangeObserver + // set on them. In the aura version of this code, it sends an + // OnWindowActivated message to both the window that lost activation, and the + // window that gained it. + + FOR_EACH_OBSERVER(FocusControllerObserver, focus_controller_observers_, + OnActivated(active_view_)); +} + +void FocusController::ViewLostFocusFromDispositionChange( + View* view, + View* next) { + // TODO(erg): We clear the modality state here in the aura::Window version of + // this class, and should probably do the same once we have modal windows. + + // TODO(beng): See if this function can be replaced by a call to + // FocusWindow(). + // Activation adjustments are handled first in the event of a disposition + // changed. If an activation change is necessary, focus is reset as part of + // that process so there's no point in updating focus independently. + if (view == active_view_) { + View* next_activatable = rules_->GetNextActivatableView(view); + SetActiveView(nullptr, next_activatable); + if (!(active_view_ && active_view_->Contains(focused_view_))) + SetFocusedView(next_activatable); + } else if (view->Contains(focused_view_)) { + // Active window isn't changing, but focused window might be. + SetFocusedView(rules_->GetFocusableView(next)); + } +} + +void FocusController::ViewFocusedFromInputEvent(View* view) { + // Only focus |window| if it or any of its parents can be focused. Otherwise + // FocusWindow() will focus the topmost window, which may not be the + // currently focused one. + if (rules_->CanFocusView(GetToplevelView(view))) + FocusView(view); +} + +void SetFocusController(View* root_view, FocusController* focus_controller) { + DCHECK_EQ(root_view->GetRoot(), root_view); + root_view->SetLocalProperty(kRootViewFocusController, focus_controller); +} + +FocusController* GetFocusController(View* root_view) { + if (root_view) + DCHECK_EQ(root_view->GetRoot(), root_view); + return root_view ? + root_view->GetLocalProperty(kRootViewFocusController) : nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/focus_controller.h b/mojo/services/window_manager/focus_controller.h new file mode 100644 index 0000000..8a9e8ef --- /dev/null +++ b/mojo/services/window_manager/focus_controller.h @@ -0,0 +1,102 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/scoped_observer.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" +#include "ui/events/event_handler.h" + +namespace window_manager { + +class FocusControllerObserver; +class FocusRules; + +// FocusController handles focus and activation changes in a mojo window +// manager. Within the window manager, there can only be one focused and one +// active window at a time. When focus or activation changes, notifications are +// sent using the FocusControllerObserver interface. +class FocusController : public ui::EventHandler, public mojo::ViewObserver { + public: + // |rules| cannot be null. + explicit FocusController(scoped_ptr<FocusRules> rules); + ~FocusController() override; + + void AddObserver(FocusControllerObserver* observer); + void RemoveObserver(FocusControllerObserver* observer); + + void ActivateView(mojo::View* view); + void DeactivateView(mojo::View* view); + mojo::View* GetActiveView(); + mojo::View* GetActivatableView(mojo::View* view); + mojo::View* GetToplevelView(mojo::View* view); + bool CanActivateView(mojo::View* view) const; + + void FocusView(mojo::View* view); + + void ResetFocusWithinActiveView(mojo::View* view); + mojo::View* GetFocusedView(); + + // Overridden from ui::EventHandler: + void OnKeyEvent(ui::KeyEvent* event) override; + void OnMouseEvent(ui::MouseEvent* event) override; + void OnScrollEvent(ui::ScrollEvent* event) override; + void OnTouchEvent(ui::TouchEvent* event) override; + void OnGestureEvent(ui::GestureEvent* event) override; + + // Overridden from ViewObserver: + void OnTreeChanging(const TreeChangeParams& params) override; + void OnTreeChanged(const TreeChangeParams& params) override; + void OnViewVisibilityChanged(mojo::View* view) override; + void OnViewDestroying(mojo::View* view) override; + + private: + // Internal implementation that sets the focused view, fires events etc. + // This function must be called with a valid focusable view. + void SetFocusedView(mojo::View* view); + + // Internal implementation that sets the active window, fires events etc. + // This function must be called with a valid |activatable_window|. + // |requested window| refers to the window that was passed in to an external + // request (e.g. FocusWindow or ActivateWindow). It may be null, e.g. if + // SetActiveWindow was not called by an external request. |activatable_window| + // refers to the actual window to be activated, which may be different. + void SetActiveView(mojo::View* requested_view, mojo::View* activatable_view); + + // Called when a window's disposition changed such that it and its hierarchy + // are no longer focusable/activatable. |next| is a valid window that is used + // as a starting point for finding a window to focus next based on rules. + void ViewLostFocusFromDispositionChange(mojo::View* view, mojo::View* next); + + // Called when an attempt is made to focus or activate a window via an input + // event targeted at that window. Rules determine the best focusable window + // for the input window. + void ViewFocusedFromInputEvent(mojo::View* view); + + mojo::View* active_view_; + mojo::View* focused_view_; + + bool updating_focus_; + bool updating_activation_; + + scoped_ptr<FocusRules> rules_; + + ObserverList<FocusControllerObserver> focus_controller_observers_; + + ScopedObserver<mojo::View, ViewObserver> observer_manager_; + + DISALLOW_COPY_AND_ASSIGN(FocusController); +}; + +// Sets/Gets the focus controller for a view. +void SetFocusController(mojo::View* view, FocusController* focus_controller); +FocusController* GetFocusController(mojo::View* view); + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_H_ diff --git a/mojo/services/window_manager/focus_controller_observer.h b/mojo/services/window_manager/focus_controller_observer.h new file mode 100644 index 0000000..c5960b0 --- /dev/null +++ b/mojo/services/window_manager/focus_controller_observer.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ + +namespace mojo { +class View; +} + +namespace window_manager { + +class FocusControllerObserver { + public: + // Called when |active| gains focus, or there is no active view + // (|active| is null in this case.). + virtual void OnActivated(mojo::View* gained_active) = 0; + + // Called when focus moves to |gained_focus|. + virtual void OnFocused(mojo::View* gained_focus) = 0; + + // Called when during view activation the currently active view is + // selected for activation. This can happen when a view requested for + // activation cannot be activated because a system modal view is active. + virtual void OnAttemptToReactivateView(mojo::View* request_active, + mojo::View* actual_active) {} + + protected: + virtual ~FocusControllerObserver() {} +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_CONTROLLER_OBSERVER_H_ diff --git a/mojo/services/window_manager/focus_controller_unittest.cc b/mojo/services/window_manager/focus_controller_unittest.cc new file mode 100644 index 0000000..ea60ac7 --- /dev/null +++ b/mojo/services/window_manager/focus_controller_unittest.cc @@ -0,0 +1,1197 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/focus_controller.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/geometry/rect.h" + +using mojo::View; + +namespace window_manager { + +// Counts the number of events that occur. +class FocusNotificationObserver : public FocusControllerObserver { + public: + FocusNotificationObserver() + : activation_changed_count_(0), + focus_changed_count_(0), + reactivation_count_(0), + reactivation_requested_view_(NULL), + reactivation_actual_view_(NULL) {} + ~FocusNotificationObserver() override {} + + void ExpectCounts(int activation_changed_count, int focus_changed_count) { + EXPECT_EQ(activation_changed_count, activation_changed_count_); + EXPECT_EQ(focus_changed_count, focus_changed_count_); + } + int reactivation_count() const { return reactivation_count_; } + View* reactivation_requested_view() const { + return reactivation_requested_view_; + } + View* reactivation_actual_view() const { + return reactivation_actual_view_; + } + + protected: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + ++activation_changed_count_; + } + + void OnFocused(View* gained_focus) override { ++focus_changed_count_; } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + ++reactivation_count_; + reactivation_requested_view_ = request_active; + reactivation_actual_view_ = actual_active; + } + + private: + int activation_changed_count_; + int focus_changed_count_; + int reactivation_count_; + View* reactivation_requested_view_; + View* reactivation_actual_view_; + + DISALLOW_COPY_AND_ASSIGN(FocusNotificationObserver); +}; + +class ViewDestroyer { + public: + virtual View* GetDestroyedView() = 0; + + protected: + virtual ~ViewDestroyer() {} +}; + +// FocusNotificationObserver that keeps track of whether it was notified about +// activation changes or focus changes with a destroyed view. +class RecordingFocusNotificationObserver : public FocusNotificationObserver { + public: + RecordingFocusNotificationObserver(FocusController* focus_controller, + ViewDestroyer* destroyer) + : focus_controller_(focus_controller), + destroyer_(destroyer), + active_(nullptr), + focus_(nullptr), + was_notified_with_destroyed_view_(false) { + focus_controller_->AddObserver(this); + } + ~RecordingFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + bool was_notified_with_destroyed_view() const { + return was_notified_with_destroyed_view_; + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (active_ && active_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (focus_ && focus_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + focus_ = gained_focus; + } + + private: + FocusController* focus_controller_; + + // Not owned. + ViewDestroyer* destroyer_; + View* active_; + View* focus_; + + // Whether the observer was notified about the loss of activation or the + // loss of focus with a view already destroyed by |destroyer_| as the + // |lost_active| or |lost_focus| parameter. + bool was_notified_with_destroyed_view_; + + DISALLOW_COPY_AND_ASSIGN(RecordingFocusNotificationObserver); +}; + +class DestroyOnLoseActivationFocusNotificationObserver + : public FocusNotificationObserver, + public ViewDestroyer { + public: + DestroyOnLoseActivationFocusNotificationObserver( + FocusController* focus_controller, + View* view_to_destroy, + View* initial_active) + : focus_controller_(focus_controller), + view_to_destroy_(view_to_destroy), + active_(initial_active), + did_destroy_(false) { + focus_controller_->AddObserver(this); + } + ~DestroyOnLoseActivationFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (view_to_destroy_ && active_ == view_to_destroy_) { + view_to_destroy_->Destroy(); + did_destroy_ = true; + } + active_ = gained_active; + } + + // Overridden from ViewDestroyer: + View* GetDestroyedView() override { + return did_destroy_ ? view_to_destroy_ : nullptr; + } + + private: + FocusController* focus_controller_; + View* view_to_destroy_; + View* active_; + bool did_destroy_; + + DISALLOW_COPY_AND_ASSIGN(DestroyOnLoseActivationFocusNotificationObserver); +}; + +class ScopedFocusNotificationObserver : public FocusNotificationObserver { + public: + ScopedFocusNotificationObserver(FocusController* focus_controller) + : focus_controller_(focus_controller) { + focus_controller_->AddObserver(this); + } + ~ScopedFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + FocusController* focus_controller_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFocusNotificationObserver); +}; + +// Only responds to events if a message contains |target| as a parameter. +class ScopedFilteringFocusNotificationObserver + : public FocusNotificationObserver { + public: + ScopedFilteringFocusNotificationObserver(FocusController* focus_controller, + View* target, + View* initial_active, + View* initial_focus) + : focus_controller_(focus_controller), + target_(target), + active_(initial_active), + focus_(initial_focus) { + focus_controller_->AddObserver(this); + } + ~ScopedFilteringFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + if (gained_active == target_ || active_ == target_) + FocusNotificationObserver::OnActivated(gained_active); + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (gained_focus == target_ || focus_ == target_) + FocusNotificationObserver::OnFocused(gained_focus); + focus_ = gained_focus; + } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + if (request_active == target_ || actual_active == target_) { + FocusNotificationObserver::OnAttemptToReactivateView(request_active, + actual_active); + } + } + + FocusController* focus_controller_; + View* target_; + View* active_; + View* focus_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFilteringFocusNotificationObserver); +}; + +// Used to fake the handling of events in the pre-target phase. +class SimpleEventHandler : public ui::EventHandler { + public: + SimpleEventHandler() {} + ~SimpleEventHandler() override {} + + // Overridden from ui::EventHandler: + void OnMouseEvent(ui::MouseEvent* event) override { + event->SetHandled(); + } + void OnGestureEvent(ui::GestureEvent* event) override { + event->SetHandled(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleEventHandler); +}; + +class FocusShiftingActivationObserver + : public FocusControllerObserver { + public: + explicit FocusShiftingActivationObserver(FocusController* focus_controller, + View* activated_view) + : focus_controller_(focus_controller), + activated_view_(activated_view), + shift_focus_to_(NULL) {} + ~FocusShiftingActivationObserver() override {} + + void set_shift_focus_to(View* shift_focus_to) { + shift_focus_to_ = shift_focus_to; + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + // Shift focus to a child. This should prevent the default focusing from + // occurring in FocusController::FocusView(). + if (gained_active == activated_view_) + focus_controller_->FocusView(shift_focus_to_); + } + + void OnFocused(View* gained_focus) override {} + + FocusController* focus_controller_; + View* activated_view_; + View* shift_focus_to_; + + DISALLOW_COPY_AND_ASSIGN(FocusShiftingActivationObserver); +}; + +// BasicFocusRules subclass that allows basic overrides of focus/activation to +// be tested. This is intended more as a test that the override system works at +// all, rather than as an exhaustive set of use cases, those should be covered +// in tests for those FocusRules implementations. +class TestFocusRules : public BasicFocusRules { + public: + TestFocusRules(View* root) + : BasicFocusRules(root), focus_restriction_(NULL) {} + + // Restricts focus and activation to this view and its child hierarchy. + void set_focus_restriction(View* focus_restriction) { + focus_restriction_ = focus_restriction; + } + + // Overridden from BasicFocusRules: + bool SupportsChildActivation(View* view) const override { + // In FocusControllerTests, only the Root has activatable children. + return view->GetRoot() == view; + } + bool CanActivateView(View* view) const override { + // Restricting focus to a non-activatable child view means the activatable + // parent outside the focus restriction is activatable. + bool can_activate = + CanFocusOrActivate(view) || view->Contains(focus_restriction_); + return can_activate ? BasicFocusRules::CanActivateView(view) : false; + } + bool CanFocusView(View* view) const override { + return CanFocusOrActivate(view) ? BasicFocusRules::CanFocusView(view) + : false; + } + View* GetActivatableView(View* view) const override { + return BasicFocusRules::GetActivatableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetFocusableView(View* view) const override { + return BasicFocusRules::GetFocusableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetNextActivatableView(View* ignore) const override { + View* next_activatable = BasicFocusRules::GetNextActivatableView(ignore); + return CanFocusOrActivate(next_activatable) + ? next_activatable + : GetActivatableView(focus_restriction_); + } + + private: + bool CanFocusOrActivate(View* view) const { + return !focus_restriction_ || focus_restriction_->Contains(view); + } + + View* focus_restriction_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusRules); +}; + +// Common infrastructure shared by all FocusController test types. +class FocusControllerTestBase : public testing::Test { + protected: + // Hierarchy used by all tests: + // root_view + // +-- w1 + // | +-- w11 + // | +-- w12 + // +-- w2 + // | +-- w21 + // | +-- w211 + // +-- w3 + FocusControllerTestBase() + : root_view_(TestView::Build(0, gfx::Rect(0, 0, 800, 600))), + v1(TestView::Build(1, gfx::Rect(0, 0, 50, 50), root_view())), + v11(TestView::Build(11, gfx::Rect(5, 5, 10, 10), v1)), + v12(TestView::Build(12, gfx::Rect(15, 15, 10, 10), v1)), + v2(TestView::Build(2, gfx::Rect(75, 75, 50, 50), root_view())), + v21(TestView::Build(21, gfx::Rect(5, 5, 10, 10), v2)), + v211(TestView::Build(211, gfx::Rect(1, 1, 5, 5), v21)), + v3(TestView::Build(3, gfx::Rect(125, 125, 50, 50), root_view())) {} + + // Overridden from testing::Test: + void SetUp() override { + testing::Test::SetUp(); + + test_focus_rules_ = new TestFocusRules(root_view()); + focus_controller_.reset( + new FocusController(scoped_ptr<FocusRules>(test_focus_rules_))); + SetFocusController(root_view(), focus_controller_.get()); + + capture_controller_.reset(new CaptureController); + SetCaptureController(root_view(), capture_controller_.get()); + + ViewTarget* root_target = root_view_->target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_.reset(new ViewEventDispatcher()); + view_event_dispatcher_->SetRootViewTarget(root_target); + + GetRootViewTarget()->AddPreTargetHandler(focus_controller_.get()); + } + + void TearDown() override { + GetRootViewTarget()->RemovePreTargetHandler(focus_controller_.get()); + view_event_dispatcher_.reset(); + + root_view_->Destroy(); + + capture_controller_.reset(); + test_focus_rules_ = nullptr; // Owned by FocusController. + focus_controller_.reset(); + + testing::Test::TearDown(); + } + + void FocusView(View* view) { focus_controller_->FocusView(view); } + View* GetFocusedView() { return focus_controller_->GetFocusedView(); } + int GetFocusedViewId() { + View* focused_view = GetFocusedView(); + return focused_view ? focused_view->id() : -1; + } + void ActivateView(View* view) { focus_controller_->ActivateView(view); } + void DeactivateView(View* view) { focus_controller_->DeactivateView(view); } + View* GetActiveView() { return focus_controller_->GetActiveView(); } + int GetActiveViewId() { + View* active_view = GetActiveView(); + return active_view ? active_view->id() : -1; + } + + View* GetViewById(int id) { return root_view_->GetChildById(id); } + + void ClickLeftButton(View* view) { + // Get the center bounds of |target| in |root_view_| coordinate space. + gfx::Point center = + gfx::Rect(view->bounds().To<gfx::Rect>().size()).CenterPoint(); + ViewTarget::ConvertPointToTarget(ViewTarget::TargetFromView(view), + root_view_->target(), ¢er); + + ui::MouseEvent button_down(ui::ET_MOUSE_PRESSED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&button_down); + CHECK(!details.dispatcher_destroyed); + + ui::MouseEvent button_up(ui::ET_MOUSE_RELEASED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + details = view_event_dispatcher_->OnEventFromSource(&button_up); + CHECK(!details.dispatcher_destroyed); + } + + ViewTarget* GetRootViewTarget() { + return ViewTarget::TargetFromView(root_view()); + } + + View* root_view() { return root_view_; } + TestFocusRules* test_focus_rules() { return test_focus_rules_; } + FocusController* focus_controller() { return focus_controller_.get(); } + CaptureController* capture_controller() { return capture_controller_.get(); } + + // Test functions. + virtual void BasicFocus() = 0; + virtual void BasicActivation() = 0; + virtual void FocusEvents() = 0; + virtual void DuplicateFocusEvents() {} + virtual void ActivationEvents() = 0; + virtual void ReactivationEvents() {} + virtual void DuplicateActivationEvents() {} + virtual void ShiftFocusWithinActiveView() {} + virtual void ShiftFocusToChildOfInactiveView() {} + virtual void ShiftFocusToParentOfFocusedView() {} + virtual void FocusRulesOverride() = 0; + virtual void ActivationRulesOverride() = 0; + virtual void ShiftFocusOnActivation() {} + virtual void ShiftFocusOnActivationDueToHide() {} + virtual void NoShiftActiveOnActivation() {} + virtual void ChangeFocusWhenNothingFocusedAndCaptured() {} + virtual void DontPassDestroyedView() {} + // TODO(erg): Also, void FocusedTextInputClient() once we build the IME. + + private: + TestView* root_view_; + scoped_ptr<FocusController> focus_controller_; + TestFocusRules* test_focus_rules_; + scoped_ptr<CaptureController> capture_controller_; + // TODO(erg): The aura version of this class also keeps track of WMState. Do + // we need something analogous here? + + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + TestView* v1; + TestView* v11; + TestView* v12; + TestView* v2; + TestView* v21; + TestView* v211; + TestView* v3; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerTestBase); +}; + +// Test base for tests where focus is directly set to a target view. +class FocusControllerDirectTestBase : public FocusControllerTestBase { + protected: + FocusControllerDirectTestBase() {} + + // Different test types shift focus in different ways. + virtual void FocusViewDirect(View* view) = 0; + virtual void ActivateViewDirect(View* view) = 0; + virtual void DeactivateViewDirect(View* view) = 0; + + // Input events do not change focus if the view can not be focused. + virtual bool IsInputEvent() = 0; + + void FocusViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + FocusViewDirect(view); + } + void ActivateViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + ActivateViewDirect(view); + } + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(nullptr, GetFocusedView()); + FocusViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + } + void BasicActivation() override { + EXPECT_EQ(nullptr, GetActiveView()); + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + // Verify that attempting to deactivate NULL does not crash and does not + // change activation. + DeactivateView(nullptr); + EXPECT_EQ(2, GetActiveViewId()); + DeactivateView(GetActiveView()); + EXPECT_EQ(1, GetActiveViewId()); + } + void FocusEvents() override { + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + { + SCOPED_TRACE("initial state"); + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(1); + { + SCOPED_TRACE("FocusViewById(1)"); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(2); + { + SCOPED_TRACE("FocusViewById(2)"); + root_observer.ExpectCounts(2, 2); + observer1.ExpectCounts(2, 2); + observer2.ExpectCounts(1, 1); + } + } + void DuplicateFocusEvents() override { + // Focusing an existing focused view should not resend focus events. + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + } + void ActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ReactivationEvents() override { + ActivateViewById(1); + ScopedFocusNotificationObserver root_observer(focus_controller()); + EXPECT_EQ(0, root_observer.reactivation_count()); + GetViewById(2)->SetVisible(false); + // When we attempt to activate "2", which cannot be activated because it + // is not visible, "1" will be reactivated. + ActivateViewById(2); + EXPECT_EQ(1, root_observer.reactivation_count()); + EXPECT_EQ(GetViewById(2), + root_observer.reactivation_requested_view()); + EXPECT_EQ(GetViewById(1), + root_observer.reactivation_actual_view()); + } + void DuplicateActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + + // Activating an existing active view should not resend activation events. + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ShiftFocusWithinActiveView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ShiftFocusToChildOfInactiveView() override { + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(11, GetFocusedViewId()); + } + void ShiftFocusToParentOfFocusedView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(1); + // Focus should _not_ shift to the parent of the already-focused view. + EXPECT_EQ(11, GetFocusedViewId()); + } + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(GetViewById(211)); + FocusViewById(12); + // Input events leave focus unchanged; direct API calls will change focus + // to the restricted view. + int focused_view = IsInputEvent() ? 11 : 211; + EXPECT_EQ(focused_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ActivationRulesOverride() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* v3 = GetViewById(3); + test_focus_rules()->set_focus_restriction(v3); + + ActivateViewById(2); + // Input events leave activation unchanged; direct API calls will activate + // the restricted view. + int active_view = IsInputEvent() ? 1 : 3; + EXPECT_EQ(active_view, GetActiveViewId()); + EXPECT_EQ(active_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + void ShiftFocusOnActivation() override { + // When a view is activated, by default that view is also focused. + // An ActivationChangeObserver may shift focus to another view within the + // same activatable view. + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + ActivateViewById(2); + + View* target = GetViewById(1); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(11)); + focus_controller()->AddObserver(observer.get()); + + ActivateViewById(1); + + // w1's ActivationChangeObserver shifted focus to this child, pre-empting + // FocusController's default setting. + EXPECT_EQ(11, GetFocusedViewId()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + + // Simulate a focus reset by the ActivationChangeObserver. This should + // trigger the default setting in FocusController. + observer->set_shift_focus_to(nullptr); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + } + void ShiftFocusOnActivationDueToHide() override { + // Similar to ShiftFocusOnActivation except the activation change is + // triggered by hiding the active view. + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + // Removes view 3 as candidate for next activatable view. + root_view()->GetChildById(3)->SetVisible(false); + EXPECT_EQ(1, GetFocusedViewId()); + + View* target = root_view()->GetChildById(2); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(21)); + focus_controller()->AddObserver(observer.get()); + + // Hide the active view. + root_view()->GetChildById(1)->SetVisible(false); + + EXPECT_EQ(21, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + } + void NoShiftActiveOnActivation() override { + // When a view is activated, we need to prevent any change to activation + // from being made in response to an activation change notification. + } + + // Verifies focus change is honored while capture held. + void ChangeFocusWhenNothingFocusedAndCaptured() override { + View* v1 = root_view()->GetChildById(1); + capture_controller()->SetCapture(v1); + + EXPECT_EQ(-1, GetActiveViewId()); + EXPECT_EQ(-1, GetFocusedViewId()); + + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + capture_controller()->ReleaseCapture(v1); + } + + // Verifies if a view that loses activation or focus is destroyed during + // observer notification we don't pass the destroyed view to other observers. + void DontPassDestroyedView() override { + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + { + View* to_destroy = root_view()->GetChildById(1); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(2); + + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + + { + View* to_destroy = root_view()->GetChildById(2); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(3); + + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDirectTestBase); +}; + +// Focus and Activation changes via the FocusController API. +class FocusControllerApiTest : public FocusControllerDirectTestBase { + public: + FocusControllerApiTest() {} + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { FocusView(view); } + void ActivateViewDirect(View* view) override { ActivateView(view); } + void DeactivateViewDirect(View* view) override { DeactivateView(view); } + bool IsInputEvent() override { return false; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerApiTest); +}; + +// Focus and Activation changes via input events. +class FocusControllerMouseEventTest : public FocusControllerDirectTestBase { + public: + FocusControllerMouseEventTest() {} + + // Tests that a handled mouse event does not trigger a view activation. + void IgnoreHandledEvent() { + EXPECT_EQ(NULL, GetActiveView()); + View* v1 = root_view()->GetChildById(1); + SimpleEventHandler handler; + GetRootViewTarget()->PrependPreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(NULL, GetActiveView()); + // TODO(erg): Add gesture testing when we get gestures working. + GetRootViewTarget()->RemovePreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(1, GetActiveViewId()); + } + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { ClickLeftButton(view); } + void ActivateViewDirect(View* view) override { ClickLeftButton(view); } + void DeactivateViewDirect(View* view) override { + View* next_activatable = test_focus_rules()->GetNextActivatableView(view); + ClickLeftButton(next_activatable); + } + bool IsInputEvent() override { return true; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest); +}; + +// TODO(erg): Add a FocusControllerGestureEventTest once we have working +// gesture forwarding and handling. + +// Test base for tests where focus is implicitly set to a window as the result +// of a disposition change to the focused window or the hierarchy that contains +// it. +class FocusControllerImplicitTestBase : public FocusControllerTestBase { + protected: + explicit FocusControllerImplicitTestBase(bool parent) : parent_(parent) {} + + View* GetDispositionView(View* view) { + return parent_ ? view->parent() : view; + } + + // Change the disposition of |view| in such a way as it will lose focus. + virtual void ChangeViewDisposition(View* view) = 0; + + // Allow each disposition change test to add additional post-disposition + // change expectations. + virtual void PostDispostionChangeExpectations() {} + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(NULL, GetFocusedView()); + + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + ChangeViewDisposition(w211); + // BasicFocusRules passes focus to the parent. + EXPECT_EQ(parent_ ? 2 : 21, GetFocusedViewId()); + } + + void BasicActivation() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + EXPECT_EQ(NULL, GetActiveView()); + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + EXPECT_EQ(2, GetActiveViewId()); + + ChangeViewDisposition(w2); + EXPECT_EQ(3, GetActiveViewId()); + PostDispostionChangeExpectations(); + } + + void FocusEvents() override { + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer211( + focus_controller(), GetViewById(211), GetActiveView(), + GetFocusedView()); + + { + SCOPED_TRACE("first"); + root_observer.ExpectCounts(0, 0); + observer211.ExpectCounts(0, 0); + } + + ChangeViewDisposition(w211); + { + SCOPED_TRACE("second"); + { + SCOPED_TRACE("root_observer"); + root_observer.ExpectCounts(0, 1); + } + { + SCOPED_TRACE("observer211"); + observer211.ExpectCounts(0, 1); + } + } + } + + void ActivationEvents() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer3( + focus_controller(), GetViewById(3), GetActiveView(), GetFocusedView()); + root_observer.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + observer3.ExpectCounts(0, 0); + + ChangeViewDisposition(w2); + root_observer.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + observer3.ExpectCounts(1, 1); + } + + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(root_view()->GetChildById(11)); + ChangeViewDisposition(w211); + // Normally, focus would shift to the parent (w21) but the override shifts + // it to 11. + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + } + + void ActivationRulesOverride() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w1 = root_view()->GetChildById(1); + ActivateView(w1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* w3 = root_view()->GetChildById(3); + test_focus_rules()->set_focus_restriction(w3); + + // Normally, activation/focus would move to w2, but since we have a focus + // restriction, it should move to w3 instead. + ChangeViewDisposition(w1); + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateView(root_view()->GetChildById(2)); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + + private: + // When true, the disposition change occurs to the parent of the window + // instead of to the window. This verifies that changes occurring in the + // hierarchy that contains the window affect the window's focus. + bool parent_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerImplicitTestBase); +}; + +// Focus and Activation changes in response to window visibility changes. +class FocusControllerHideTest : public FocusControllerImplicitTestBase { + public: + FocusControllerHideTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerHideTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->SetVisible(false); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerHideTest); +}; + +// Focus and Activation changes in response to window parent visibility +// changes. +class FocusControllerParentHideTest : public FocusControllerHideTest { + public: + FocusControllerParentHideTest() : FocusControllerHideTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentHideTest); +}; + +// Focus and Activation changes in response to window destruction. +class FocusControllerDestructionTest : public FocusControllerImplicitTestBase { + public: + FocusControllerDestructionTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerDestructionTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->Destroy(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDestructionTest); +}; + +// Focus and Activation changes in response to window removal. +class FocusControllerRemovalTest : public FocusControllerImplicitTestBase { + public: + FocusControllerRemovalTest() + : FocusControllerImplicitTestBase(false), + window_to_destroy_(nullptr) {} + + protected: + FocusControllerRemovalTest(bool parent) + : FocusControllerImplicitTestBase(parent), + window_to_destroy_(nullptr) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + View* disposition_view = GetDispositionView(view); + disposition_view->parent()->RemoveChild(disposition_view); + window_to_destroy_ = disposition_view; + } + void TearDown() override { + if (window_to_destroy_) + window_to_destroy_->Destroy(); + + FocusControllerImplicitTestBase::TearDown(); + } + + private: + View* window_to_destroy_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerRemovalTest); +}; + +// Focus and Activation changes in response to window parent removal. +class FocusControllerParentRemovalTest : public FocusControllerRemovalTest { + public: + FocusControllerParentRemovalTest() : FocusControllerRemovalTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentRemovalTest); +}; + +#define FOCUS_CONTROLLER_TEST(TESTCLASS, TESTNAME) \ + TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); } + +// Runs direct focus change tests (input events and API calls). +// +// TODO(erg): Enable gesture events in the future. +#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target. +#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerDestructionTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerRemovalTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target's parent +// hierarchy. +#define IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentRemovalTest, TESTNAME) +// TODO(erg): FocusControllerParentDestructionTest were commented out in the +// aura version of this file, and don't work when I tried porting things over. + +// Runs all implicit focus change tests (changes to the target and target's +// parent hierarchy) +#define IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) + +// Runs all possible focus change tests. +#define ALL_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) + +// Runs focus change tests that apply only to the target. For example, +// implicit activation changes caused by window disposition changes do not +// occur when changes to the containing hierarchy happen. +#define TARGET_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) + +// - Focuses a window, verifies that focus changed. +ALL_FOCUS_TESTS(BasicFocus); + +// - Activates a window, verifies that activation changed. +TARGET_FOCUS_TESTS(BasicActivation); + +// - Focuses a window, verifies that focus events were dispatched. +ALL_FOCUS_TESTS(FocusEvents); + +// - Focuses or activates a window multiple times, verifies that events are only +// dispatched when focus/activation actually changes. +DIRECT_FOCUS_CHANGE_TESTS(DuplicateFocusEvents); +DIRECT_FOCUS_CHANGE_TESTS(DuplicateActivationEvents); + +// - Activates a window, verifies that activation events were dispatched. +TARGET_FOCUS_TESTS(ActivationEvents); + +// - Attempts to active a hidden view, verifies that current view is +// attempted to be reactivated and the appropriate event dispatched. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, ReactivationEvents); + +// - Input events/API calls shift focus between focusable views within the +// active view. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusWithinActiveView); + +// - Input events/API calls to a child view of an inactive view shifts +// activation to the activatable parent and focuses the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToChildOfInactiveView); + +// - Input events/API calls to focus the parent of the focused view do not +// shift focus away from the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToParentOfFocusedView); + +// - Verifies that FocusRules determine what can be focused. +ALL_FOCUS_TESTS(FocusRulesOverride); + +// - Verifies that FocusRules determine what can be activated. +TARGET_FOCUS_TESTS(ActivationRulesOverride); + +// - Verifies that attempts to change focus or activation from a focus or +// activation change observer are ignored. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivation); +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivationDueToHide); +DIRECT_FOCUS_CHANGE_TESTS(NoShiftActiveOnActivation); + +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, + ChangeFocusWhenNothingFocusedAndCaptured); + +// See description above DontPassDestroyedView() for details. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, DontPassDestroyedView); + +// TODO(erg): Add the TextInputClient tests here. + +// If a mouse event was handled, it should not activate a view. +FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent); + +} // namespace window_manager diff --git a/mojo/services/window_manager/focus_rules.h b/mojo/services/window_manager/focus_rules.h new file mode 100644 index 0000000..becd114 --- /dev/null +++ b/mojo/services/window_manager/focus_rules.h @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ +#define SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ + +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace window_manager { + +// Implemented by an object that establishes the rules about what can be +// focused or activated. +class FocusRules { + public: + virtual ~FocusRules() {} + + // Returns true if the children of |window| can be activated. + virtual bool SupportsChildActivation(mojo::View* window) const = 0; + + // Returns true if |view| is a toplevel view. Whether or not a view + // is considered toplevel is determined by a similar set of rules that + // govern activation and focus. Not all toplevel views are activatable, + // call CanActivateView() to determine if a view can be activated. + virtual bool IsToplevelView(mojo::View* view) const = 0; + // Returns true if |view| can be activated or focused. + virtual bool CanActivateView(mojo::View* view) const = 0; + // For CanFocusView(), null is supported, because null is a valid focusable + // view (in the case of clearing focus). + virtual bool CanFocusView(mojo::View* view) const = 0; + + // Returns the toplevel view containing |view|. Not all toplevel views + // are activatable, call GetActivatableView() instead to return the + // activatable view, which might be in a different hierarchy. + // Will return null if |view| is not contained by a view considered to be + // a toplevel view. + virtual mojo::View* GetToplevelView(mojo::View* view) const = 0; + // Returns the activatable or focusable view given an attempt to activate or + // focus |view|. Some possible scenarios (not intended to be exhaustive): + // - |view| is a child of a non-focusable view and so focus must be set + // according to rules defined by the delegate, e.g. to a parent. + // - |view| is an activatable view that is the transient parent of a modal + // view, so attempts to activate |view| should result in the modal + // transient being activated instead. + // These methods may return null if they are unable to find an activatable + // or focusable view given |view|. + virtual mojo::View* GetActivatableView(mojo::View* view) const = 0; + virtual mojo::View* GetFocusableView(mojo::View* view) const = 0; + + // Returns the next view to activate in the event that |ignore| is no longer + // activatable. This function is called when something is happening to + // |ignore| that means it can no longer have focus or activation, including + // but not limited to: + // - it or its parent hierarchy is being hidden, or removed from the + // RootView. + // - it is being destroyed. + // - it is being explicitly deactivated. + // |ignore| cannot be null. + virtual mojo::View* GetNextActivatableView(mojo::View* ignore) const = 0; +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_FOCUS_RULES_H_ diff --git a/mojo/services/window_manager/hit_test.h b/mojo/services/window_manager/hit_test.h new file mode 100644 index 0000000..747dd01 --- /dev/null +++ b/mojo/services/window_manager/hit_test.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_HIT_TEST_H_ +#define SERVICES_WINDOW_MANAGER_HIT_TEST_H_ + +#if !defined(OS_WIN) + +// Defines the same symbolic names used by the WM_NCHITTEST Notification under +// win32 (the integer values are not guaranteed to be equivalent). We do this +// because we have a whole bunch of code that deals with window resizing and +// such that requires these values. +enum HitTestCompat { + HTNOWHERE = 0, + HTBORDER, + HTBOTTOM, + HTBOTTOMLEFT, + HTBOTTOMRIGHT, + HTCAPTION, + HTCLIENT, + HTCLOSE, + HTERROR, + HTGROWBOX, + HTHELP, + HTHSCROLL, + HTLEFT, + HTMENU, + HTMAXBUTTON, + HTMINBUTTON, + HTREDUCE, + HTRIGHT, + HTSIZE, + HTSYSMENU, + HTTOP, + HTTOPLEFT, + HTTOPRIGHT, + HTTRANSPARENT, + HTVSCROLL, + HTZOOM +}; + +#endif // !defined(OS_WIN) + +#endif // SERVICES_WINDOW_MANAGER_HIT_TEST_H_ diff --git a/mojo/services/window_manager/main.cc b/mojo/services/window_manager/main.cc new file mode 100644 index 0000000..701a249 --- /dev/null +++ b/mojo/services/window_manager/main.cc @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/tracing_impl.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "mojo/services/window_manager/window_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" + +// ApplicationDelegate implementation file for WindowManager users (e.g. +// core window manager tests) that do not want to provide their own +// ApplicationDelegate::Create(). + +using mojo::View; +using mojo::ViewManager; + +namespace window_manager { + +class DefaultWindowManager : public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate, + public WindowManagerDelegate { + public: + DefaultWindowManager() + : window_manager_app_(new WindowManagerApp(this, this)), + root_(nullptr), + window_offset_(10) { + } + ~DefaultWindowManager() override {} + + private: + // Overridden from mojo::ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* impl) override { + window_manager_app_->Initialize(impl); + tracing_.Initialize(impl); + } + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + window_manager_app_->ConfigureIncomingConnection(connection); + return true; + } + + // Overridden from ViewManagerDelegate: + void OnEmbed(View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + root_ = root; + window_manager_app_->InitFocus( + make_scoped_ptr(new window_manager::BasicFocusRules(root_))); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + // Overridden from WindowManagerDelegate: + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + DCHECK(root_); + View* view = root_->view_manager()->CreateView(); + root_->AddChild(view); + + mojo::Rect rect; + rect.x = rect.y = window_offset_; + rect.width = rect.height = 100; + view->SetBounds(rect); + window_offset_ += 10; + + view->SetVisible(true); + view->Embed(url, services.Pass(), exposed_services.Pass()); + } + + scoped_ptr<WindowManagerApp> window_manager_app_; + + View* root_; + int window_offset_; + mojo::TracingImpl tracing_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(DefaultWindowManager); +}; + +} // namespace window_manager + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner( + new window_manager::DefaultWindowManager); + return runner.Run(shell_handle); +} diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc new file mode 100644 index 0000000..4bd0b80 --- /dev/null +++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.cc @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h" + +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +NativeViewportEventDispatcherImpl::NativeViewportEventDispatcherImpl( + WindowManagerApp* app, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request) + : app_(app), binding_(this, request.Pass()) { +} +NativeViewportEventDispatcherImpl::~NativeViewportEventDispatcherImpl() { +} + +ui::EventProcessor* NativeViewportEventDispatcherImpl::GetEventProcessor() { + return app_->event_dispatcher(); +} + +void NativeViewportEventDispatcherImpl::OnEvent( + mojo::EventPtr event, + const mojo::Callback<void()>& callback) { + scoped_ptr<ui::Event> ui_event = event.To<scoped_ptr<ui::Event>>(); + + if (ui_event) + SendEventToProcessor(ui_event.get()); + + callback.Run(); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h new file mode 100644 index 0000000..6624c93 --- /dev/null +++ b/mojo/services/window_manager/native_viewport_event_dispatcher_impl.h @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ +#define SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ + +#include "base/basictypes.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "ui/events/event_source.h" + +namespace window_manager { + +class WindowManagerApp; + +class NativeViewportEventDispatcherImpl + : public ui::EventSource, + public mojo::NativeViewportEventDispatcher { + public: + NativeViewportEventDispatcherImpl( + WindowManagerApp* app, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request); + ~NativeViewportEventDispatcherImpl() override; + + private: + // ui::EventSource: + ui::EventProcessor* GetEventProcessor() override; + + // NativeViewportEventDispatcher: + void OnEvent(mojo::EventPtr event, + const mojo::Callback<void()>& callback) override; + + WindowManagerApp* app_; + + mojo::StrongBinding<mojo::NativeViewportEventDispatcher> binding_; + DISALLOW_COPY_AND_ASSIGN(NativeViewportEventDispatcherImpl); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_NATIVE_VIEWPORT_EVENT_DISPATCHER_IMPL_H_ diff --git a/mojo/services/window_manager/run_all_unittests.cc b/mojo/services/window_manager/run_all_unittests.cc new file mode 100644 index 0000000..51fd967 --- /dev/null +++ b/mojo/services/window_manager/run_all_unittests.cc @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "ui/gl/gl_surface.h" + +#if defined(USE_X11) +#include "ui/gfx/x/x11_connection.h" +#endif + +namespace window_manager { + +class WindowManagerTestSuite : public base::TestSuite { + public: + WindowManagerTestSuite(int argc, char** argv) : TestSuite(argc, argv) {} + ~WindowManagerTestSuite() override {} + + protected: + void Initialize() override { +#if defined(USE_X11) + // Each test ends up creating a new thread for the native viewport service. + // In other words we'll use X on different threads, so tell it that. + gfx::InitializeThreadedX11(); +#endif + base::TestSuite::Initialize(); + gfx::GLSurface::InitializeOneOffForTests(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(WindowManagerTestSuite); +}; + +} // namespace window_manager + +int main(int argc, char** argv) { + window_manager::WindowManagerTestSuite test_suite(argc, argv); + + return base::LaunchUnitTests( + argc, argv, base::Bind(&TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/mojo/services/window_manager/view_event_dispatcher.cc b/mojo/services/window_manager/view_event_dispatcher.cc new file mode 100644 index 0000000..657cf88 --- /dev/null +++ b/mojo/services/window_manager/view_event_dispatcher.cc @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/view_event_dispatcher.h" + +#include "mojo/services/window_manager/view_target.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace window_manager { + +ViewEventDispatcher::ViewEventDispatcher() + : event_dispatch_target_(nullptr), + old_dispatch_target_(nullptr) { +} + +ViewEventDispatcher::~ViewEventDispatcher() {} + +void ViewEventDispatcher::SetRootViewTarget(ViewTarget* root_view_target) { + root_view_target_ = root_view_target; +} + +ui::EventTarget* ViewEventDispatcher::GetRootTarget() { + return root_view_target_; +} + +void ViewEventDispatcher::OnEventProcessingStarted(ui::Event* event) { +} + +bool ViewEventDispatcher::CanDispatchToTarget(ui::EventTarget* target) { + return event_dispatch_target_ == target; +} + +ui::EventDispatchDetails ViewEventDispatcher::PreDispatchEvent( + ui::EventTarget* target, + ui::Event* event) { + // TODO(erg): PreDispatch in aura::WindowEventDispatcher does many, many + // things. It, and the functions split off for different event types, are + // most of the file. + old_dispatch_target_ = event_dispatch_target_; + event_dispatch_target_ = static_cast<ViewTarget*>(target); + return ui::EventDispatchDetails(); +} + +ui::EventDispatchDetails ViewEventDispatcher::PostDispatchEvent( + ui::EventTarget* target, + const ui::Event& event) { + // TODO(erg): Not at all as long as PreDispatchEvent, but still missing core + // details. + event_dispatch_target_ = old_dispatch_target_; + old_dispatch_target_ = nullptr; + return ui::EventDispatchDetails(); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_event_dispatcher.h b/mojo/services/window_manager/view_event_dispatcher.h new file mode 100644 index 0000000..399939e --- /dev/null +++ b/mojo/services/window_manager/view_event_dispatcher.h @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/events/event_processor.h" +#include "ui/events/event_target.h" + +namespace window_manager { + +class ViewTarget; + +class ViewEventDispatcher : public ui::EventProcessor { + public: + ViewEventDispatcher(); + ~ViewEventDispatcher() override; + + void SetRootViewTarget(ViewTarget* root_view_target); + + private: + // Overridden from ui::EventProcessor: + ui::EventTarget* GetRootTarget() override; + void OnEventProcessingStarted(ui::Event* event) override; + + // Overridden from ui::EventDispatcherDelegate. + bool CanDispatchToTarget(ui::EventTarget* target) override; + ui::EventDispatchDetails PreDispatchEvent(ui::EventTarget* target, + ui::Event* event) override; + ui::EventDispatchDetails PostDispatchEvent( + ui::EventTarget* target, const ui::Event& event) override; + + // We keep a weak reference to ViewTarget*, which corresponds to the root of + // the mojo::View tree. + ViewTarget* root_view_target_; + + ViewTarget* event_dispatch_target_; + ViewTarget* old_dispatch_target_; + + DISALLOW_COPY_AND_ASSIGN(ViewEventDispatcher); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_EVENT_DISPATCHER_H_ diff --git a/mojo/services/window_manager/view_target.cc b/mojo/services/window_manager/view_target.cc new file mode 100644 index 0000000..adb3ec2 --- /dev/null +++ b/mojo/services/window_manager/view_target.cc @@ -0,0 +1,192 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/view_target.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_property.h" +#include "ui/events/event.h" +#include "ui/events/event_target_iterator.h" +#include "ui/events/event_targeter.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/transform.h" + +namespace window_manager { + +namespace { + +DEFINE_OWNED_VIEW_PROPERTY_KEY(ViewTarget, kViewTargetKey, nullptr); + +// Provides a version which keeps a copy of the data (for when it has to be +// derived instead of pointed at). +template <typename T> +class CopyingEventTargetIteratorImpl : public ui::EventTargetIterator { + public: + explicit CopyingEventTargetIteratorImpl(const std::vector<T*>& children) + : children_(children), + begin_(children_.rbegin()), + end_(children_.rend()) {} + ~CopyingEventTargetIteratorImpl() override {} + + ui::EventTarget* GetNextTarget() override { + if (begin_ == end_) + return nullptr; + ui::EventTarget* target = *(begin_); + ++begin_; + return target; + } + + private: + typename std::vector<T*> children_; + typename std::vector<T*>::const_reverse_iterator begin_; + typename std::vector<T*>::const_reverse_iterator end_; +}; + +} // namespace + +ViewTarget::~ViewTarget() { +} + +// static +ViewTarget* ViewTarget::TargetFromView(mojo::View* view) { + if (!view) + return nullptr; + + ViewTarget* target = view->GetLocalProperty(kViewTargetKey); + if (target) + return target; + + return new ViewTarget(view); +} + +void ViewTarget::ConvertPointToTarget(const ViewTarget* source, + const ViewTarget* target, + gfx::Point* point) { + // TODO(erg): Do we need to deal with |source| and |target| being in + // different trees? + DCHECK_EQ(source->GetRoot(), target->GetRoot()); + if (source == target) + return; + + const ViewTarget* root_target = source->GetRoot(); + CHECK_EQ(root_target, target->GetRoot()); + + if (source != root_target) + source->ConvertPointForAncestor(root_target, point); + if (target != root_target) + target->ConvertPointFromAncestor(root_target, point); +} + +std::vector<ViewTarget*> ViewTarget::GetChildren() const { + std::vector<ViewTarget*> targets; + for (mojo::View* child : view_->children()) + targets.push_back(TargetFromView(child)); + return targets; +} + +const ViewTarget* ViewTarget::GetParent() const { + return TargetFromView(view_->parent()); +} + +gfx::Rect ViewTarget::GetBounds() const { + return view_->bounds().To<gfx::Rect>(); +} + +bool ViewTarget::HasParent() const { + return !!view_->parent(); +} + +bool ViewTarget::IsVisible() const { + return view_->visible(); +} + +const ViewTarget* ViewTarget::GetRoot() const { + const ViewTarget* root = this; + for (const ViewTarget* parent = this; parent; parent = parent->GetParent()) + root = parent; + return root; +} + +scoped_ptr<ViewTargeter> ViewTarget::SetEventTargeter( + scoped_ptr<ViewTargeter> targeter) { + scoped_ptr<ViewTargeter> old_targeter = targeter_.Pass(); + targeter_ = targeter.Pass(); + return old_targeter.Pass(); +} + +bool ViewTarget::CanAcceptEvent(const ui::Event& event) { + // We need to make sure that a touch cancel event and any gesture events it + // creates can always reach the window. This ensures that we receive a valid + // touch / gesture stream. + if (event.IsEndingEvent()) + return true; + + if (!view_->visible()) + return false; + + // The top-most window can always process an event. + if (!view_->parent()) + return true; + + // In aura, we only accept events if this is a key event or if the user + // supplied a TargetHandler, usually the aura::WindowDelegate. Here, we're + // just forwarding events to other Views which may be in other processes, so + // always accept. + return true; +} + +ui::EventTarget* ViewTarget::GetParentTarget() { + return TargetFromView(view_->parent()); +} + +scoped_ptr<ui::EventTargetIterator> ViewTarget::GetChildIterator() const { + return scoped_ptr<ui::EventTargetIterator>( + new CopyingEventTargetIteratorImpl<ViewTarget>(GetChildren())); +} + +ui::EventTargeter* ViewTarget::GetEventTargeter() { + return targeter_.get(); +} + +void ViewTarget::ConvertEventToTarget(ui::EventTarget* target, + ui::LocatedEvent* event) { + event->ConvertLocationToTarget(this, static_cast<ViewTarget*>(target)); +} + +ViewTarget::ViewTarget(mojo::View* view_to_wrap) : view_(view_to_wrap) { + DCHECK(view_->GetLocalProperty(kViewTargetKey) == nullptr); + view_->SetLocalProperty(kViewTargetKey, this); +} + +bool ViewTarget::ConvertPointForAncestor(const ViewTarget* ancestor, + gfx::Point* point) const { + gfx::Vector2d offset; + bool result = GetTargetOffsetRelativeTo(ancestor, &offset); + *point += offset; + return result; +} + +bool ViewTarget::ConvertPointFromAncestor(const ViewTarget* ancestor, + gfx::Point* point) const { + gfx::Vector2d offset; + bool result = GetTargetOffsetRelativeTo(ancestor, &offset); + *point -= offset; + return result; +} + +bool ViewTarget::GetTargetOffsetRelativeTo(const ViewTarget* ancestor, + gfx::Vector2d* offset) const { + const ViewTarget* v = this; + for (; v && v != ancestor; v = v->GetParent()) { + gfx::Rect bounds = v->GetBounds(); + *offset += gfx::Vector2d(bounds.x(), bounds.y()); + } + return v == ancestor; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_target.h b/mojo/services/window_manager/view_target.h new file mode 100644 index 0000000..1119e1f --- /dev/null +++ b/mojo/services/window_manager/view_target.h @@ -0,0 +1,100 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ + +#include "ui/events/event_target.h" + +namespace gfx { +class Point; +class Rect; +class Vector2d; +} + +namespace ui { +class EventTargeter; +} + +namespace mojo { +class View; +} + +namespace window_manager { + +class TestView; +class ViewTargeter; +class WindowManagerApp; + +// A wrapper class around mojo::View; we can't subclass View to implement the +// event targeting interfaces, so we create a separate object which observes +// the View and ties its lifetime to it. +// +// We set ourselves as a property of the view passed in, and we are owned by +// said View. +class ViewTarget : public ui::EventTarget { + public: + ~ViewTarget() override; + + // Returns the ViewTarget for a View. ViewTargets are owned by the |view| + // passed in, and are created on demand. + static ViewTarget* TargetFromView(mojo::View* view); + + // Converts |point| from |source|'s coordinates to |target|'s. If |source| is + // NULL, the function returns without modifying |point|. |target| cannot be + // NULL. + static void ConvertPointToTarget(const ViewTarget* source, + const ViewTarget* target, + gfx::Point* point); + + mojo::View* view() { return view_; } + + // TODO(erg): Make this const once we've removed aura from the tree and it's + // feasible to change all callers of the EventTargeter interface to pass and + // accept const objects. (When that gets done, re-const the + // EventTargetIterator::GetNextTarget and EventTarget::GetChildIterator + // interfaces.) + std::vector<ViewTarget*> GetChildren() const; + + const ViewTarget* GetParent() const; + gfx::Rect GetBounds() const; + bool HasParent() const; + bool IsVisible() const; + + const ViewTarget* GetRoot() const; + + // Sets a new ViewTargeter for the view, and returns the previous + // ViewTargeter. + scoped_ptr<ViewTargeter> SetEventTargeter(scoped_ptr<ViewTargeter> targeter); + + // Overridden from ui::EventTarget: + bool CanAcceptEvent(const ui::Event& event) override; + EventTarget* GetParentTarget() override; + scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override; + ui::EventTargeter* GetEventTargeter() override; + void ConvertEventToTarget(ui::EventTarget* target, + ui::LocatedEvent* event) override; + + private: + friend class TestView; + explicit ViewTarget(mojo::View* view_to_wrap); + + bool ConvertPointForAncestor(const ViewTarget* ancestor, + gfx::Point* point) const; + bool ConvertPointFromAncestor(const ViewTarget* ancestor, + gfx::Point* point) const; + bool GetTargetOffsetRelativeTo(const ViewTarget* ancestor, + gfx::Vector2d* offset) const; + + // The mojo::View that we dispatch to. + mojo::View* view_; + + scoped_ptr<ViewTargeter> targeter_; + + DISALLOW_COPY_AND_ASSIGN(ViewTarget); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGET_H_ diff --git a/mojo/services/window_manager/view_target_unittest.cc b/mojo/services/window_manager/view_target_unittest.cc new file mode 100644 index 0000000..bec5c37 --- /dev/null +++ b/mojo/services/window_manager/view_target_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/view_target.h" + +#include <set> + +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "ui/gfx/geometry/rect.h" + +namespace window_manager { + +using ViewTargetTest = testing::Test; + +// V1 +// +-- V2 +// +-- V3 +TEST_F(ViewTargetTest, GetRoot) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + TestView v3(3, gfx::Rect(10, 10, 100, 100)); + v1.AddChild(&v2); + v2.AddChild(&v3); + + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v1)->GetRoot()); + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v2)->GetRoot()); + EXPECT_EQ(ViewTarget::TargetFromView(&v1), + ViewTarget::TargetFromView(&v3)->GetRoot()); +} + +// V1 +// +-- V2 +TEST_F(ViewTargetTest, ConvertPointToTarget_Simple) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + v1.AddChild(&v2); + + ViewTarget* t1 = v1.target(); + ViewTarget* t2 = v2.target(); + + gfx::Point point1_in_t2_coords(5, 5); + ViewTarget::ConvertPointToTarget(t2, t1, &point1_in_t2_coords); + gfx::Point point1_in_t1_coords(15, 15); + EXPECT_EQ(point1_in_t1_coords, point1_in_t2_coords); + + gfx::Point point2_in_t1_coords(5, 5); + ViewTarget::ConvertPointToTarget(t1, t2, &point2_in_t1_coords); + gfx::Point point2_in_t2_coords(-5, -5); + EXPECT_EQ(point2_in_t2_coords, point2_in_t1_coords); +} + +// V1 +// +-- V2 +// +-- V3 +TEST_F(ViewTargetTest, ConvertPointToTarget_Medium) { + TestView v1(1, gfx::Rect(20, 20, 400, 400)); + TestView v2(2, gfx::Rect(10, 10, 350, 350)); + TestView v3(3, gfx::Rect(10, 10, 100, 100)); + v1.AddChild(&v2); + v2.AddChild(&v3); + + ViewTarget* t1 = v1.target(); + ViewTarget* t3 = v3.target(); + + gfx::Point point1_in_t3_coords(5, 5); + ViewTarget::ConvertPointToTarget(t3, t1, &point1_in_t3_coords); + gfx::Point point1_in_t1_coords(25, 25); + EXPECT_EQ(point1_in_t1_coords, point1_in_t3_coords); + + gfx::Point point2_in_t1_coords(5, 5); + ViewTarget::ConvertPointToTarget(t1, t3, &point2_in_t1_coords); + gfx::Point point2_in_t3_coords(-15, -15); + EXPECT_EQ(point2_in_t3_coords, point2_in_t1_coords); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_targeter.cc b/mojo/services/window_manager/view_targeter.cc new file mode 100644 index 0000000..58fb370 --- /dev/null +++ b/mojo/services/window_manager/view_targeter.cc @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/view_targeter.h" + +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/view_target.h" + +namespace window_manager { + +ViewTargeter::ViewTargeter() {} + +ViewTargeter::~ViewTargeter() {} + +ui::EventTarget* ViewTargeter::FindTargetForEvent(ui::EventTarget* root, + ui::Event* event) { + ViewTarget* view = static_cast<ViewTarget*>(root); + ViewTarget* target = + event->IsKeyEvent() + ? FindTargetForKeyEvent(view, *static_cast<ui::KeyEvent*>(event)) + : static_cast<ViewTarget*>( + EventTargeter::FindTargetForEvent(root, event)); + + // TODO(erg): The aura version of this method does a lot of work to handle + // dispatching to a target that isn't a child of |view|. For now, punt on + // this. + DCHECK_EQ(view->GetRoot(), target->GetRoot()); + + return target; +} + +ui::EventTarget* ViewTargeter::FindTargetForLocatedEvent( + ui::EventTarget* root, + ui::LocatedEvent* event) { + ViewTarget* view = static_cast<ViewTarget*>(root); + if (!view->HasParent()) { + ViewTarget* target = FindTargetInRootView(view, *event); + if (target) { + view->ConvertEventToTarget(target, event); + return target; + } + } + return EventTargeter::FindTargetForLocatedEvent(view, event); +} + +bool ViewTargeter::SubtreeCanAcceptEvent(ui::EventTarget* target, + const ui::LocatedEvent& event) const { + ViewTarget* view = static_cast<ViewTarget*>(target); + + if (!view->IsVisible()) + return false; + + // TODO(erg): We may need to keep track of the parent on ViewTarget, because + // we have a check here about + // WindowDelegate::ShouldDescendIntoChildForEventHandling(). + + // TODO(sky): decide if we really want this. If we do, it should be a public + // constant and documented. + if (view->view()->shared_properties().count("deliver-events-to-parent")) + return false; + + return true; +} + +bool ViewTargeter::EventLocationInsideBounds( + ui::EventTarget* target, + const ui::LocatedEvent& event) const { + ViewTarget* view = static_cast<ViewTarget*>(target); + gfx::Point point = event.location(); + const ViewTarget* parent = view->GetParent(); + if (parent) + ViewTarget::ConvertPointToTarget(parent, view, &point); + return gfx::Rect(view->GetBounds().size()).Contains(point); +} + +ViewTarget* ViewTargeter::FindTargetForKeyEvent(ViewTarget* view_target, + const ui::KeyEvent& key) { + FocusController* focus_controller = GetFocusController(view_target->view()); + if (focus_controller) { + mojo::View* focused_view = focus_controller->GetFocusedView(); + if (focused_view) + return ViewTarget::TargetFromView(focused_view); + } + return view_target; +} + +ViewTarget* ViewTargeter::FindTargetInRootView(ViewTarget* root_view, + const ui::LocatedEvent& event) { + // TODO(erg): This here is important because it resolves + // mouse_pressed_handler() in the aura version. This is what makes sure + // that a view gets both the mouse down and up. + + CaptureController* capture_controller = + GetCaptureController(root_view->view()); + if (capture_controller) { + mojo::View* capture_view = capture_controller->GetCapture(); + if (capture_view) + return ViewTarget::TargetFromView(capture_view); + } + + // TODO(erg): There's a whole bunch of junk about handling touch events + // here. Handle later. + + return nullptr; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/view_targeter.h b/mojo/services/window_manager/view_targeter.h new file mode 100644 index 0000000..3a01e57 --- /dev/null +++ b/mojo/services/window_manager/view_targeter.h @@ -0,0 +1,44 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ +#define SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ + +#include "ui/events/event_targeter.h" + +namespace window_manager { + +class ViewTarget; + +class ViewTargeter : public ui::EventTargeter { + public: + ViewTargeter(); + ~ViewTargeter() override; + + protected: + // ui::EventTargeter: + ui::EventTarget* FindTargetForEvent(ui::EventTarget* root, + ui::Event* event) override; + ui::EventTarget* FindTargetForLocatedEvent(ui::EventTarget* root, + ui::LocatedEvent* event) override; + bool SubtreeCanAcceptEvent(ui::EventTarget* target, + const ui::LocatedEvent& event) const override; + bool EventLocationInsideBounds(ui::EventTarget* target, + const ui::LocatedEvent& event) const override; + + private: + // Targets either the root View or the currently focused view. + ViewTarget* FindTargetForKeyEvent(ViewTarget* view, const ui::KeyEvent& key); + + // Deals with cases where the |root_view| needs to change how things are + // dispatched. (For example, in the case of capture.) + ViewTarget* FindTargetInRootView(ViewTarget* root_view, + const ui::LocatedEvent& event); + + DISALLOW_COPY_AND_ASSIGN(ViewTargeter); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_VIEW_TARGETER_H_ diff --git a/mojo/services/window_manager/view_targeter_unittest.cc b/mojo/services/window_manager/view_targeter_unittest.cc new file mode 100644 index 0000000..0b3281d --- /dev/null +++ b/mojo/services/window_manager/view_targeter_unittest.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/view_targeter.h" + +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_utils.h" +#include "ui/events/test/test_event_handler.h" + +namespace window_manager { + +class ViewTargeterTest : public testing::Test { + public: + ViewTargeterTest() {} + ~ViewTargeterTest() override {} + + void SetUp() override { + view_event_dispatcher_.reset(new ViewEventDispatcher()); + } + + void TearDown() override { + view_event_dispatcher_.reset(); + testing::Test::TearDown(); + } + + protected: + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewTargeterTest); +}; + +TEST_F(ViewTargeterTest, Basic) { + // The dispatcher will take ownership of the tree root. + TestView root(1, gfx::Rect(0, 0, 100, 100)); + ViewTarget* root_target = root.target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_->SetRootViewTarget(root_target); + + CaptureController capture_controller; + SetCaptureController(&root, &capture_controller); + + TestView one(2, gfx::Rect(0, 0, 500, 100)); + TestView two(3, gfx::Rect(501, 0, 500, 1000)); + + root.AddChild(&one); + root.AddChild(&two); + + ui::test::TestEventHandler handler; + one.target()->AddPreTargetHandler(&handler); + + ui::MouseEvent press(ui::ET_MOUSE_PRESSED, gfx::Point(20, 20), + gfx::Point(20, 20), ui::EventTimeForNow(), ui::EF_NONE, + ui::EF_NONE); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&press); + ASSERT_FALSE(details.dispatcher_destroyed); + + EXPECT_EQ(1, handler.num_mouse_events()); + + one.target()->RemovePreTargetHandler(&handler); +} + +TEST_F(ViewTargeterTest, KeyTest) { + // The dispatcher will take ownership of the tree root. + TestView root(1, gfx::Rect(0, 0, 100, 100)); + ViewTarget* root_target = root.target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_->SetRootViewTarget(root_target); + + CaptureController capture_controller; + SetCaptureController(&root, &capture_controller); + + TestView one(2, gfx::Rect(0, 0, 500, 100)); + TestView two(3, gfx::Rect(501, 0, 500, 1000)); + + root.AddChild(&one); + root.AddChild(&two); + + ui::test::TestEventHandler one_handler; + one.target()->AddPreTargetHandler(&one_handler); + + ui::test::TestEventHandler two_handler; + two.target()->AddPreTargetHandler(&two_handler); + + FocusController focus_controller(make_scoped_ptr(new BasicFocusRules(&root))); + SetFocusController(&root, &focus_controller); + + // Focus |one|. Then test that it receives a key event. + focus_controller.FocusView(&one); + ui::KeyEvent key_event_one(ui::ET_KEY_PRESSED, ui::VKEY_A, 0); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&key_event_one); + ASSERT_FALSE(details.dispatcher_destroyed); + EXPECT_EQ(1, one_handler.num_key_events()); + + // Focus |two|. Then test that it receives a key event. + focus_controller.FocusView(&two); + ui::KeyEvent key_event_two(ui::ET_KEY_PRESSED, ui::VKEY_A, 0); + details = view_event_dispatcher_->OnEventFromSource(&key_event_two); + ASSERT_FALSE(details.dispatcher_destroyed); + EXPECT_EQ(1, two_handler.num_key_events()); + + two.target()->RemovePreTargetHandler(&two_handler); + one.target()->RemovePreTargetHandler(&one_handler); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_api_unittest.cc b/mojo/services/window_manager/window_manager_api_unittest.cc new file mode 100644 index 0000000..7d5dccf --- /dev/null +++ b/mojo/services/window_manager/window_manager_api_unittest.cc @@ -0,0 +1,260 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" +#include "mojo/shell/application_manager/application_manager.h" +#include "mojo/shell/shell_test_helper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +using mojo::ApplicationImpl; +using mojo::Id; +using mojo::View; + +namespace window_manager { +namespace { + +const char kTestServiceURL[] = "mojo:test_url"; + +void EmptyResultCallback(bool result) {} + +class TestWindowManagerObserver : public mojo::WindowManagerObserver { + public: + using NodeIdCallback = base::Callback<void(Id)>; + + explicit TestWindowManagerObserver( + mojo::InterfaceRequest<mojo::WindowManagerObserver> observer_request) + : binding_(this, observer_request.Pass()) {} + ~TestWindowManagerObserver() override {} + + void set_focus_changed_callback(const NodeIdCallback& callback) { + focus_changed_callback_ = callback; + } + void set_active_window_changed_callback(const NodeIdCallback& callback) { + active_window_changed_callback_ = callback; + } + + private: + // Overridden from mojo::WindowManagerObserver: + void OnCaptureChanged(Id new_capture_node_id) override {} + void OnFocusChanged(Id focused_node_id) override { + if (!focus_changed_callback_.is_null()) + focus_changed_callback_.Run(focused_node_id); + } + void OnActiveWindowChanged(Id active_window) override { + if (!active_window_changed_callback_.is_null()) + active_window_changed_callback_.Run(active_window); + } + + NodeIdCallback focus_changed_callback_; + NodeIdCallback active_window_changed_callback_; + mojo::Binding<WindowManagerObserver> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver); +}; + +class TestApplicationLoader : public mojo::shell::ApplicationLoader, + public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate { + public: + typedef base::Callback<void(View*)> RootAddedCallback; + + explicit TestApplicationLoader(const RootAddedCallback& root_added_callback) + : root_added_callback_(root_added_callback) {} + ~TestApplicationLoader() override {} + + private: + // Overridden from mojo::shell::ApplicationLoader: + void Load( + const GURL& url, + mojo::InterfaceRequest<mojo::Application> application_request) override { + ASSERT_TRUE(application_request.is_pending()); + scoped_ptr<ApplicationImpl> app( + new ApplicationImpl(this, application_request.Pass())); + apps_.push_back(app.release()); + } + + // Overridden from mojo::ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new mojo::ViewManagerClientFactory(app->shell(), this)); + } + + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + // Overridden from mojo::ViewManagerDelegate: + void OnEmbed(View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override { + root_added_callback_.Run(root); + } + void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override {} + + RootAddedCallback root_added_callback_; + + ScopedVector<ApplicationImpl> apps_; + scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_; + + DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader); +}; + +} // namespace + +class WindowManagerApiTest : public testing::Test { + public: + WindowManagerApiTest() {} + ~WindowManagerApiTest() override {} + + protected: + Id WaitForEmbed() { + Id id; + base::RunLoop run_loop; + root_added_callback_ = base::Bind(&WindowManagerApiTest::OnEmbed, + base::Unretained(this), &id, &run_loop); + run_loop.Run(); + return id; + } + + Id WaitForFocusChange() { + Id new_focused; + base::RunLoop run_loop; + window_manager_observer()->set_focus_changed_callback( + base::Bind(&WindowManagerApiTest::OnFocusChanged, + base::Unretained(this), &new_focused, &run_loop)); + run_loop.Run(); + return new_focused; + } + + Id WaitForActiveWindowChange() { + Id new_active; + base::RunLoop run_loop; + window_manager_observer()->set_active_window_changed_callback( + base::Bind(&WindowManagerApiTest::OnActiveWindowChanged, + base::Unretained(this), &new_active, &run_loop)); + run_loop.Run(); + return new_active; + } + + Id OpenWindow() { + return OpenWindowWithURL(kTestServiceURL); + } + + Id OpenWindowWithURL(const std::string& url) { + base::RunLoop run_loop; + window_manager_->Embed(url, nullptr, nullptr); + run_loop.Run(); + return WaitForEmbed(); + } + + TestWindowManagerObserver* window_manager_observer() { + return window_manager_observer_.get(); + } + + mojo::WindowManagerPtr window_manager_; + + private: + // Overridden from testing::Test: + void SetUp() override { + test_helper_.reset(new mojo::shell::ShellTestHelper); + test_helper_->Init(); + test_helper_->AddURLMapping(GURL("mojo:window_manager"), + GURL("mojo:core_window_manager")); + test_helper_->SetLoaderForURL( + scoped_ptr<mojo::shell::ApplicationLoader>( + new TestApplicationLoader(base::Bind( + &WindowManagerApiTest::OnRootAdded, base::Unretained(this)))), + GURL(kTestServiceURL)); + ConnectToWindowManager2(); + } + void TearDown() override {} + + void ConnectToWindowManager2() { + test_helper_->application_manager()->ConnectToService( + GURL("mojo:window_manager"), &window_manager_); + base::RunLoop connect_loop; + mojo::WindowManagerObserverPtr observer; + window_manager_observer_.reset( + new TestWindowManagerObserver(GetProxy(&observer))); + + window_manager_->GetFocusedAndActiveViews( + observer.Pass(), + base::Bind(&WindowManagerApiTest::GotFocusedAndActiveViews, + base::Unretained(this))); + connect_loop.Run(); + + // The RunLoop above ensures the connection to the window manager completes. + // Without this the ApplicationManager would load the window manager twice. + test_helper_->application_manager()->ConnectToService( + GURL("mojo:core_window_manager"), &window_manager_); + } + + void GotFocusedAndActiveViews(uint32_t, uint32_t, uint32_t) {} + + void OnRootAdded(View* root) { + if (!root_added_callback_.is_null()) + root_added_callback_.Run(root); + } + + void OnEmbed(Id* root_id, + base::RunLoop* loop, + View* root) { + *root_id = root->id(); + loop->Quit(); + } + + void OnFocusChanged(Id* new_focused, + base::RunLoop* run_loop, + Id focused_node_id) { + *new_focused = focused_node_id; + run_loop->Quit(); + } + + void OnActiveWindowChanged(Id* new_active, + base::RunLoop* run_loop, + Id active_node_id) { + *new_active = active_node_id; + run_loop->Quit(); + } + + scoped_ptr<mojo::shell::ShellTestHelper> test_helper_; + scoped_ptr<TestWindowManagerObserver> window_manager_observer_; + TestApplicationLoader::RootAddedCallback root_added_callback_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerApiTest); +}; + +// TODO(sky): resolve this. Temporarily disabled as ApplicationManager ends up +// loading windowmanager twice because of the mapping of window_manager to +// core_window_manager. +TEST_F(WindowManagerApiTest, DISABLED_FocusAndActivateWindow) { + Id first_window = OpenWindow(); + window_manager_->FocusWindow(first_window, base::Bind(&EmptyResultCallback)); + Id id = WaitForFocusChange(); + EXPECT_EQ(id, first_window); + + Id second_window = OpenWindow(); + window_manager_->ActivateWindow(second_window, + base::Bind(&EmptyResultCallback)); + id = WaitForActiveWindowChange(); + EXPECT_EQ(id, second_window); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app.cc b/mojo/services/window_manager/window_manager_app.cc new file mode 100644 index 0000000..99d2b35 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app.cc @@ -0,0 +1,417 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_app.h" + +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/interfaces/application/shell.mojom.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/focus_rules.h" +#include "mojo/services/window_manager/hit_test.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" + +using mojo::ApplicationConnection; +using mojo::Id; +using mojo::ServiceProvider; +using mojo::View; +using mojo::WindowManager; + +namespace window_manager { + +namespace { + +Id GetIdForView(View* view) { + return view ? view->id() : 0; +} + +} // namespace + +// Used for calls to Embed() that occur before we've connected to the +// ViewManager. +struct WindowManagerApp::PendingEmbed { + mojo::String url; + mojo::InterfaceRequest<ServiceProvider> services; + mojo::ServiceProviderPtr exposed_services; +}; + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, public: + +WindowManagerApp::WindowManagerApp( + ViewManagerDelegate* view_manager_delegate, + WindowManagerDelegate* window_manager_delegate) + : shell_(nullptr), + wrapped_view_manager_delegate_(view_manager_delegate), + window_manager_delegate_(window_manager_delegate), + root_(nullptr) { +} + +WindowManagerApp::~WindowManagerApp() { + // TODO(msw|sky): Should this destructor explicitly delete the ViewManager? + mojo::ViewManager* cached_view_manager = view_manager(); + for (RegisteredViewIdSet::const_iterator it = registered_view_id_set_.begin(); + cached_view_manager && it != registered_view_id_set_.end(); ++it) { + View* view = cached_view_manager->GetViewById(*it); + if (view && view == root_) + root_ = nullptr; + if (view) + view->RemoveObserver(this); + } + registered_view_id_set_.clear(); + DCHECK(!root_); + + STLDeleteElements(&connections_); +} + +void WindowManagerApp::AddConnection(WindowManagerImpl* connection) { + DCHECK(connections_.find(connection) == connections_.end()); + connections_.insert(connection); +} + +void WindowManagerApp::RemoveConnection(WindowManagerImpl* connection) { + DCHECK(connections_.find(connection) != connections_.end()); + connections_.erase(connection); +} + +bool WindowManagerApp::SetCapture(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && SetCaptureImpl(view); +} + +bool WindowManagerApp::FocusWindow(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && FocusWindowImpl(view); +} + +bool WindowManagerApp::ActivateWindow(Id view_id) { + View* view = view_manager()->GetViewById(view_id); + return view && ActivateWindowImpl(view); +} + +bool WindowManagerApp::IsReady() const { + return !!root_; +} + +void WindowManagerApp::InitFocus(scoped_ptr<FocusRules> rules) { + DCHECK(root_); + + focus_controller_.reset(new FocusController(rules.Pass())); + focus_controller_->AddObserver(this); + SetFocusController(root_, focus_controller_.get()); + + capture_controller_.reset(new CaptureController); + capture_controller_->AddObserver(this); + SetCaptureController(root_, capture_controller_.get()); +} + +void WindowManagerApp::Embed( + const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + if (view_manager()) { + window_manager_delegate_->Embed(url, services.Pass(), + exposed_services.Pass()); + return; + } + scoped_ptr<PendingEmbed> pending_embed(new PendingEmbed); + pending_embed->url = url; + pending_embed->services = services.Pass(); + pending_embed->exposed_services = exposed_services.Pass(); + pending_embeds_.push_back(pending_embed.release()); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ApplicationDelegate implementation: + +void WindowManagerApp::Initialize(mojo::ApplicationImpl* impl) { + shell_ = impl->shell(); + LaunchViewManager(impl); +} + +bool WindowManagerApp::ConfigureIncomingConnection( + ApplicationConnection* connection) { + connection->AddService<WindowManager>(this); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ViewManagerDelegate implementation: + +void WindowManagerApp::OnEmbed( + View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + DCHECK(!root_); + root_ = root; + + view_event_dispatcher_.reset(new ViewEventDispatcher); + + RegisterSubtree(root_); + + if (wrapped_view_manager_delegate_) { + wrapped_view_manager_delegate_->OnEmbed(root, services.Pass(), + exposed_services.Pass()); + } + + for (PendingEmbed* pending_embed : pending_embeds_) { + Embed(pending_embed->url, pending_embed->services.Pass(), + pending_embed->exposed_services.Pass()); + } + pending_embeds_.clear(); +} + +void WindowManagerApp::OnViewManagerDisconnected( + mojo::ViewManager* view_manager) { + if (wrapped_view_manager_delegate_) + wrapped_view_manager_delegate_->OnViewManagerDisconnected(view_manager); + + base::MessageLoop* message_loop = base::MessageLoop::current(); + if (message_loop && message_loop->is_running()) + message_loop->Quit(); +} + +bool WindowManagerApp::OnPerformAction(mojo::View* view, + const std::string& action) { + if (!view) + return false; + if (action == "capture") + return SetCaptureImpl(view); + if (action == "focus") + return FocusWindowImpl(view); + else if (action == "activate") + return ActivateWindowImpl(view); + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ViewObserver implementation: + +void WindowManagerApp::OnTreeChanged( + const ViewObserver::TreeChangeParams& params) { + if (params.receiver != root_) + return; + DCHECK(params.old_parent || params.new_parent); + if (!params.target) + return; + + if (params.new_parent) { + if (registered_view_id_set_.find(params.target->id()) == + registered_view_id_set_.end()) { + RegisteredViewIdSet::const_iterator it = + registered_view_id_set_.find(params.new_parent->id()); + DCHECK(it != registered_view_id_set_.end()); + RegisterSubtree(params.target); + } + } else if (params.old_parent) { + UnregisterSubtree(params.target); + } +} + +void WindowManagerApp::OnViewDestroying(View* view) { + Unregister(view); + if (view == root_) { + root_ = nullptr; + if (focus_controller_) + focus_controller_->RemoveObserver(this); + if (capture_controller_) + capture_controller_->RemoveObserver(this); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, ui::EventHandler implementation: + +void WindowManagerApp::OnEvent(ui::Event* event) { + if (!window_manager_client_) + return; + + View* view = static_cast<ViewTarget*>(event->target())->view(); + if (!view) + return; + + if (event->IsKeyEvent()) { + const ui::KeyEvent* key_event = static_cast<const ui::KeyEvent*>(event); + if (key_event->type() == ui::ET_KEY_PRESSED) { + ui::Accelerator accelerator = ConvertEventToAccelerator(key_event); + if (accelerator_manager_.Process(accelerator)) + return; + } + } + + if (focus_controller_) + focus_controller_->OnEvent(event); + + window_manager_client_->DispatchInputEventToView(view->id(), + mojo::Event::From(*event)); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, mojo::FocusControllerObserver implementation: + +void WindowManagerApp::OnFocused(View* gained_focus) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyViewFocused(GetIdForView(gained_focus)); + } +} + +void WindowManagerApp::OnActivated(View* gained_active) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyWindowActivated(GetIdForView(gained_active)); + } + if (gained_active) + gained_active->MoveToFront(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, mojo::CaptureControllerObserver implementation: + +void WindowManagerApp::OnCaptureChanged(View* gained_capture) { + for (Connections::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + (*it)->NotifyCaptureChanged(GetIdForView(gained_capture)); + } + if (gained_capture) + gained_capture->MoveToFront(); +} + +//////////////////////////////////////////////////////////////////////////////// +// WindowManagerApp, private: + +bool WindowManagerApp::SetCaptureImpl(View* view) { + CHECK(view); + capture_controller_->SetCapture(view); + return capture_controller_->GetCapture() == view; +} + +bool WindowManagerApp::FocusWindowImpl(View* view) { + CHECK(view); + focus_controller_->FocusView(view); + return focus_controller_->GetFocusedView() == view; +} + +bool WindowManagerApp::ActivateWindowImpl(View* view) { + CHECK(view); + focus_controller_->ActivateView(view); + return focus_controller_->GetActiveView() == view; +} + +void WindowManagerApp::RegisterSubtree(View* view) { + view->AddObserver(this); + DCHECK(registered_view_id_set_.find(view->id()) == + registered_view_id_set_.end()); + // All events pass through the root during dispatch, so we only need a handler + // installed there. + if (view == root_) { + ViewTarget* target = ViewTarget::TargetFromView(view); + target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + target->AddPreTargetHandler(this); + view_event_dispatcher_->SetRootViewTarget(target); + } + registered_view_id_set_.insert(view->id()); + View::Children::const_iterator it = view->children().begin(); + for (; it != view->children().end(); ++it) + RegisterSubtree(*it); +} + +void WindowManagerApp::UnregisterSubtree(View* view) { + for (View* child : view->children()) + UnregisterSubtree(child); + Unregister(view); +} + +void WindowManagerApp::Unregister(View* view) { + RegisteredViewIdSet::iterator it = registered_view_id_set_.find(view->id()); + if (it == registered_view_id_set_.end()) { + // Because we unregister in OnViewDestroying() we can still get a subsequent + // OnTreeChanged for the same view. Ignore this one. + return; + } + view->RemoveObserver(this); + DCHECK(it != registered_view_id_set_.end()); + registered_view_id_set_.erase(it); +} + +void WindowManagerApp::DispatchInputEventToView(View* view, + mojo::EventPtr event) { + window_manager_client_->DispatchInputEventToView(view->id(), event.Pass()); +} + +void WindowManagerApp::SetViewportSize(const gfx::Size& size) { + window_manager_client_->SetViewportSize(mojo::Size::From(size)); +} + +void WindowManagerApp::LaunchViewManager(mojo::ApplicationImpl* app) { + // TODO(sky): figure out logic if this connection goes away. + view_manager_client_factory_.reset( + new mojo::ViewManagerClientFactory(shell_, this)); + + ApplicationConnection* view_manager_app = + app->ConnectToApplication("mojo:view_manager"); + view_manager_app->ConnectToService(&view_manager_service_); + + view_manager_app->AddService<WindowManagerInternal>(this); + view_manager_app->AddService<mojo::NativeViewportEventDispatcher>(this); + + view_manager_app->ConnectToService(&window_manager_client_); +} + +void WindowManagerApp::Create( + ApplicationConnection* connection, + mojo::InterfaceRequest<WindowManagerInternal> request) { + if (wm_internal_binding_.get()) { + VLOG(1) << + "WindowManager allows only one WindowManagerInternal connection."; + return; + } + wm_internal_binding_.reset( + new mojo::Binding<WindowManagerInternal>(this, request.Pass())); +} + +void WindowManagerApp::Create(ApplicationConnection* connection, + mojo::InterfaceRequest<WindowManager> request) { + WindowManagerImpl* wm = new WindowManagerImpl(this, false); + wm->Bind(request.PassMessagePipe()); + // WindowManagerImpl is deleted when the connection has an error, or from our + // destructor. +} + +void WindowManagerApp::Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> request) { + new NativeViewportEventDispatcherImpl(this, request.Pass()); +} + +void WindowManagerApp::CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) { + // TODO(sky): pass in |connection_id| for validation. + WindowManagerImpl* wm = new WindowManagerImpl(this, true); + wm->Bind(window_manager_pipe.Pass()); + // WindowManagerImpl is deleted when the connection has an error, or from our + // destructor. +} + +void WindowManagerApp::SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) { + view_manager_client_.reset( + mojo::ViewManagerClientFactory::WeakBindViewManagerToPipe( + mojo::MakeRequest<mojo::ViewManagerClient>( + view_manager_client_request.Pass()), + view_manager_service_.Pass(), shell_, this)); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app.h b/mojo/services/window_manager/window_manager_app.h new file mode 100644 index 0000000..f91be46 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app.h @@ -0,0 +1,213 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ + +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/services/window_manager/capture_controller_observer.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/native_viewport_event_dispatcher_impl.h" +#include "mojo/services/window_manager/view_target.h" +#include "mojo/services/window_manager/window_manager_impl.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" +#include "ui/base/accelerators/accelerator_manager.h" +#include "ui/events/event_handler.h" + +namespace gfx { +class Size; +} + +namespace window_manager { + +class CaptureController; +class FocusController; +class FocusRules; +class ViewEventDispatcher; +class WindowManagerDelegate; +class WindowManagerImpl; + +// Implements core window manager functionality that could conceivably be shared +// across multiple window managers implementing superficially different user +// experiences. Establishes communication with the view manager. +// A window manager wishing to use this core should create and own an instance +// of this object. They may implement the associated ViewManager/WindowManager +// delegate interfaces exposed by the view manager, this object provides the +// canonical implementation of said interfaces but will call out to the wrapped +// instances. +class WindowManagerApp + : public mojo::ApplicationDelegate, + public mojo::ViewManagerDelegate, + public mojo::ViewObserver, + public ui::EventHandler, + public FocusControllerObserver, + public CaptureControllerObserver, + public mojo::InterfaceFactory<mojo::WindowManager>, + public mojo::InterfaceFactory<mojo::WindowManagerInternal>, + public mojo::InterfaceFactory<mojo::NativeViewportEventDispatcher>, + public mojo::WindowManagerInternal { + public: + WindowManagerApp(ViewManagerDelegate* view_manager_delegate, + WindowManagerDelegate* window_manager_delegate); + ~WindowManagerApp() override; + + ViewEventDispatcher* event_dispatcher() { + return view_event_dispatcher_.get(); + } + + // Register/deregister new connections to the window manager service. + void AddConnection(WindowManagerImpl* connection); + void RemoveConnection(WindowManagerImpl* connection); + + // These are canonical implementations of the window manager API methods. + bool SetCapture(mojo::Id view); + bool FocusWindow(mojo::Id view); + bool ActivateWindow(mojo::Id view); + + void DispatchInputEventToView(mojo::View* view, mojo::EventPtr event); + void SetViewportSize(const gfx::Size& size); + + bool IsReady() const; + + FocusController* focus_controller() { return focus_controller_.get(); } + CaptureController* capture_controller() { return capture_controller_.get(); } + + void InitFocus(scoped_ptr<FocusRules> rules); + + ui::AcceleratorManager* accelerator_manager() { + return &accelerator_manager_; + } + + // WindowManagerImpl::Embed() forwards to this. If connected to ViewManager + // then forwards to delegate, otherwise waits for connection to establish then + // forwards. + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + + // Overridden from ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* impl) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + + private: + // TODO(sky): rename this. Connections is ambiguous. + typedef std::set<WindowManagerImpl*> Connections; + typedef std::set<mojo::Id> RegisteredViewIdSet; + + struct PendingEmbed; + class WindowManagerInternalImpl; + + mojo::ViewManager* view_manager() { + return root_ ? root_->view_manager() : nullptr; + } + + bool SetCaptureImpl(mojo::View* view); + bool FocusWindowImpl(mojo::View* view); + bool ActivateWindowImpl(mojo::View* view); + + ui::Accelerator ConvertEventToAccelerator(const ui::KeyEvent* event); + + // Creates an ViewTarget for every view in the hierarchy beneath |view|, + // and adds to the registry so that it can be retrieved later via + // GetViewTargetForViewId(). + // TODO(beng): perhaps View should have a property bag. + void RegisterSubtree(mojo::View* view); + + // Recursively invokes Unregister() for |view| and all its descendants. + void UnregisterSubtree(mojo::View* view); + + // Deletes the ViewTarget associated with the hierarchy beneath |id|, + // and removes from the registry. + void Unregister(mojo::View* view); + + // Overridden from ViewManagerDelegate: + void OnEmbed(mojo::View* root, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + void OnViewManagerDisconnected(mojo::ViewManager* view_manager) override; + bool OnPerformAction(mojo::View* view, const std::string& action) override; + + // Overridden from ViewObserver: + void OnTreeChanged(const ViewObserver::TreeChangeParams& params) override; + void OnViewDestroying(mojo::View* view) override; + + // Overridden from ui::EventHandler: + void OnEvent(ui::Event* event) override; + + // Overridden from mojo::FocusControllerObserver: + void OnFocused(mojo::View* gained_focus) override; + void OnActivated(mojo::View* gained_active) override; + + // Overridden from mojo::CaptureControllerObserver: + void OnCaptureChanged(mojo::View* gained_capture) override; + + // Creates the connection to the ViewManager. + void LaunchViewManager(mojo::ApplicationImpl* app); + + // InterfaceFactory<WindowManagerInternal>: + void Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override; + + // InterfaceFactory<WindowManager>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManager> request) override; + + // InterfaceFactory<NativeViewportEventDispatcher>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::NativeViewportEventDispatcher> + request) override; + + // WindowManagerInternal: + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override; + void SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) override; + + mojo::Shell* shell_; + + ViewManagerDelegate* wrapped_view_manager_delegate_; + WindowManagerDelegate* window_manager_delegate_; + + mojo::ViewManagerServicePtr view_manager_service_; + scoped_ptr<mojo::ViewManagerClientFactory> view_manager_client_factory_; + mojo::View* root_; + + scoped_ptr<FocusController> focus_controller_; + scoped_ptr<CaptureController> capture_controller_; + + ui::AcceleratorManager accelerator_manager_; + + Connections connections_; + RegisteredViewIdSet registered_view_id_set_; + + mojo::WindowManagerInternalClientPtr window_manager_client_; + + ScopedVector<PendingEmbed> pending_embeds_; + + scoped_ptr<mojo::ViewManagerClient> view_manager_client_; + + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + scoped_ptr<mojo::Binding<WindowManagerInternal>> wm_internal_binding_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerApp); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_APP_H_ diff --git a/mojo/services/window_manager/window_manager_app_android.cc b/mojo/services/window_manager/window_manager_app_android.cc new file mode 100644 index 0000000..84feff1 --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_android.cc @@ -0,0 +1,20 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_app.h" + +#include <android/keycodes.h> + +#include "ui/events/keycodes/keyboard_codes_posix.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + if (event->platform_keycode() == AKEYCODE_BACK) + return ui::Accelerator(ui::VKEY_BROWSER_BACK, 0); + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app_linux.cc b/mojo/services/window_manager/window_manager_app_linux.cc new file mode 100644 index 0000000..11d43dc --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_linux.cc @@ -0,0 +1,14 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_app_win.cc b/mojo/services/window_manager/window_manager_app_win.cc new file mode 100644 index 0000000..11d43dc --- /dev/null +++ b/mojo/services/window_manager/window_manager_app_win.cc @@ -0,0 +1,14 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_app.h" + +namespace window_manager { + +ui::Accelerator WindowManagerApp::ConvertEventToAccelerator( + const ui::KeyEvent* event) { + return ui::Accelerator(event->key_code(), event->flags()); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_apptest.cc b/mojo/services/window_manager/window_manager_apptest.cc new file mode 100644 index 0000000..12a9aca --- /dev/null +++ b/mojo/services/window_manager/window_manager_apptest.cc @@ -0,0 +1,212 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "mojo/public/cpp/application/application_test_base.h" +#include "mojo/public/cpp/application/service_provider_impl.h" +#include "mojo/public/cpp/system/macros.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +namespace mojo { +namespace { + +// TestApplication's view is embedded by the window manager. +class TestApplication : public ApplicationDelegate, public ViewManagerDelegate { + public: + TestApplication() : root_(nullptr) {} + ~TestApplication() override {} + + View* root() const { return root_; } + + void set_embed_callback(const base::Closure& callback) { + embed_callback_ = callback; + } + + private: + // ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new ViewManagerClientFactory(app->shell(), this)); + } + + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + // ViewManagerDelegate: + void OnEmbed(View* root, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override { + root_ = root; + embed_callback_.Run(); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + View* root_; + base::Closure embed_callback_; + scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TestApplication); +}; + +class TestWindowManagerObserver : public WindowManagerObserver { + public: + explicit TestWindowManagerObserver( + InterfaceRequest<WindowManagerObserver> observer_request) + : binding_(this, observer_request.Pass()) {} + ~TestWindowManagerObserver() override {} + + private: + // Overridden from WindowManagerClient: + void OnCaptureChanged(Id new_capture_node_id) override {} + void OnFocusChanged(Id focused_node_id) override {} + void OnActiveWindowChanged(Id active_window) override {} + + Binding<WindowManagerObserver> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerObserver); +}; + +class WindowManagerApplicationTest : public test::ApplicationTestBase { + public: + WindowManagerApplicationTest() {} + ~WindowManagerApplicationTest() override {} + + protected: + // ApplicationTestBase: + void SetUp() override { + ApplicationTestBase::SetUp(); + application_impl()->ConnectToService("mojo:window_manager", + &window_manager_); + } + ApplicationDelegate* GetApplicationDelegate() override { + return &test_application_; + } + + void EmbedApplicationWithURL(const std::string& url) { + window_manager_->Embed(url, nullptr, nullptr); + + base::RunLoop run_loop; + test_application_.set_embed_callback(run_loop.QuitClosure()); + run_loop.Run(); + } + + WindowManagerPtr window_manager_; + TestApplication test_application_; + + private: + MOJO_DISALLOW_COPY_AND_ASSIGN(WindowManagerApplicationTest); +}; + +TEST_F(WindowManagerApplicationTest, Embed) { + EXPECT_EQ(nullptr, test_application_.root()); + EmbedApplicationWithURL(application_impl()->url()); + EXPECT_NE(nullptr, test_application_.root()); +} + +struct BoolCallback { + BoolCallback(bool* bool_value, base::RunLoop* run_loop) + : bool_value(bool_value), run_loop(run_loop) {} + + void Run(bool value) const { + *bool_value = value; + run_loop->Quit(); + } + + bool* bool_value; + base::RunLoop* run_loop; +}; + +TEST_F(WindowManagerApplicationTest, SetCaptureFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->SetCapture(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +TEST_F(WindowManagerApplicationTest, FocusWindowFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->FocusWindow(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +TEST_F(WindowManagerApplicationTest, ActivateWindowFailsFromNonVM) { + EmbedApplicationWithURL(application_impl()->url()); + bool callback_value = true; + base::RunLoop run_loop; + window_manager_->ActivateWindow(test_application_.root()->id(), + BoolCallback(&callback_value, &run_loop)); + run_loop.Run(); + // This call only succeeds for WindowManager connections from the ViewManager. + EXPECT_FALSE(callback_value); +} + +struct FocusedAndActiveViewsCallback { + FocusedAndActiveViewsCallback(uint32* capture_view_id, + uint32* focused_view_id, + uint32* active_view_id, + base::RunLoop* run_loop) + : capture_view_id(capture_view_id), + focused_view_id(focused_view_id), + active_view_id(active_view_id), + run_loop(run_loop) { + } + + void Run(uint32 capture, uint32 focused, uint32 active) const { + *capture_view_id = capture; + *focused_view_id = focused; + *active_view_id = active; + run_loop->Quit(); + } + + uint32* capture_view_id; + uint32* focused_view_id; + uint32* active_view_id; + base::RunLoop* run_loop; +}; + +TEST_F(WindowManagerApplicationTest, GetFocusedAndActiveViewsFailsWithoutFC) { + EmbedApplicationWithURL(application_impl()->url()); + uint32 capture_view_id = -1; + uint32 focused_view_id = -1; + uint32 active_view_id = -1; + base::RunLoop run_loop; + + WindowManagerObserverPtr observer; + scoped_ptr<TestWindowManagerObserver> window_manager_observer( + new TestWindowManagerObserver(GetProxy(&observer))); + + window_manager_->GetFocusedAndActiveViews( + observer.Pass(), + FocusedAndActiveViewsCallback(&capture_view_id, + &focused_view_id, + &active_view_id, + &run_loop)); + run_loop.Run(); + // This call fails if the WindowManager does not have a FocusController. + EXPECT_EQ(0u, capture_view_id); + EXPECT_EQ(0u, focused_view_id); + EXPECT_EQ(0u, active_view_id); +} + +// TODO(msw): Write tests exercising other WindowManager functionality. + +} // namespace +} // namespace mojo diff --git a/mojo/services/window_manager/window_manager_delegate.h b/mojo/services/window_manager/window_manager_delegate.h new file mode 100644 index 0000000..277e3a8 --- /dev/null +++ b/mojo/services/window_manager/window_manager_delegate.h @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ + +#include "mojo/public/cpp/bindings/string.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" + +namespace window_manager { + +class WindowManagerDelegate { + public: + // See WindowManager::Embed() for details. + virtual void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) = 0; + + protected: + virtual ~WindowManagerDelegate() {} +}; + +} // namespace mojo + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_DELEGATE_H_ diff --git a/mojo/services/window_manager/window_manager_impl.cc b/mojo/services/window_manager/window_manager_impl.cc new file mode 100644 index 0000000..99b3e2f --- /dev/null +++ b/mojo/services/window_manager/window_manager_impl.cc @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_impl.h" + +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller.h" +#include "mojo/services/window_manager/window_manager_app.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +using mojo::Callback; +using mojo::Id; + +namespace window_manager { + +WindowManagerImpl::WindowManagerImpl(WindowManagerApp* window_manager, + bool from_vm) + : window_manager_(window_manager), from_vm_(from_vm), binding_(this) { + window_manager_->AddConnection(this); + binding_.set_error_handler(this); +} + +WindowManagerImpl::~WindowManagerImpl() { + window_manager_->RemoveConnection(this); +} + +void WindowManagerImpl::Bind( + mojo::ScopedMessagePipeHandle window_manager_pipe) { + binding_.Bind(window_manager_pipe.Pass()); +} + +void WindowManagerImpl::NotifyViewFocused(Id focused_id) { + if (from_vm_ && observer_) + observer_->OnFocusChanged(focused_id); +} + +void WindowManagerImpl::NotifyWindowActivated(Id active_id) { + if (from_vm_ && observer_) + observer_->OnActiveWindowChanged(active_id); +} + +void WindowManagerImpl::NotifyCaptureChanged(Id capture_id) { + if (from_vm_ && observer_) + observer_->OnCaptureChanged(capture_id); +} + +void WindowManagerImpl::Embed( + const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + window_manager_->Embed(url, services.Pass(), exposed_services.Pass()); +} + +void WindowManagerImpl::SetCapture(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->SetCapture(view)); +} + +void WindowManagerImpl::FocusWindow(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->FocusWindow(view)); +} + +void WindowManagerImpl::ActivateWindow(Id view, + const Callback<void(bool)>& callback) { + callback.Run(from_vm_ && window_manager_->IsReady() && + window_manager_->ActivateWindow(view)); +} + +void WindowManagerImpl::GetFocusedAndActiveViews( + mojo::WindowManagerObserverPtr observer, + const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback) { + observer_ = observer.Pass(); + if (!window_manager_->focus_controller()) { + // TODO(sky): add typedef for 0. + callback.Run(0, 0, 0); + return; + } + mojo::View* capture_view = + window_manager_->capture_controller()->GetCapture(); + mojo::View* active_view = + window_manager_->focus_controller()->GetActiveView(); + mojo::View* focused_view = + window_manager_->focus_controller()->GetFocusedView(); + // TODO(sky): sanitize ids for client. + callback.Run(capture_view ? capture_view->id() : 0, + focused_view ? focused_view->id() : 0, + active_view ? active_view->id() : 0); +} + +void WindowManagerImpl::OnConnectionError() { + delete this; +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_impl.h b/mojo/services/window_manager/window_manager_impl.h new file mode 100644 index 0000000..f51941f --- /dev/null +++ b/mojo/services/window_manager/window_manager_impl.h @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ + +#include "base/basictypes.h" +#include "base/logging.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" + +namespace window_manager { + +class WindowManagerApp; + +class WindowManagerImpl : public mojo::WindowManager, + public mojo::ErrorHandler { + public: + // See description above |from_vm_| for details on |from_vm|. + // WindowManagerImpl deletes itself on connection errors. WindowManagerApp + // also deletes WindowManagerImpl in its destructor. + WindowManagerImpl(WindowManagerApp* window_manager, bool from_vm); + ~WindowManagerImpl() override; + + void Bind(mojo::ScopedMessagePipeHandle window_manager_pipe); + + void NotifyViewFocused(mojo::Id focused_id); + void NotifyWindowActivated(mojo::Id active_id); + void NotifyCaptureChanged(mojo::Id capture_id); + + private: + // mojo::WindowManager: + void Embed(const mojo::String& url, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) override; + void SetCapture(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void FocusWindow(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void ActivateWindow(uint32_t view_id, + const mojo::Callback<void(bool)>& callback) override; + void GetFocusedAndActiveViews( + mojo::WindowManagerObserverPtr observer, + const mojo::WindowManager::GetFocusedAndActiveViewsCallback& callback) + override; + + // mojo::ErrorHandler: + void OnConnectionError() override; + + WindowManagerApp* window_manager_; + + // Whether this connection originated from the ViewManager. Connections that + // originate from the view manager are expected to have clients. Connections + // that don't originate from the view manager do not have clients. + const bool from_vm_; + + mojo::Binding<mojo::WindowManager> binding_; + mojo::WindowManagerObserverPtr observer_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerImpl); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_IMPL_H_ diff --git a/mojo/services/window_manager/window_manager_test_util.cc b/mojo/services/window_manager/window_manager_test_util.cc new file mode 100644 index 0000000..7c3eb21 --- /dev/null +++ b/mojo/services/window_manager/window_manager_test_util.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/services/window_manager/window_manager_test_util.h" + +#include "base/stl_util.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "ui/gfx/geometry/rect.h" + +namespace window_manager { + +TestView::TestView(int id, const gfx::Rect& rect) + : target_(new ViewTarget(this)) { + mojo::ViewPrivate(this).set_id(id); + + mojo::Rect mojo_rect = *mojo::Rect::From(rect); + SetBounds(mojo_rect); +} + +TestView::TestView(int id, const gfx::Rect& rect, View* parent) + : TestView(id, rect) { + parent->AddChild(this); +} + +TestView::~TestView() { +} + +// static +TestView* TestView::Build(int id, const gfx::Rect& rect) { + return new TestView(id, rect); +} + +// static +TestView* TestView::Build(int id, const gfx::Rect& rect, mojo::View* parent) { + return new TestView(id, rect, parent); +} + +} // namespace window_manager diff --git a/mojo/services/window_manager/window_manager_test_util.h b/mojo/services/window_manager/window_manager_test_util.h new file mode 100644 index 0000000..62424c7 --- /dev/null +++ b/mojo/services/window_manager/window_manager_test_util.h @@ -0,0 +1,43 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ +#define SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ + +#include <set> + +#include "mojo/services/window_manager/view_target.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_private.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view.h" + +namespace gfx { +class Rect; +} + +namespace window_manager { + +// A wrapper around View so we can instantiate these directly without a +// ViewManager. +class TestView : public mojo::View { + public: + TestView(int id, const gfx::Rect& rect); + TestView(int id, const gfx::Rect& rect, mojo::View* parent); + ~TestView(); + + // Builds a child view as a pointer. The caller is responsible for making + // sure that the root of any tree allocated this way is Destroy()ed. + static TestView* Build(int id, const gfx::Rect& rect); + static TestView* Build(int id, const gfx::Rect& rect, View* parent); + + ViewTarget* target() { return target_; } + + private: + ViewTarget* target_; + + DISALLOW_COPY_AND_ASSIGN(TestView); +}; + +} // namespace window_manager + +#endif // SERVICES_WINDOW_MANAGER_WINDOW_MANAGER_TEST_UTIL_H_ |