diff options
author | sky <sky@chromium.org> | 2015-04-20 20:08:26 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-04-21 03:08:28 +0000 |
commit | ba75ded669862381ab3885197562fe05982df6ad (patch) | |
tree | 70f5da5f668e6282aa8013c230633a1bcbb5bb25 /components/view_manager | |
parent | c2a1922419f12248bf72a1ea744e66ced39b4cd5 (diff) | |
download | chromium_src-ba75ded669862381ab3885197562fe05982df6ad.zip chromium_src-ba75ded669862381ab3885197562fe05982df6ad.tar.gz chromium_src-ba75ded669862381ab3885197562fe05982df6ad.tar.bz2 |
Moves mojo/services/* to components/* part 3
Moves view_manager
R=ben@chromium.org
TBR=ben@chromium.org
BUG=none
TEST=none
Review URL: https://codereview.chromium.org/1100603004
Cr-Commit-Position: refs/heads/master@{#325973}
Diffstat (limited to 'components/view_manager')
56 files changed, 10215 insertions, 0 deletions
diff --git a/components/view_manager/BUILD.gn b/components/view_manager/BUILD.gn new file mode 100644 index 0000000..04b3988 --- /dev/null +++ b/components/view_manager/BUILD.gn @@ -0,0 +1,182 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/ui.gni") +import("//third_party/mojo/src/mojo/public/mojo_application.gni") +import("//testing/test.gni") + +mojo_native_application("view_manager") { + sources = [ + "main.cc", + "view_manager_app.cc", + "view_manager_app.h", + ] + + deps = [ + ":view_manager_lib", + "//base", + "//mojo/application", + "//mojo/common:tracing_impl", + "//mojo/environment:chromium", + "//mojo/converters/geometry", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} + +source_set("view_manager_lib") { + sources = [ + "access_policy.h", + "access_policy_delegate.h", + "animation_runner.cc", + "animation_runner.h", + "animation_runner_observer.h", + "client_connection.cc", + "client_connection.h", + "connection_manager.cc", + "connection_manager.h", + "connection_manager_delegate.h", + "default_access_policy.cc", + "default_access_policy.h", + "display_manager.cc", + "display_manager.h", + "focus_controller.cc", + "focus_controller.h", + "focus_controller_delegate.h", + "gesture_manager.cc", + "gesture_manager.h", + "gesture_manager_delegate.h", + "scheduled_animation_group.cc", + "scheduled_animation_group.h", + "server_view.cc", + "server_view.h", + "server_view_delegate.h", + "server_view_drawn_tracker.cc", + "server_view_drawn_tracker.h", + "server_view_drawn_tracker_observer.h", + "server_view_observer.h", + "view_coordinate_conversions.cc", + "view_coordinate_conversions.h", + "view_locator.cc", + "view_locator.h", + "view_manager_service_impl.cc", + "view_manager_service_impl.h", + "window_manager_access_policy.cc", + "window_manager_access_policy.h", + ] + + public_deps = [ + "//third_party/mojo_services/src/view_manager/public/cpp", + ] + + deps = [ + "//base", + "//cc/surfaces", + "//cc/surfaces:surface_id", + "//mojo/application", + "//mojo/common", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//mojo/converters/surfaces", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/cpp/bindings:callback", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/input_events/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/interfaces", + "//third_party/mojo_services/src/surfaces/public/cpp", + "//third_party/mojo_services/src/surfaces/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp:common", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//ui/gfx", + "//ui/gfx/geometry", + ] +} + +source_set("test_support") { + testonly = true + + sources = [ + "test_change_tracker.cc", + "test_change_tracker.h", + ] + + deps = [ + "//base", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/cpp:common", + "//third_party/mojo_services/src/view_manager/public/interfaces", + ] +} + +test("view_manager_service_unittests") { + sources = [ + "animation_runner_unittest.cc", + "focus_controller_unittest.cc", + "gesture_manager_unittest.cc", + "scheduled_animation_group_unittest.cc", + "server_view_drawn_tracker_unittest.cc", + "test_server_view_delegate.cc", + "test_server_view_delegate.h", + "view_coordinate_conversions_unittest.cc", + "view_manager_service_unittest.cc", + ] + + deps = [ + ":test_support", + ":view_manager_lib", + "//base", + "//base/test:test_config", + "//mojo/converters/geometry", + "//mojo/converters/input_events", + "//third_party/mojo/src/mojo/edk/test:run_all_unittests", + "//mojo/environment:chromium", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo/src/mojo/public/interfaces/application", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/input_events/public/interfaces", + "//third_party/mojo_services/src/native_viewport/public/cpp:args", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + "//testing/gtest", + "//ui/gfx", + "//ui/gfx:test_support", + "//ui/gfx/geometry", + ] + + if (!is_android) { # TODO(GYP) Enable on Android when osmesa links. + deps += [ "//third_party/mesa:osmesa" ] + } +} + +mojo_native_application("apptests") { + output_name = "view_manager_apptests" + testonly = true + + sources = [ + "view_manager_client_apptest.cc", + "view_manager_service_apptest.cc", + ] + + deps = [ + ":test_support", + "//base", + "//base/test:test_config", + "//mojo/application", + "//mojo/application:test_support", + "//mojo/common", + "//third_party/mojo/src/mojo/public/cpp/bindings:bindings", + "//third_party/mojo_services/src/geometry/public/cpp:cpp", + "//third_party/mojo_services/src/geometry/public/interfaces", + "//third_party/mojo_services/src/view_manager/public/cpp", + "//third_party/mojo_services/src/view_manager/public/interfaces", + "//third_party/mojo_services/src/window_manager/public/interfaces", + ] +} diff --git a/components/view_manager/DEPS b/components/view_manager/DEPS new file mode 100644 index 0000000..69acc37 --- /dev/null +++ b/components/view_manager/DEPS @@ -0,0 +1,9 @@ +include_rules = [ + "+cc", + "+mojo/application", + "+mojo/common", + "+mojo/converters", + "+third_party/mojo/src/mojo/public", + "+third_party/mojo_services", + "+ui", +] diff --git a/components/view_manager/access_policy.h b/components/view_manager/access_policy.h new file mode 100644 index 0000000..eb5815b --- /dev/null +++ b/components/view_manager/access_policy.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_H_ +#define COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_H_ + +#include "components/view_manager/ids.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +namespace view_manager { + +class ServerView; + +// AccessPolicy is used by ViewManagerServiceImpl to determine what a connection +// is allowed to do. +class AccessPolicy { + public: + virtual ~AccessPolicy() {} + + // Unless otherwise mentioned all arguments have been validated. That is the + // |view| arguments are non-null unless otherwise stated (eg CanSetView() is + // allowed to take a NULL view). + virtual bool CanRemoveViewFromParent(const ServerView* view) const = 0; + virtual bool CanAddView(const ServerView* parent, + const ServerView* child) const = 0; + virtual bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const = 0; + virtual bool CanDeleteView(const ServerView* view) const = 0; + virtual bool CanGetViewTree(const ServerView* view) const = 0; + // Used when building a view tree (GetViewTree()) to decide if we should + // descend into |view|. + virtual bool CanDescendIntoViewForViewTree(const ServerView* view) const = 0; + virtual bool CanEmbed(const ServerView* view) const = 0; + virtual bool CanChangeViewVisibility(const ServerView* view) const = 0; + virtual bool CanSetViewSurfaceId(const ServerView* view) const = 0; + virtual bool CanSetViewBounds(const ServerView* view) const = 0; + virtual bool CanSetViewProperties(const ServerView* view) const = 0; + + // Returns whether the connection should notify on a hierarchy change. + // |new_parent| and |old_parent| are initially set to the new and old parents + // but may be altered so that the client only sees a certain set of views. + virtual bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const = 0; +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_H_ diff --git a/components/view_manager/access_policy_delegate.h b/components/view_manager/access_policy_delegate.h new file mode 100644 index 0000000..0d9d105 --- /dev/null +++ b/components/view_manager/access_policy_delegate.h @@ -0,0 +1,36 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ + +#include <vector> + +#include "base/containers/hash_tables.h" +#include "components/view_manager/ids.h" + +namespace view_manager { + +class ServerView; + +// Delegate used by the AccessPolicy implementations to get state. +class AccessPolicyDelegate { + public: + // Returns true if |id| is the root of the connection. + virtual bool IsRootForAccessPolicy(const ViewId& id) const = 0; + + // Returns true if |view| has been exposed to the client. + virtual bool IsViewKnownForAccessPolicy(const ServerView* view) const = 0; + + // Returns true if Embed(view) has been invoked on |view|. + virtual bool IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const = 0; + + protected: + virtual ~AccessPolicyDelegate() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_ACCESS_POLICY_DELEGATE_H_ diff --git a/components/view_manager/animation_runner.cc b/components/view_manager/animation_runner.cc new file mode 100644 index 0000000..6569ff1 --- /dev/null +++ b/components/view_manager/animation_runner.cc @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/animation_runner.h" + +#include "base/memory/scoped_vector.h" +#include "components/view_manager/animation_runner_observer.h" +#include "components/view_manager/scheduled_animation_group.h" +#include "components/view_manager/server_view.h" + +namespace view_manager { +namespace { + +bool ConvertViewAndAnimationPairToScheduledAnimationGroup( + const std::vector<AnimationRunner::ViewAndAnimationPair>& views, + AnimationRunner::AnimationId id, + base::TimeTicks now, + ScopedVector<ScheduledAnimationGroup>* groups) { + for (const auto& view_animation_pair : views) { + DCHECK(view_animation_pair.second); + scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create( + view_animation_pair.first, now, id, *(view_animation_pair.second))); + if (!group.get()) + return false; + groups->push_back(group.release()); + } + return true; +} + +} // namespace + +AnimationRunner::AnimationRunner(base::TimeTicks now) + : next_id_(1), last_tick_time_(now) { +} + +AnimationRunner::~AnimationRunner() { +} + +void AnimationRunner::AddObserver(AnimationRunnerObserver* observer) { + observers_.AddObserver(observer); +} + +void AnimationRunner::RemoveObserver(AnimationRunnerObserver* observer) { + observers_.RemoveObserver(observer); +} + +AnimationRunner::AnimationId AnimationRunner::Schedule( + const std::vector<ViewAndAnimationPair>& views, + base::TimeTicks now) { + DCHECK_GE(now, last_tick_time_); + + const AnimationId animation_id = next_id_++; + ScopedVector<ScheduledAnimationGroup> groups; + if (!ConvertViewAndAnimationPairToScheduledAnimationGroup(views, animation_id, + now, &groups)) { + return 0; + } + + // Cancel any animations for the views. + for (auto* group : groups) { + ScheduledAnimationGroup* current_group = + view_to_animation_map_.get(group->view()); + if (current_group) + current_group->SetValuesToTargetValuesForPropertiesNotIn(*group); + + CancelAnimationForViewImpl(group->view(), CANCEL_SOURCE_SCHEDULE); + } + + for (auto* group : groups) { + group->ObtainStartValues(); + view_to_animation_map_.set(group->view(), make_scoped_ptr(group)); + DCHECK(!id_to_views_map_[animation_id].count(group->view())); + id_to_views_map_[animation_id].insert(group->view()); + } + // |view_to_animation_map_| owns the groups. + groups.weak_clear(); + + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationScheduled(animation_id)); + return animation_id; +} + +void AnimationRunner::CancelAnimation(AnimationId id) { + if (id_to_views_map_.count(id) == 0) + return; + + std::set<ServerView*> views(id_to_views_map_[id]); + for (ServerView* view : views) + CancelAnimationForView(view); +} + +void AnimationRunner::CancelAnimationForView(ServerView* view) { + CancelAnimationForViewImpl(view, CANCEL_SOURCE_CANCEL); +} + +void AnimationRunner::Tick(base::TimeTicks time) { + DCHECK(time >= last_tick_time_); + last_tick_time_ = time; + if (view_to_animation_map_.empty()) + return; + + // The animation ids of any views whose animation completes are added here. We + // notify after processing all views so that if an observer mutates us in some + // way we're aren't left in a weird state. + std::set<AnimationId> animations_completed; + for (ViewToAnimationMap::iterator i = view_to_animation_map_.begin(); + i != view_to_animation_map_.end();) { + if (i->second->Tick(time)) { + const AnimationId animation_id = i->second->id(); + ServerView* view = i->first; + ++i; + if (RemoveViewFromMaps(view)) + animations_completed.insert(animation_id); + } else { + ++i; + } + } + for (const AnimationId& id : animations_completed) { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, OnAnimationDone(id)); + } +} + +void AnimationRunner::CancelAnimationForViewImpl(ServerView* view, + CancelSource source) { + if (!view_to_animation_map_.contains(view)) + return; + + const AnimationId animation_id = view_to_animation_map_.get(view)->id(); + if (RemoveViewFromMaps(view)) { + // This was the last view in the group. + if (source == CANCEL_SOURCE_CANCEL) { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationCanceled(animation_id)); + } else { + FOR_EACH_OBSERVER(AnimationRunnerObserver, observers_, + OnAnimationInterrupted(animation_id)); + } + } +} + +bool AnimationRunner::RemoveViewFromMaps(ServerView* view) { + DCHECK(view_to_animation_map_.contains(view)); + + const AnimationId animation_id = view_to_animation_map_.get(view)->id(); + view_to_animation_map_.erase(view); + + DCHECK(id_to_views_map_.count(animation_id)); + id_to_views_map_[animation_id].erase(view); + if (!id_to_views_map_[animation_id].empty()) + return false; + + id_to_views_map_.erase(animation_id); + return true; +} + +} // namespace view_manager diff --git a/components/view_manager/animation_runner.h b/components/view_manager/animation_runner.h new file mode 100644 index 0000000..02f9f2c --- /dev/null +++ b/components/view_manager/animation_runner.h @@ -0,0 +1,114 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_H_ +#define COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_H_ + +#include <algorithm> +#include <map> +#include <set> +#include <vector> + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/observer_list.h" +#include "base/time/time.h" + +namespace mojo { +class AnimationGroup; +} + +namespace view_manager { + +class AnimationRunnerObserver; +class ScheduledAnimationGroup; +class ServerView; + +// AnimationRunner is responsible for maintaing and running a set of animations. +// The animations are represented as a set of AnimationGroups. New animations +// are scheduled by way of Schedule(). A |view| may only have one animation +// running at a time. Schedule()ing a new animation for a view already animating +// implicitly cancels the current animation for the view. Animations progress +// by way of the Tick() function. +class AnimationRunner { + public: + using AnimationId = uint32_t; + using ViewAndAnimationPair = + std::pair<ServerView*, const mojo::AnimationGroup*>; + + explicit AnimationRunner(base::TimeTicks now); + ~AnimationRunner(); + + void AddObserver(AnimationRunnerObserver* observer); + void RemoveObserver(AnimationRunnerObserver* observer); + + // Schedules animations. If any of the groups are not valid no animations are + // scheuled and 0 is returned. If there is an existing animation in progress + // for any of the views it is canceled and any properties that were animating + // but are no longer animating are set to their target value. + AnimationId Schedule(const std::vector<ViewAndAnimationPair>& views, + base::TimeTicks now); + + // Cancels an animation scheduled by an id that was previously returned from + // Schedule(). + void CancelAnimation(AnimationId id); + + // Cancels the animation scheduled for |view|. Does nothing if there is no + // animation scheduled for |view|. This does not change |view|. That is, any + // in progress animations are stopped. + void CancelAnimationForView(ServerView* view); + + // Advance the animations updating values appropriately. + void Tick(base::TimeTicks time); + + // Returns true if there are animations currently scheduled. + bool HasAnimations() const { return !view_to_animation_map_.empty(); } + + // Returns true if the animation identified by |id| is valid and animating. + bool IsAnimating(AnimationId id) const { + return id_to_views_map_.count(id) > 0; + } + + // Returns the views that are currently animating for |id|. Returns an empty + // set if |id| does not identify a valid animation. + std::set<ServerView*> GetViewsAnimating(AnimationId id) { + return IsAnimating(id) ? id_to_views_map_.find(id)->second + : std::set<ServerView*>(); + } + + private: + enum CancelSource { + // Cancel is the result of scheduling another animation for the view. + CANCEL_SOURCE_SCHEDULE, + + // Cancel originates from an explicit call to cancel. + CANCEL_SOURCE_CANCEL, + }; + + using ViewToAnimationMap = + base::ScopedPtrHashMap<ServerView*, ScheduledAnimationGroup>; + using IdToViewsMap = std::map<AnimationId, std::set<ServerView*>>; + + void CancelAnimationForViewImpl(ServerView* view, CancelSource source); + + // Removes |view| from both |view_to_animation_map_| and |id_to_views_map_|. + // Returns true if there are no more views animating with the animation id + // the view is associated with. + bool RemoveViewFromMaps(ServerView* view); + + AnimationId next_id_; + + base::TimeTicks last_tick_time_; + + ObserverList<AnimationRunnerObserver> observers_; + + ViewToAnimationMap view_to_animation_map_; + + IdToViewsMap id_to_views_map_; + + DISALLOW_COPY_AND_ASSIGN(AnimationRunner); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_H_ diff --git a/components/view_manager/animation_runner_observer.h b/components/view_manager/animation_runner_observer.h new file mode 100644 index 0000000..aa99d4c --- /dev/null +++ b/components/view_manager/animation_runner_observer.h @@ -0,0 +1,23 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ +#define COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ + +namespace view_manager { + +class AnimationRunnerObserver { + public: + virtual void OnAnimationScheduled(uint32_t id) = 0; + virtual void OnAnimationDone(uint32_t id) = 0; + virtual void OnAnimationInterrupted(uint32_t id) = 0; + virtual void OnAnimationCanceled(uint32_t id) = 0; + + protected: + virtual ~AnimationRunnerObserver() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_ANIMATION_RUNNER_OBSERVER_H_ diff --git a/components/view_manager/animation_runner_unittest.cc b/components/view_manager/animation_runner_unittest.cc new file mode 100644 index 0000000..6de5686 --- /dev/null +++ b/components/view_manager/animation_runner_unittest.cc @@ -0,0 +1,641 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/animation_runner.h" + +#include "base/strings/stringprintf.h" +#include "components/view_manager/animation_runner_observer.h" +#include "components/view_manager/scheduled_animation_group.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +using base::TimeDelta; +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::ANIMATION_TWEEN_TYPE_LINEAR; +using mojo::AnimationElement; +using mojo::AnimationGroup; +using mojo::AnimationProperty; +using mojo::AnimationSequence; +using mojo::AnimationTweenType; +using mojo::AnimationValue; +using mojo::AnimationValuePtr; +using mojo::Transform; + +namespace view_manager { +namespace { + +class TestAnimationRunnerObserver : public AnimationRunnerObserver { + public: + TestAnimationRunnerObserver() {} + ~TestAnimationRunnerObserver() override {} + + std::vector<std::string>* changes() { return &changes_; } + std::vector<uint32_t>* change_ids() { return &change_ids_; } + + void clear_changes() { + changes_.clear(); + change_ids_.clear(); + } + + // AnimationRunnerDelgate: + void OnAnimationScheduled(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("scheduled"); + } + void OnAnimationDone(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("done"); + } + void OnAnimationInterrupted(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("interrupted"); + } + void OnAnimationCanceled(uint32_t id) override { + change_ids_.push_back(id); + changes_.push_back("canceled"); + } + + private: + std::vector<uint32_t> change_ids_; + std::vector<std::string> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestAnimationRunnerObserver); +}; + +// Creates an AnimationValuePtr from the specified float value. +AnimationValuePtr FloatAnimationValue(float float_value) { + AnimationValuePtr value(AnimationValue::New()); + value->float_value = float_value; + return value.Pass(); +} + +// Creates an AnimationValuePtr from the specified transform. +AnimationValuePtr TransformAnimationValue(const gfx::Transform& transform) { + AnimationValuePtr value(AnimationValue::New()); + value->transform = Transform::From(transform); + return value.Pass(); +} + +// Adds an AnimationElement to |group|s last sequence with the specified value. +void AddElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value, + AnimationProperty property, + AnimationTweenType tween_type) { + AnimationSequence& sequence = + *(group->sequences[group->sequences.size() - 1]); + sequence.elements.push_back(AnimationElement::New()); + AnimationElement& element = + *(sequence.elements[sequence.elements.size() - 1]); + element.property = property; + element.duration = time.InMicroseconds(); + element.tween_type = tween_type; + element.start_value = start_value.Pass(); + element.target_value = target_value.Pass(); +} + +void AddOpacityElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, start_value.Pass(), target_value.Pass(), + ANIMATION_PROPERTY_OPACITY, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void AddTransformElement(AnimationGroup* group, + TimeDelta time, + AnimationValuePtr start_value, + AnimationValuePtr target_value) { + AddElement(group, time, start_value.Pass(), target_value.Pass(), + ANIMATION_PROPERTY_TRANSFORM, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void AddPauseElement(AnimationGroup* group, TimeDelta time) { + AddElement(group, time, AnimationValuePtr(), AnimationValuePtr(), + ANIMATION_PROPERTY_NONE, ANIMATION_TWEEN_TYPE_LINEAR); +} + +void InitGroupForView(AnimationGroup* group, + const ViewId& id, + int cycle_count) { + group->view_id = ViewIdToTransportId(id); + group->sequences.push_back(AnimationSequence::New()); + group->sequences[group->sequences.size() - 1]->cycle_count = cycle_count; +} + +} // namespace + +class AnimationRunnerTest : public testing::Test { + public: + AnimationRunnerTest() + : initial_time_(base::TimeTicks::Now()), runner_(initial_time_) { + runner_.AddObserver(&runner_observer_); + } + ~AnimationRunnerTest() override { runner_.RemoveObserver(&runner_observer_); } + + protected: + // Convenience to schedule an animation for a single view/group pair. + AnimationRunner::AnimationId ScheduleForSingleView( + ServerView* view, + const AnimationGroup* group, + base::TimeTicks now) { + std::vector<AnimationRunner::ViewAndAnimationPair> pairs; + pairs.push_back(std::make_pair(view, group)); + return runner_.Schedule(pairs, now); + } + + // If |id| is valid and there is only one view schedule against the animation + // it is returned; otherwise returns null. + ServerView* GetSingleViewAnimating(AnimationRunner::AnimationId id) { + std::set<ServerView*> views(runner_.GetViewsAnimating(id)); + return views.size() == 1 ? *views.begin() : nullptr; + } + + const base::TimeTicks initial_time_; + TestAnimationRunnerObserver runner_observer_; + AnimationRunner runner_; + + private: + DISALLOW_COPY_AND_ASSIGN(AnimationRunnerTest); +}; + +// Opacity from 1 to .5 over 1000. +TEST_F(AnimationRunnerTest, SingleProperty) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + runner_observer_.clear_changes(); + + EXPECT_TRUE(runner_.HasAnimations()); + + // Opacity should still be 1 (the initial value). + EXPECT_EQ(1.f, view.opacity()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Run well past the end. Value should progress to end and delegate should + // be notified. + runner_.Tick(initial_time_ + TimeDelta::FromSeconds(10)); + EXPECT_EQ(.5f, view.opacity()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + + EXPECT_FALSE(runner_.HasAnimations()); +} + +// Opacity from 1 to .5, followed by transform from identity to 2x,3x. +TEST_F(AnimationRunnerTest, TwoPropertiesInSequence) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5f)); + + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(2000), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(1.f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Animate half way from through opacity animation. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Finish first element (opacity). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Half way through second (transform). + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Opacity from .5 to 1 over 1000, transform to 2x,4x over 500. +TEST_F(AnimationRunnerTest, TwoPropertiesInParallel) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 1)); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + FloatAnimationValue(.5f), FloatAnimationValue(1)); + + group.sequences.push_back(AnimationSequence::New()); + group.sequences[1]->cycle_count = 1; + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(1.f, view.opacity()); + EXPECT_TRUE(view.transform().IsIdentity()); + + // Animate to 250, which is 1/4 way through opacity and half way through + // transform. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250)); + + EXPECT_EQ(.625f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + // Animate to 500, which is 1/2 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + // Animate to 750, which is 3/4 way through opacity and transform done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.875f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // To end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3500)); + EXPECT_EQ(1.f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Opacity from .5 to 1 over 1000, pause for 500, 1 to .5 over 500, with a cycle +// count of 3. +TEST_F(AnimationRunnerTest, Cycles) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + view.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 3); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + AddPauseElement(&group, TimeDelta::FromMicroseconds(500)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Nothing in the view should have changed yet. + EXPECT_EQ(.5f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1250)); + EXPECT_EQ(1.f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2500)); + EXPECT_EQ(.75f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3250)); + EXPECT_EQ(1.f, view.opacity()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(3750)); + EXPECT_EQ(.75f, view.opacity()); + + // Animate to the end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(6500)); + EXPECT_EQ(.5f, view.opacity()); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); +} + +// Verifies scheduling the same view twice sends an interrupt. +TEST_F(AnimationRunnerTest, ScheduleTwice) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Schedule again. We should get an interrupt, but opacity shouldn't change. + const uint32_t animation2_id = ScheduleForSingleView( + &view, &group, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Id should have changed. + EXPECT_NE(animation_id, animation2_id); + + EXPECT_FALSE(runner_.IsAnimating(animation_id)); + EXPECT_EQ(&view, GetSingleViewAnimating(animation2_id)); + + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(2u, runner_observer_.changes()->size()); + EXPECT_EQ("interrupted", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(1)); + EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(1)); + runner_observer_.clear_changes(); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.625f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation2_id, runner_observer_.change_ids()->at(0)); +} + +// Verifies Remove() works. +TEST_F(AnimationRunnerTest, CancelAnimationForView) { + // Create an animation and advance it part way. + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + const uint32_t animation_id = + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + EXPECT_EQ(&view, GetSingleViewAnimating(animation_id)); + + EXPECT_TRUE(runner_.HasAnimations()); + + // Animate half way. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_TRUE(runner_observer_.changes()->empty()); + + // Cancel the animation. + runner_.CancelAnimationForView(&view); + + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_EQ(nullptr, GetSingleViewAnimating(animation_id)); + + EXPECT_EQ(.75f, view.opacity()); + + EXPECT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("canceled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); +} + +// Verifies a tick with a very large delta and a sequence that repeats forever +// doesn't take a long time. +TEST_F(AnimationRunnerTest, InfiniteRepeatWithHugeGap) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId(1, 2)); + + view.SetOpacity(.5f); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 0); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(1)); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), FloatAnimationValue(.5)); + + ScheduleForSingleView(&view, &group, initial_time_); + runner_observer_.clear_changes(); + + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000000000750)); + + EXPECT_EQ(.75f, view.opacity()); + + ASSERT_EQ(0u, runner_observer_.changes()->size()); +} + +// Verifies a second schedule sets any properties that are no longer animating +// to their final value. +TEST_F(AnimationRunnerTest, RescheduleSetsPropertiesToFinalValue) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + ScheduleForSingleView(&view, &group, initial_time_); + + // Schedule() again, this time without animating opacity. + group.sequences[0]->elements[0]->property = ANIMATION_PROPERTY_NONE; + ScheduleForSingleView(&view, &group, initial_time_); + + // Opacity should go to final value. + EXPECT_EQ(.5f, view.opacity()); + // Transform shouldn't have changed since newly scheduled animation also has + // transform in it. + EXPECT_TRUE(view.transform().IsIdentity()); +} + +// Opacity from 1 to .5 over 1000 of v1 and v2 transform to 2x,4x over 500. +TEST_F(AnimationRunnerTest, TwoViews) { + TestServerViewDelegate view_delegate; + ServerView view1(&view_delegate, ViewId()); + ServerView view2(&view_delegate, ViewId(1, 2)); + + AnimationGroup group1; + InitGroupForView(&group1, view1.id(), 1); + AddOpacityElement(&group1, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(.5)); + + AnimationGroup group2; + InitGroupForView(&group2, view2.id(), 1); + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group2, TimeDelta::FromMicroseconds(500), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + + std::vector<AnimationRunner::ViewAndAnimationPair> pairs; + pairs.push_back(std::make_pair(&view1, &group1)); + pairs.push_back(std::make_pair(&view2, &group2)); + + const uint32_t animation_id = runner_.Schedule(pairs, initial_time_); + + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id, runner_observer_.change_ids()->at(0)); + runner_observer_.clear_changes(); + + EXPECT_TRUE(runner_.HasAnimations()); + EXPECT_TRUE(runner_.IsAnimating(animation_id)); + + // Properties should be at the initial value. + EXPECT_EQ(1.f, view1.opacity()); + EXPECT_TRUE(view2.transform().IsIdentity()); + + // Animate 250ms in, which is quarter way for opacity and half way for + // transform. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(250)); + EXPECT_EQ(.875f, view1.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view2.transform()); + std::set<ServerView*> views_animating( + runner_.GetViewsAnimating(animation_id)); + EXPECT_EQ(2u, views_animating.size()); + EXPECT_EQ(1u, views_animating.count(&view1)); + EXPECT_EQ(1u, views_animating.count(&view2)); + + // Animate 750ms in, view1 should be done 3/4 done, and view2 done. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(750)); + EXPECT_EQ(.625, view1.opacity()); + EXPECT_EQ(done_transform, view2.transform()); + views_animating = runner_.GetViewsAnimating(animation_id); + EXPECT_EQ(1u, views_animating.size()); + EXPECT_EQ(1u, views_animating.count(&view1)); + EXPECT_TRUE(runner_.HasAnimations()); + EXPECT_TRUE(runner_.IsAnimating(animation_id)); + + // Animate to end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1750)); + EXPECT_EQ(.5, view1.opacity()); + EXPECT_EQ(done_transform, view2.transform()); + views_animating = runner_.GetViewsAnimating(animation_id); + EXPECT_TRUE(views_animating.empty()); + EXPECT_FALSE(runner_.HasAnimations()); + EXPECT_FALSE(runner_.IsAnimating(animation_id)); +} + +TEST_F(AnimationRunnerTest, Reschedule) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + + // Animation from 1-0 over 1ms and in parallel transform to 2x,4x over 1ms. + AnimationGroup group; + InitGroupForView(&group, view.id(), 1); + AddOpacityElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(0)); + group.sequences.push_back(AnimationSequence::New()); + group.sequences[1]->cycle_count = 1; + gfx::Transform done_transform; + done_transform.Scale(2, 4); + AddTransformElement(&group, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), + TransformAnimationValue(done_transform)); + const uint32_t animation_id1 = + ScheduleForSingleView(&view, &group, initial_time_); + + // Animate half way in. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(500)); + EXPECT_EQ(.5f, view.opacity()); + gfx::Transform half_way_transform; + half_way_transform.Scale(1.5, 2.5); + EXPECT_EQ(half_way_transform, view.transform()); + + runner_observer_.clear_changes(); + + // Schedule the same view animating opacity to 1. + AnimationGroup group2; + InitGroupForView(&group2, view.id(), 1); + AddOpacityElement(&group2, TimeDelta::FromMicroseconds(1000), + AnimationValuePtr(), FloatAnimationValue(1)); + const uint32_t animation_id2 = ScheduleForSingleView( + &view, &group2, initial_time_ + TimeDelta::FromMicroseconds(500)); + + // Opacity should remain at .5, but transform should go to end state. + EXPECT_EQ(.5f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + + ASSERT_EQ(2u, runner_observer_.changes()->size()); + EXPECT_EQ("interrupted", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id1, runner_observer_.change_ids()->at(0)); + EXPECT_EQ("scheduled", runner_observer_.changes()->at(1)); + EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(1)); + runner_observer_.clear_changes(); + + // Animate half way through new sequence. Opacity should be the only thing + // changing. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(1000)); + EXPECT_EQ(.75f, view.opacity()); + EXPECT_EQ(done_transform, view.transform()); + ASSERT_EQ(0u, runner_observer_.changes()->size()); + + // Animate to end. + runner_.Tick(initial_time_ + TimeDelta::FromMicroseconds(2000)); + ASSERT_EQ(1u, runner_observer_.changes()->size()); + EXPECT_EQ("done", runner_observer_.changes()->at(0)); + EXPECT_EQ(animation_id2, runner_observer_.change_ids()->at(0)); +} + +} // namespace view_manager diff --git a/components/view_manager/client_connection.cc b/components/view_manager/client_connection.cc new file mode 100644 index 0000000..62aae04 --- /dev/null +++ b/components/view_manager/client_connection.cc @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/client_connection.h" + +#include "components/view_manager/connection_manager.h" +#include "components/view_manager/view_manager_service_impl.h" + +namespace view_manager { + +ClientConnection::ClientConnection(scoped_ptr<ViewManagerServiceImpl> service, + mojo::ViewManagerClient* client) + : service_(service.Pass()), client_(client) { +} + +ClientConnection::~ClientConnection() { +} + +DefaultClientConnection::DefaultClientConnection( + scoped_ptr<ViewManagerServiceImpl> service_impl, + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ViewManagerClientPtr client) + : ClientConnection(service_impl.Pass(), client.get()), + connection_manager_(connection_manager), + binding_(service(), service_request.Pass()), + client_(client.Pass()) { + binding_.set_error_handler(this); +} + +DefaultClientConnection::~DefaultClientConnection() { +} + +void DefaultClientConnection::OnConnectionError() { + connection_manager_->OnConnectionError(this); +} + +} // namespace view_manager diff --git a/components/view_manager/client_connection.h b/components/view_manager/client_connection.h new file mode 100644 index 0000000..2589204 --- /dev/null +++ b/components/view_manager/client_connection.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_CLIENT_CONNECTION_H_ +#define COMPONENTS_VIEW_MANAGER_CLIENT_CONNECTION_H_ + +#include "base/memory/scoped_ptr.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace view_manager { + +class ConnectionManager; +class ViewManagerServiceImpl; + +// ClientConnection encapsulates the state needed for a single client connected +// to the view manager. +class ClientConnection { + public: + ClientConnection(scoped_ptr<ViewManagerServiceImpl> service, + mojo::ViewManagerClient* client); + virtual ~ClientConnection(); + + ViewManagerServiceImpl* service() { return service_.get(); } + const ViewManagerServiceImpl* service() const { return service_.get(); } + + mojo::ViewManagerClient* client() { return client_; } + + private: + scoped_ptr<ViewManagerServiceImpl> service_; + mojo::ViewManagerClient* client_; + + DISALLOW_COPY_AND_ASSIGN(ClientConnection); +}; + +// Bindings implementation of ClientConnection. +class DefaultClientConnection : public ClientConnection, + public mojo::ErrorHandler { + public: + DefaultClientConnection( + scoped_ptr<ViewManagerServiceImpl> service_impl, + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ViewManagerClientPtr client); + ~DefaultClientConnection() override; + + private: + // ErrorHandler: + void OnConnectionError() override; + + ConnectionManager* connection_manager_; + mojo::Binding<mojo::ViewManagerService> binding_; + mojo::ViewManagerClientPtr client_; + + DISALLOW_COPY_AND_ASSIGN(DefaultClientConnection); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_CLIENT_CONNECTION_H_ diff --git a/components/view_manager/connection_manager.cc b/components/view_manager/connection_manager.cc new file mode 100644 index 0000000..a7e1f5f --- /dev/null +++ b/components/view_manager/connection_manager.cc @@ -0,0 +1,507 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/connection_manager.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/view_manager/client_connection.h" +#include "components/view_manager/connection_manager_delegate.h" +#include "components/view_manager/display_manager.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/view_coordinate_conversions.h" +#include "components/view_manager/view_manager_service_impl.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "third_party/mojo/src/mojo/public/interfaces/application/service_provider.mojom.h" + +using mojo::ConnectionSpecificId; + +namespace view_manager { +namespace { + +// Creates a copy of |view|. The copied view has |delegate| as its delegate. +// This does not recurse. +ServerView* CloneView(const ServerView* view, ServerViewDelegate* delegate) { + ServerView* clone = new ServerView(delegate, ClonedViewId()); + clone->SetBounds(view->bounds()); + clone->SetSurfaceId(view->surface_id()); + clone->SetOpacity(view->opacity()); + return clone; +} + +// Creates copies of all the visible children of |parent|. Newly cloned views +// are added to |cloned_parent| and have |delegate| as their delegate. The +// stacking order of the cloned views is preseved. +void CloneViewTree(const ServerView* parent, + ServerView* cloned_parent, + ServerViewDelegate* delegate) { + DCHECK(parent->visible()); + for (const ServerView* to_clone : parent->GetChildren()) { + if (to_clone->visible()) { + ServerView* cloned = CloneView(to_clone, delegate); + cloned_parent->Add(cloned); + CloneViewTree(to_clone, cloned, delegate); + } + } +} + +// Recurses through all the children of |view| moving any cloned views to +// |new_parent| stacked above |stack_above|. |stack_above| is updated as views +// are moved. +void ReparentClonedViews(ServerView* new_parent, + ServerView** stack_above, + ServerView* view) { + if (view->id() == ClonedViewId()) { + const gfx::Rect new_bounds(ConvertRectBetweenViews( + view, new_parent, gfx::Rect(view->bounds().size()))); + new_parent->Add(view); + new_parent->Reorder(view, *stack_above, mojo::ORDER_DIRECTION_ABOVE); + view->SetBounds(new_bounds); + *stack_above = view; + return; + } + + for (ServerView* child : view->GetChildren()) + ReparentClonedViews(new_parent, stack_above, child); +} + +// Deletes |view| and all its descendants. +void DeleteViewTree(ServerView* view) { + for (ServerView* child : view->GetChildren()) + DeleteViewTree(child); + + delete view; +} + +// TODO(sky): nuke, proof of concept. +bool DecrementAnimatingViewsOpacity(ServerView* view) { + if (view->id() == ClonedViewId()) { + const float new_opacity = view->opacity() - .05f; + if (new_opacity <= 0) + DeleteViewTree(view); + else + view->SetOpacity(new_opacity); + return true; + } + bool ret_value = false; + for (ServerView* child : view->GetChildren()) { + if (DecrementAnimatingViewsOpacity(child)) + ret_value = true; + } + return ret_value; +} + +} // namespace + +ConnectionManager::ScopedChange::ScopedChange( + ViewManagerServiceImpl* connection, + ConnectionManager* connection_manager, + bool is_delete_view) + : connection_manager_(connection_manager), + connection_id_(connection->id()), + is_delete_view_(is_delete_view) { + connection_manager_->PrepareForChange(this); +} + +ConnectionManager::ScopedChange::~ScopedChange() { + connection_manager_->FinishChange(); +} + +ConnectionManager::ConnectionManager(ConnectionManagerDelegate* delegate, + scoped_ptr<DisplayManager> display_manager, + mojo::WindowManagerInternal* wm_internal) + : delegate_(delegate), + window_manager_client_connection_(nullptr), + next_connection_id_(1), + display_manager_(display_manager.Pass()), + root_(CreateServerView(RootViewId())), + wm_internal_(wm_internal), + current_change_(nullptr), + in_destructor_(false), + animation_runner_(base::TimeTicks::Now()) { + root_->SetBounds(gfx::Rect(800, 600)); + root_->SetVisible(true); + display_manager_->Init(this); +} + +ConnectionManager::~ConnectionManager() { + in_destructor_ = true; + + STLDeleteValues(&connection_map_); + // All the connections should have been destroyed. + DCHECK(connection_map_.empty()); + root_.reset(); +} + +ServerView* ConnectionManager::CreateServerView(const ViewId& id) { + ServerView* view = new ServerView(this, id); + view->AddObserver(this); + return view; +} + +ConnectionSpecificId ConnectionManager::GetAndAdvanceNextConnectionId() { + const ConnectionSpecificId id = next_connection_id_++; + DCHECK_LT(id, next_connection_id_); + return id; +} + +void ConnectionManager::OnConnectionError(ClientConnection* connection) { + if (connection == window_manager_client_connection_) { + window_manager_client_connection_ = nullptr; + delegate_->OnLostConnectionToWindowManager(); + // Assume we've been destroyed. + return; + } + + scoped_ptr<ClientConnection> connection_owner(connection); + + connection_map_.erase(connection->service()->id()); + + // Notify remaining connections so that they can cleanup. + for (auto& pair : connection_map_) { + pair.second->service()->OnWillDestroyViewManagerServiceImpl( + connection->service()); + } +} + +void ConnectionManager::EmbedAtView( + ConnectionSpecificId creator_id, + const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services) { + std::string creator_url; + ConnectionMap::const_iterator it = connection_map_.find(creator_id); + if (it != connection_map_.end()) + creator_url = it->second->service()->url(); + + mojo::ViewManagerServicePtr service_ptr; + ClientConnection* client_connection = + delegate_->CreateClientConnectionForEmbedAtView( + this, GetProxy(&service_ptr), creator_id, creator_url, url, view_id); + AddConnection(client_connection); + client_connection->service()->Init(client_connection->client(), + service_ptr.Pass(), services.Pass(), + exposed_services.Pass()); + OnConnectionMessagedClient(client_connection->service()->id()); +} + +void ConnectionManager::EmbedAtView(mojo::ConnectionSpecificId creator_id, + const ViewId& view_id, + mojo::ViewManagerClientPtr client) { + std::string creator_url; + ConnectionMap::const_iterator it = connection_map_.find(creator_id); + if (it != connection_map_.end()) + creator_url = it->second->service()->url(); + + mojo::ViewManagerServicePtr service_ptr; + ClientConnection* client_connection = + delegate_->CreateClientConnectionForEmbedAtView( + this, GetProxy(&service_ptr), creator_id, creator_url, view_id, + client.Pass()); + AddConnection(client_connection); + client_connection->service()->Init(client_connection->client(), + service_ptr.Pass(), nullptr, nullptr); + OnConnectionMessagedClient(client_connection->service()->id()); +} + +ViewManagerServiceImpl* ConnectionManager::GetConnection( + ConnectionSpecificId connection_id) { + ConnectionMap::iterator i = connection_map_.find(connection_id); + return i == connection_map_.end() ? nullptr : i->second->service(); +} + +ServerView* ConnectionManager::GetView(const ViewId& id) { + if (id == root_->id()) + return root_.get(); + ViewManagerServiceImpl* service = GetConnection(id.connection_id); + return service ? service->GetView(id) : nullptr; +} + +void ConnectionManager::OnConnectionMessagedClient(ConnectionSpecificId id) { + if (current_change_) + current_change_->MarkConnectionAsMessaged(id); +} + +bool ConnectionManager::DidConnectionMessageClient( + ConnectionSpecificId id) const { + return current_change_ && current_change_->DidMessageConnection(id); +} + +const ViewManagerServiceImpl* ConnectionManager::GetConnectionWithRoot( + const ViewId& id) const { + for (auto& pair : connection_map_) { + if (pair.second->service()->IsRoot(id)) + return pair.second->service(); + } + return nullptr; +} + +void ConnectionManager::SetWindowManagerClientConnection( + scoped_ptr<ClientConnection> connection) { + CHECK(!window_manager_client_connection_); + window_manager_client_connection_ = connection.release(); + AddConnection(window_manager_client_connection_); + window_manager_client_connection_->service()->Init( + window_manager_client_connection_->client(), nullptr, nullptr, nullptr); +} + +mojo::ViewManagerClient* +ConnectionManager::GetWindowManagerViewManagerClient() { + CHECK(window_manager_client_connection_); + return window_manager_client_connection_->client(); +} + +bool ConnectionManager::CloneAndAnimate(const ViewId& view_id) { + ServerView* view = GetView(view_id); + if (!view || !view->IsDrawn(root_.get()) || view == root_.get()) + return false; + if (!animation_timer_.IsRunning()) { + animation_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(100), + this, &ConnectionManager::DoAnimation); + } + ServerView* clone = CloneView(view, this); + CloneViewTree(view, clone, this); + view->parent()->Add(clone); + view->parent()->Reorder(clone, view, mojo::ORDER_DIRECTION_ABOVE); + return true; +} + +void ConnectionManager::ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewBoundsChanged( + view, old_bounds, new_bounds, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewportMetricsChanged( + const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewportMetricsChanged( + old_metrics, new_metrics, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessWillChangeViewHierarchy( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessWillChangeViewHierarchy( + view, new_parent, old_parent, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewHierarchyChanged( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewHierarchyChanged( + view, new_parent, old_parent, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewReorder( + const ServerView* view, + const ServerView* relative_view, + const mojo::OrderDirection direction) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewReorder(view, relative_view, direction, + IsChangeSource(pair.first)); + } +} + +void ConnectionManager::ProcessViewDeleted(const ViewId& view) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewDeleted(view, + IsChangeSource(pair.first)); + } +} + +void ConnectionManager::PrepareForChange(ScopedChange* change) { + // Should only ever have one change in flight. + CHECK(!current_change_); + current_change_ = change; +} + +void ConnectionManager::FinishChange() { + // PrepareForChange/FinishChange should be balanced. + CHECK(current_change_); + current_change_ = NULL; +} + +void ConnectionManager::DoAnimation() { + if (!DecrementAnimatingViewsOpacity(root())) + animation_timer_.Stop(); +} + +void ConnectionManager::AddConnection(ClientConnection* connection) { + DCHECK_EQ(0u, connection_map_.count(connection->service()->id())); + connection_map_[connection->service()->id()] = connection; +} + +void ConnectionManager::PrepareToDestroyView(ServerView* view) { + if (!in_destructor_ && root_->Contains(view) && view != root_.get() && + view->id() != ClonedViewId()) { + // We're about to destroy a view. Any cloned views need to be reparented + // else the animation would no longer be visible. By moving to a visible + // view, view->parent(), we ensure the animation is still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::PrepareToChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { + if (view->id() == ClonedViewId() || in_destructor_) + return; + + if (root_->Contains(view) && view != root_.get()) { + // We're about to reparent a view. Any cloned views need to be reparented + // else the animation may be effected in unusual ways. For example, the view + // could move to a new location such that the animation is entirely clipped. + // By moving to view->parent() we ensure the animation is still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::PrepareToChangeViewVisibility(ServerView* view) { + if (in_destructor_) + return; + + if (view != root_.get() && view->id() != ClonedViewId() && + root_->Contains(view) && view->IsDrawn(root_.get())) { + // We're about to hide |view|, this would implicitly make any cloned views + // hide too. Reparent so that animations are still visible. + ServerView* parent_above = view; + ReparentClonedViews(view->parent(), &parent_above, view); + } + + const bool is_parent_drawn = + view->parent() && view->parent()->IsDrawn(root_.get()); + if (!is_parent_drawn || !view->visible()) + animation_runner_.CancelAnimationForView(view); +} + +void ConnectionManager::OnScheduleViewPaint(const ServerView* view) { + if (!in_destructor_) + display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size())); +} + +void ConnectionManager::OnViewDestroyed(ServerView* view) { + if (!in_destructor_) + ProcessViewDeleted(view->id()); +} + +void ConnectionManager::OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { + if (view->id() == ClonedViewId() || in_destructor_) + return; + + ProcessWillChangeViewHierarchy(view, new_parent, old_parent); +} + +void ConnectionManager::OnViewHierarchyChanged(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { + if (in_destructor_) + return; + + ProcessViewHierarchyChanged(view, new_parent, old_parent); + + // TODO(beng): optimize. + if (old_parent) { + display_manager_->SchedulePaint(old_parent, + gfx::Rect(old_parent->bounds().size())); + } + if (new_parent) { + display_manager_->SchedulePaint(new_parent, + gfx::Rect(new_parent->bounds().size())); + } +} + +void ConnectionManager::OnViewBoundsChanged(ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + if (in_destructor_) + return; + + ProcessViewBoundsChanged(view, old_bounds, new_bounds); + if (!view->parent()) + return; + + // TODO(sky): optimize this. + display_manager_->SchedulePaint(view->parent(), old_bounds); + display_manager_->SchedulePaint(view->parent(), new_bounds); +} + +void ConnectionManager::OnViewReordered(ServerView* view, + ServerView* relative, + mojo::OrderDirection direction) { + if (!in_destructor_) + display_manager_->SchedulePaint(view, gfx::Rect(view->bounds().size())); +} + +void ConnectionManager::OnWillChangeViewVisibility(ServerView* view) { + if (in_destructor_) + return; + + // Need to repaint if the view was drawn (which means it's in the process of + // hiding) or the view is transitioning to drawn. + if (view->IsDrawn(root_.get()) || (!view->visible() && view->parent() && + view->parent()->IsDrawn(root_.get()))) { + display_manager_->SchedulePaint(view->parent(), view->bounds()); + } + + for (auto& pair : connection_map_) { + pair.second->service()->ProcessWillChangeViewVisibility( + view, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::OnViewSharedPropertyChanged( + ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) { + for (auto& pair : connection_map_) { + pair.second->service()->ProcessViewPropertyChanged( + view, name, new_data, IsChangeSource(pair.first)); + } +} + +void ConnectionManager::DispatchInputEventToView(mojo::Id transport_view_id, + mojo::EventPtr event) { + const ViewId view_id(ViewIdFromTransportId(transport_view_id)); + + ViewManagerServiceImpl* connection = GetConnectionWithRoot(view_id); + if (!connection) + connection = GetConnection(view_id.connection_id); + if (connection) { + connection->client()->OnViewInputEvent( + transport_view_id, event.Pass(), base::Bind(&base::DoNothing)); + } +} + +void ConnectionManager::SetViewportSize(mojo::SizePtr size) { + gfx::Size new_size = size.To<gfx::Size>(); + display_manager_->SetViewportSize(new_size); +} + +void ConnectionManager::CloneAndAnimate(mojo::Id transport_view_id) { + CloneAndAnimate(ViewIdFromTransportId(transport_view_id)); +} + +} // namespace view_manager diff --git a/components/view_manager/connection_manager.h b/components/view_manager/connection_manager.h new file mode 100644 index 0000000..e7b3347 --- /dev/null +++ b/components/view_manager/connection_manager.h @@ -0,0 +1,252 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_H_ +#define COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/timer/timer.h" +#include "components/view_manager/animation_runner.h" +#include "components/view_manager/ids.h" +#include "components/view_manager/server_view_delegate.h" +#include "components/view_manager/server_view_observer.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/array.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +namespace view_manager { + +class ClientConnection; +class ConnectionManagerDelegate; +class DisplayManager; +class ServerView; +class ViewManagerServiceImpl; + +// ConnectionManager manages the set of connections to the ViewManager (all the +// ViewManagerServiceImpls) as well as providing the root of the hierarchy. +class ConnectionManager : public ServerViewDelegate, + public ServerViewObserver, + public mojo::WindowManagerInternalClient { + public: + // Create when a ViewManagerServiceImpl is about to make a change. Ensures + // clients are notified correctly. + class ScopedChange { + public: + ScopedChange(ViewManagerServiceImpl* connection, + ConnectionManager* connection_manager, + bool is_delete_view); + ~ScopedChange(); + + mojo::ConnectionSpecificId connection_id() const { return connection_id_; } + bool is_delete_view() const { return is_delete_view_; } + + // Marks the connection with the specified id as having seen a message. + void MarkConnectionAsMessaged(mojo::ConnectionSpecificId connection_id) { + message_ids_.insert(connection_id); + } + + // Returns true if MarkConnectionAsMessaged(connection_id) was invoked. + bool DidMessageConnection(mojo::ConnectionSpecificId connection_id) const { + return message_ids_.count(connection_id) > 0; + } + + private: + ConnectionManager* connection_manager_; + const mojo::ConnectionSpecificId connection_id_; + const bool is_delete_view_; + + // See description of MarkConnectionAsMessaged/DidMessageConnection. + std::set<mojo::ConnectionSpecificId> message_ids_; + + DISALLOW_COPY_AND_ASSIGN(ScopedChange); + }; + + ConnectionManager(ConnectionManagerDelegate* delegate, + scoped_ptr<DisplayManager> display_manager, + mojo::WindowManagerInternal* wm_internal); + ~ConnectionManager() override; + + // Creates a new ServerView. The return value is owned by the caller, but must + // be destroyed before ConnectionManager. + ServerView* CreateServerView(const ViewId& id); + + // Returns the id for the next ViewManagerServiceImpl. + mojo::ConnectionSpecificId GetAndAdvanceNextConnectionId(); + + // Invoked when a ViewManagerServiceImpl's connection encounters an error. + void OnConnectionError(ClientConnection* connection); + + // See description of ViewManagerService::Embed() for details. This assumes + // |transport_view_id| is valid. + void EmbedAtView(mojo::ConnectionSpecificId creator_id, + const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + void EmbedAtView(mojo::ConnectionSpecificId creator_id, + const ViewId& view_id, + mojo::ViewManagerClientPtr client); + + // Returns the connection by id. + ViewManagerServiceImpl* GetConnection( + mojo::ConnectionSpecificId connection_id); + + // Returns the View identified by |id|. + ServerView* GetView(const ViewId& id); + + ServerView* root() { return root_.get(); } + DisplayManager* display_manager() { return display_manager_.get(); } + + bool IsProcessingChange() const { return current_change_ != NULL; } + + bool is_processing_delete_view() const { + return current_change_ && current_change_->is_delete_view(); + } + + // Invoked when a connection messages a client about the change. This is used + // to avoid sending ServerChangeIdAdvanced() unnecessarily. + void OnConnectionMessagedClient(mojo::ConnectionSpecificId id); + + // Returns true if OnConnectionMessagedClient() was invoked for id. + bool DidConnectionMessageClient(mojo::ConnectionSpecificId id) const; + + // Returns the ViewManagerServiceImpl that has |id| as a root. + ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) { + return const_cast<ViewManagerServiceImpl*>( + const_cast<const ConnectionManager*>(this)->GetConnectionWithRoot(id)); + } + const ViewManagerServiceImpl* GetConnectionWithRoot(const ViewId& id) const; + + mojo::WindowManagerInternal* wm_internal() { return wm_internal_; } + + void SetWindowManagerClientConnection( + scoped_ptr<ClientConnection> connection); + bool has_window_manager_client_connection() const { + return window_manager_client_connection_ != nullptr; + } + + mojo::ViewManagerClient* GetWindowManagerViewManagerClient(); + + // WindowManagerInternalClient implementation helper; see mojom for details. + bool CloneAndAnimate(const ViewId& view_id); + + // These functions trivially delegate to all ViewManagerServiceImpls, which in + // term notify their clients. + void ProcessViewDestroyed(ServerView* view); + void ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds); + void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics); + void ProcessWillChangeViewHierarchy(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent); + void ProcessViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent); + void ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + const mojo::OrderDirection direction); + void ProcessViewDeleted(const ViewId& view); + + private: + typedef std::map<mojo::ConnectionSpecificId, ClientConnection*> ConnectionMap; + + // Invoked when a connection is about to make a change. Subsequently followed + // by FinishChange() once the change is done. + // + // Changes should never nest, meaning each PrepareForChange() must be + // balanced with a call to FinishChange() with no PrepareForChange() + // in between. + void PrepareForChange(ScopedChange* change); + + // Balances a call to PrepareForChange(). + void FinishChange(); + + // Returns true if the specified connection originated the current change. + bool IsChangeSource(mojo::ConnectionSpecificId connection_id) const { + return current_change_ && current_change_->connection_id() == connection_id; + } + + // Adds |connection| to internal maps. + void AddConnection(ClientConnection* connection); + + // Callback from animation timer. + // TODO(sky): make this real (move to a different class). + void DoAnimation(); + + // Overridden from ServerViewDelegate: + void PrepareToDestroyView(ServerView* view) override; + void PrepareToChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void PrepareToChangeViewVisibility(ServerView* view) override; + void OnScheduleViewPaint(const ServerView* view) override; + + // Overridden from ServerViewObserver: + void OnViewDestroyed(ServerView* view) override; + void OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void OnViewHierarchyChanged(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void OnViewBoundsChanged(ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) override; + void OnViewReordered(ServerView* view, + ServerView* relative, + mojo::OrderDirection direction) override; + void OnWillChangeViewVisibility(ServerView* view) override; + void OnViewSharedPropertyChanged( + ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) override; + + // WindowManagerInternalClient: + void DispatchInputEventToView(mojo::Id transport_view_id, + mojo::EventPtr event) override; + void SetViewportSize(mojo::SizePtr size) override; + void CloneAndAnimate(mojo::Id transport_view_id) override; + + ConnectionManagerDelegate* delegate_; + + // The ClientConnection containing the ViewManagerService implementation + // provided to the initial connection (the WindowManager). + // NOTE: |window_manager_client_connection_| is also in |connection_map_|. + ClientConnection* window_manager_client_connection_; + + // ID to use for next ViewManagerServiceImpl. + mojo::ConnectionSpecificId next_connection_id_; + + // Set of ViewManagerServiceImpls. + ConnectionMap connection_map_; + + scoped_ptr<DisplayManager> display_manager_; + + scoped_ptr<ServerView> root_; + + mojo::WindowManagerInternal* wm_internal_; + + // If non-null we're processing a change. The ScopedChange is not owned by us + // (it's created on the stack by ViewManagerServiceImpl). + ScopedChange* current_change_; + + bool in_destructor_; + + // TODO(sky): nuke! Just a proof of concept until get real animation api. + base::RepeatingTimer<ConnectionManager> animation_timer_; + + AnimationRunner animation_runner_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionManager); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_H_ diff --git a/components/view_manager/connection_manager_delegate.h b/components/view_manager/connection_manager_delegate.h new file mode 100644 index 0000000..0d06f94 --- /dev/null +++ b/components/view_manager/connection_manager_delegate.h @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ + +#include <string> + +#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace mojo { +class ViewManagerService; +} + +namespace view_manager { + +class ClientConnection; +class ConnectionManager; +struct ViewId; + +class ConnectionManagerDelegate { + public: + virtual void OnLostConnectionToWindowManager() = 0; + + // Creates a ClientConnection in response to Embed() calls on the + // ConnectionManager. + virtual ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) = 0; + virtual ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) = 0; + + protected: + virtual ~ConnectionManagerDelegate() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_CONNECTION_MANAGER_DELEGATE_H_ diff --git a/components/view_manager/default_access_policy.cc b/components/view_manager/default_access_policy.cc new file mode 100644 index 0000000..ed0a6b6 --- /dev/null +++ b/components/view_manager/default_access_policy.cc @@ -0,0 +1,112 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/default_access_policy.h" + +#include "components/view_manager/access_policy_delegate.h" +#include "components/view_manager/server_view.h" + +namespace view_manager { + +DefaultAccessPolicy::DefaultAccessPolicy( + mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate) + : connection_id_(connection_id), delegate_(delegate) { +} + +DefaultAccessPolicy::~DefaultAccessPolicy() { +} + +bool DefaultAccessPolicy::CanRemoveViewFromParent( + const ServerView* view) const { + if (!WasCreatedByThisConnection(view)) + return false; // Can only unparent views we created. + + return delegate_->IsRootForAccessPolicy(view->parent()->id()) || + WasCreatedByThisConnection(view->parent()); +} + +bool DefaultAccessPolicy::CanAddView(const ServerView* parent, + const ServerView* child) const { + return WasCreatedByThisConnection(child) && + (delegate_->IsRootForAccessPolicy(parent->id()) || + (WasCreatedByThisConnection(parent) && + !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(parent))); +} + +bool DefaultAccessPolicy::CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const { + return WasCreatedByThisConnection(view) && + WasCreatedByThisConnection(relative_view); +} + +bool DefaultAccessPolicy::CanDeleteView(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanGetViewTree(const ServerView* view) const { + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanDescendIntoViewForViewTree( + const ServerView* view) const { + return (WasCreatedByThisConnection(view) && + !delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanEmbed(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanChangeViewVisibility( + const ServerView* view) const { + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanSetViewSurfaceId(const ServerView* view) const { + // Once a view embeds another app, the embedder app is no longer able to + // call SetViewSurfaceId() - this ability is transferred to the embedded app. + if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) + return false; + return WasCreatedByThisConnection(view) || + delegate_->IsRootForAccessPolicy(view->id()); +} + +bool DefaultAccessPolicy::CanSetViewBounds(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::CanSetViewProperties(const ServerView* view) const { + return WasCreatedByThisConnection(view); +} + +bool DefaultAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const { + if (!WasCreatedByThisConnection(view)) + return false; + + if (*new_parent && !WasCreatedByThisConnection(*new_parent) && + !delegate_->IsRootForAccessPolicy((*new_parent)->id())) { + *new_parent = NULL; + } + + if (*old_parent && !WasCreatedByThisConnection(*old_parent) && + !delegate_->IsRootForAccessPolicy((*old_parent)->id())) { + *old_parent = NULL; + } + return true; +} + +bool DefaultAccessPolicy::WasCreatedByThisConnection( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +} // namespace view_manager diff --git a/components/view_manager/default_access_policy.h b/components/view_manager/default_access_policy.h new file mode 100644 index 0000000..5c23118 --- /dev/null +++ b/components/view_manager/default_access_policy.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ +#define COMPONENTS_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ + +#include "base/basictypes.h" +#include "components/view_manager/access_policy.h" + +namespace view_manager { + +class AccessPolicyDelegate; + +// AccessPolicy for all connections, except the window manager. +class DefaultAccessPolicy : public AccessPolicy { + public: + DefaultAccessPolicy(mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate); + ~DefaultAccessPolicy() override; + + // AccessPolicy: + bool CanRemoveViewFromParent(const ServerView* view) const override; + bool CanAddView(const ServerView* parent, + const ServerView* child) const override; + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const override; + bool CanDeleteView(const ServerView* view) const override; + bool CanGetViewTree(const ServerView* view) const override; + bool CanDescendIntoViewForViewTree(const ServerView* view) const override; + bool CanEmbed(const ServerView* view) const override; + bool CanChangeViewVisibility(const ServerView* view) const override; + bool CanSetViewSurfaceId(const ServerView* view) const override; + bool CanSetViewBounds(const ServerView* view) const override; + bool CanSetViewProperties(const ServerView* view) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const override; + + private: + bool WasCreatedByThisConnection(const ServerView* view) const; + + const mojo::ConnectionSpecificId connection_id_; + AccessPolicyDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(DefaultAccessPolicy); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_DEFAULT_ACCESS_POLICY_H_ diff --git a/components/view_manager/display_manager.cc b/components/view_manager/display_manager.cc new file mode 100644 index 0000000..52825ed --- /dev/null +++ b/components/view_manager/display_manager.cc @@ -0,0 +1,187 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/display_manager.h" + +#include "base/numerics/safe_conversions.h" +#include "components/view_manager/connection_manager.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/view_coordinate_conversions.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" +#include "third_party/mojo_services/src/gpu/public/interfaces/gpu.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/cpp/surfaces_utils.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/quads.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surfaces.mojom.h" + +using mojo::Rect; +using mojo::Size; + +namespace view_manager { +namespace { + +void DrawViewTree(mojo::Pass* pass, + const ServerView* view, + const gfx::Vector2d& parent_to_root_origin_offset, + float opacity) { + if (!view->visible()) + return; + + const gfx::Rect absolute_bounds = + view->bounds() + parent_to_root_origin_offset; + std::vector<const ServerView*> children(view->GetChildren()); + const float combined_opacity = opacity * view->opacity(); + for (std::vector<const ServerView*>::reverse_iterator it = children.rbegin(); + it != children.rend(); + ++it) { + DrawViewTree(pass, *it, absolute_bounds.OffsetFromOrigin(), + combined_opacity); + } + + cc::SurfaceId node_id = view->surface_id(); + + auto surface_quad_state = mojo::SurfaceQuadState::New(); + surface_quad_state->surface = mojo::SurfaceId::From(node_id); + + gfx::Transform node_transform; + node_transform.Translate(absolute_bounds.x(), absolute_bounds.y()); + + const gfx::Rect bounds_at_origin(view->bounds().size()); + auto surface_quad = mojo::Quad::New(); + surface_quad->material = mojo::Material::MATERIAL_SURFACE_CONTENT; + surface_quad->rect = Rect::From(bounds_at_origin); + surface_quad->opaque_rect = Rect::From(bounds_at_origin); + surface_quad->visible_rect = Rect::From(bounds_at_origin); + surface_quad->needs_blending = true; + surface_quad->shared_quad_state_index = + base::saturated_cast<int32_t>(pass->shared_quad_states.size()); + surface_quad->surface_quad_state = surface_quad_state.Pass(); + + auto sqs = CreateDefaultSQS(*Size::From(view->bounds().size())); + sqs->blend_mode = mojo::SK_XFERMODE_kSrcOver_Mode; + sqs->opacity = combined_opacity; + sqs->content_to_target_transform = mojo::Transform::From(node_transform); + + pass->quads.push_back(surface_quad.Pass()); + pass->shared_quad_states.push_back(sqs.Pass()); +} + +} // namespace + +DefaultDisplayManager::DefaultDisplayManager( + mojo::ApplicationImpl* app_impl, + mojo::ApplicationConnection* app_connection, + const mojo::Callback<void()>& native_viewport_closed_callback) + : app_impl_(app_impl), + app_connection_(app_connection), + connection_manager_(nullptr), + draw_timer_(false, false), + frame_pending_(false), + native_viewport_closed_callback_(native_viewport_closed_callback), + weak_factory_(this) { + metrics_.size = mojo::Size::New(); + metrics_.size->width = 800; + metrics_.size->height = 600; +} + +void DefaultDisplayManager::Init(ConnectionManager* connection_manager) { + connection_manager_ = connection_manager; + app_impl_->ConnectToService("mojo:native_viewport_service", + &native_viewport_); + native_viewport_.set_error_handler(this); + native_viewport_->Create(metrics_.size->Clone(), + base::Bind(&DefaultDisplayManager::OnMetricsChanged, + weak_factory_.GetWeakPtr())); + native_viewport_->Show(); + + mojo::ContextProviderPtr context_provider; + native_viewport_->GetContextProvider(GetProxy(&context_provider)); + mojo::DisplayFactoryPtr display_factory; + app_impl_->ConnectToService("mojo:surfaces_service", &display_factory); + display_factory->Create(context_provider.Pass(), + nullptr, // returner - we never submit resources. + GetProxy(&display_)); + + mojo::NativeViewportEventDispatcherPtr event_dispatcher; + app_connection_->ConnectToService(&event_dispatcher); + native_viewport_->SetEventDispatcher(event_dispatcher.Pass()); +} + +DefaultDisplayManager::~DefaultDisplayManager() { +} + +void DefaultDisplayManager::SchedulePaint(const ServerView* view, + const gfx::Rect& bounds) { + if (!view->IsDrawn(connection_manager_->root())) + return; + const gfx::Rect root_relative_rect = + ConvertRectBetweenViews(view, connection_manager_->root(), bounds); + if (root_relative_rect.IsEmpty()) + return; + dirty_rect_.Union(root_relative_rect); + WantToDraw(); +} + +void DefaultDisplayManager::SetViewportSize(const gfx::Size& size) { + native_viewport_->SetSize(Size::From(size)); +} + +const mojo::ViewportMetrics& DefaultDisplayManager::GetViewportMetrics() { + return metrics_; +} + +void DefaultDisplayManager::Draw() { + Rect rect; + rect.width = metrics_.size->width; + rect.height = metrics_.size->height; + auto pass = CreateDefaultPass(1, rect); + pass->damage_rect = Rect::From(dirty_rect_); + + DrawViewTree(pass.get(), connection_manager_->root(), gfx::Vector2d(), 1.0f); + + auto frame = mojo::Frame::New(); + frame->passes.push_back(pass.Pass()); + frame->resources.resize(0u); + frame_pending_ = true; + display_->SubmitFrame( + frame.Pass(), + base::Bind(&DefaultDisplayManager::DidDraw, base::Unretained(this))); + dirty_rect_ = gfx::Rect(); +} + +void DefaultDisplayManager::DidDraw() { + frame_pending_ = false; + if (!dirty_rect_.IsEmpty()) + WantToDraw(); +} + +void DefaultDisplayManager::WantToDraw() { + if (draw_timer_.IsRunning() || frame_pending_) + return; + + draw_timer_.Start( + FROM_HERE, base::TimeDelta(), + base::Bind(&DefaultDisplayManager::Draw, base::Unretained(this))); +} + +void DefaultDisplayManager::OnMetricsChanged(mojo::ViewportMetricsPtr metrics) { + metrics_.size = metrics->size.Clone(); + metrics_.device_pixel_ratio = metrics->device_pixel_ratio; + gfx::Rect bounds(metrics_.size.To<gfx::Size>()); + connection_manager_->root()->SetBounds(bounds); + connection_manager_->ProcessViewportMetricsChanged(metrics_, *metrics); + native_viewport_->RequestMetrics(base::Bind( + &DefaultDisplayManager::OnMetricsChanged, weak_factory_.GetWeakPtr())); +} + +void DefaultDisplayManager::OnConnectionError() { + // This is called when the native_viewport is torn down before + // ~DefaultDisplayManager may be called. + native_viewport_closed_callback_.Run(); +} + +} // namespace view_manager diff --git a/components/view_manager/display_manager.h b/components/view_manager/display_manager.h new file mode 100644 index 0000000..92026eb --- /dev/null +++ b/components/view_manager/display_manager.h @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_DISPLAY_MANAGER_H_ +#define COMPONENTS_VIEW_MANAGER_DISPLAY_MANAGER_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/timer/timer.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/callback.h" +#include "third_party/mojo_services/src/native_viewport/public/interfaces/native_viewport.mojom.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/display.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "ui/gfx/geometry/rect.h" + +namespace cc { +class SurfaceIdAllocator; +} + +namespace mojo { +class ApplicationConnection; +class ApplicationImpl; +} + +namespace view_manager { + +class ConnectionManager; +class ServerView; + +// DisplayManager is used to connect the root ServerView to a display. +class DisplayManager { + public: + virtual ~DisplayManager() {} + + virtual void Init(ConnectionManager* connection_manager) = 0; + + // Schedules a paint for the specified region in the coordinates of |view|. + virtual void SchedulePaint(const ServerView* view, + const gfx::Rect& bounds) = 0; + + virtual void SetViewportSize(const gfx::Size& size) = 0; + + virtual const mojo::ViewportMetrics& GetViewportMetrics() = 0; +}; + +// DisplayManager implementation that connects to the services necessary to +// actually display. +class DefaultDisplayManager : public DisplayManager, + public mojo::ErrorHandler { + public: + DefaultDisplayManager( + mojo::ApplicationImpl* app_impl, + mojo::ApplicationConnection* app_connection, + const mojo::Callback<void()>& native_viewport_closed_callback); + ~DefaultDisplayManager() override; + + // DisplayManager: + void Init(ConnectionManager* connection_manager) override; + void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override; + void SetViewportSize(const gfx::Size& size) override; + const mojo::ViewportMetrics& GetViewportMetrics() override; + + private: + void WantToDraw(); + void Draw(); + void DidDraw(); + + void OnMetricsChanged(mojo::ViewportMetricsPtr metrics); + + // ErrorHandler: + void OnConnectionError() override; + + mojo::ApplicationImpl* app_impl_; + mojo::ApplicationConnection* app_connection_; + ConnectionManager* connection_manager_; + + mojo::ViewportMetrics metrics_; + gfx::Rect dirty_rect_; + base::Timer draw_timer_; + bool frame_pending_; + + mojo::DisplayPtr display_; + mojo::NativeViewportPtr native_viewport_; + mojo::Callback<void()> native_viewport_closed_callback_; + base::WeakPtrFactory<DefaultDisplayManager> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DefaultDisplayManager); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_DISPLAY_MANAGER_H_ diff --git a/components/view_manager/focus_controller.cc b/components/view_manager/focus_controller.cc new file mode 100644 index 0000000..84ee559 --- /dev/null +++ b/components/view_manager/focus_controller.cc @@ -0,0 +1,54 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/focus_controller.h" + +#include "components/view_manager/focus_controller_delegate.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/server_view_drawn_tracker.h" + +namespace view_manager { + +FocusController::FocusController(FocusControllerDelegate* delegate, + ServerView* root) + : delegate_(delegate), root_(root) { +} + +FocusController::~FocusController() { +} + +void FocusController::SetFocusedView(ServerView* view) { + if (GetFocusedView() == view) + return; + + SetFocusedViewImpl(view, CHANGE_SOURCE_EXPLICIT); +} + +ServerView* FocusController::GetFocusedView() { + return drawn_tracker_ ? drawn_tracker_->view() : nullptr; +} + +void FocusController::SetFocusedViewImpl(ServerView* view, + ChangeSource change_source) { + ServerView* old = GetFocusedView(); + + DCHECK(!view || view->IsDrawn(root_)); + + if (view) + drawn_tracker_.reset(new ServerViewDrawnTracker(root_, view, this)); + else + drawn_tracker_.reset(); + + if (change_source == CHANGE_SOURCE_DRAWN_STATE_CHANGED) + delegate_->OnFocusChanged(old, view); +} + +void FocusController::OnDrawnStateChanged(ServerView* ancestor, + ServerView* view, + bool is_drawn) { + DCHECK(!is_drawn); // We only observe when drawn. + SetFocusedViewImpl(ancestor, CHANGE_SOURCE_DRAWN_STATE_CHANGED); +} + +} // namespace view_manager diff --git a/components/view_manager/focus_controller.h b/components/view_manager/focus_controller.h new file mode 100644 index 0000000..1ad5fe5 --- /dev/null +++ b/components/view_manager/focus_controller.h @@ -0,0 +1,53 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_H_ +#define COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_H_ + +#include "base/memory/scoped_ptr.h" +#include "components/view_manager/server_view_drawn_tracker_observer.h" + +namespace view_manager { + +class FocusControllerDelegate; +class ServerView; +class ServerViewDrawnTracker; + +// Tracks a focused view. Focus is moved to another view when the drawn state +// of the focused view changes and the delegate is notified. +class FocusController : public ServerViewDrawnTrackerObserver { + public: + FocusController(FocusControllerDelegate* delegate, ServerView* root); + ~FocusController() override; + + // Sets the focused view. Does nothing if |view| is currently focused. This + // does not notify the delegate. + void SetFocusedView(ServerView* view); + ServerView* GetFocusedView(); + + private: + // Describes the source of the change. + enum ChangeSource { + CHANGE_SOURCE_EXPLICIT, + CHANGE_SOURCE_DRAWN_STATE_CHANGED, + }; + + // Implementation of SetFocusedView(). + void SetFocusedViewImpl(ServerView* view, ChangeSource change_source); + + // ServerViewDrawnTrackerObserver: + void OnDrawnStateChanged(ServerView* ancestor, + ServerView* view, + bool is_drawn) override; + + FocusControllerDelegate* delegate_; + ServerView* root_; + scoped_ptr<ServerViewDrawnTracker> drawn_tracker_; + + DISALLOW_COPY_AND_ASSIGN(FocusController); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_H_ diff --git a/components/view_manager/focus_controller_delegate.h b/components/view_manager/focus_controller_delegate.h new file mode 100644 index 0000000..ac2eeab --- /dev/null +++ b/components/view_manager/focus_controller_delegate.h @@ -0,0 +1,23 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_DELEGATE_H_ + +namespace view_manager { + +class ServerView; + +class FocusControllerDelegate { + public: + virtual void OnFocusChanged(ServerView* old_focused_view, + ServerView* new_focused_view) = 0; + + protected: + ~FocusControllerDelegate() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_FOCUS_CONTROLLER_DELEGATE_H_ diff --git a/components/view_manager/focus_controller_unittest.cc b/components/view_manager/focus_controller_unittest.cc new file mode 100644 index 0000000..95ea9c9 --- /dev/null +++ b/components/view_manager/focus_controller_unittest.cc @@ -0,0 +1,106 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/focus_controller.h" + +#include "components/view_manager/focus_controller_delegate.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace view_manager { +namespace { + +class TestFocusControllerDelegate : public FocusControllerDelegate { + public: + TestFocusControllerDelegate() + : change_count_(0u), + old_focused_view_(nullptr), + new_focused_view_(nullptr) {} + + void ClearAll() { + change_count_ = 0u; + old_focused_view_ = nullptr; + new_focused_view_ = nullptr; + } + size_t change_count() const { return change_count_; } + ServerView* old_focused_view() { return old_focused_view_; } + ServerView* new_focused_view() { return new_focused_view_; } + + private: + // FocusControllerDelegate: + void OnFocusChanged(ServerView* old_focused_view, + ServerView* new_focused_view) override { + change_count_++; + old_focused_view_ = old_focused_view; + new_focused_view_ = new_focused_view; + } + + size_t change_count_; + ServerView* old_focused_view_; + ServerView* new_focused_view_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusControllerDelegate); +}; + +} // namespace + +TEST(FocusControllerTest, Basic) { + TestServerViewDelegate server_view_delegate; + ServerView root(&server_view_delegate, ViewId()); + root.SetVisible(true); + ServerView child(&server_view_delegate, ViewId()); + child.SetVisible(true); + root.Add(&child); + ServerView child_child(&server_view_delegate, ViewId()); + child_child.SetVisible(true); + child.Add(&child_child); + + TestFocusControllerDelegate focus_delegate; + FocusController focus_controller(&focus_delegate, &root); + + focus_controller.SetFocusedView(&child_child); + EXPECT_EQ(0u, focus_delegate.change_count()); + + // Remove the ancestor of the focused view, focus should go to the |root|. + root.Remove(&child); + EXPECT_EQ(1u, focus_delegate.change_count()); + EXPECT_EQ(&root, focus_delegate.new_focused_view()); + EXPECT_EQ(&child_child, focus_delegate.old_focused_view()); + focus_delegate.ClearAll(); + + // Make the focused view invisible. Focus is lost in this case (as no one + // to give focus to). + root.SetVisible(false); + EXPECT_EQ(1u, focus_delegate.change_count()); + EXPECT_EQ(nullptr, focus_delegate.new_focused_view()); + EXPECT_EQ(&root, focus_delegate.old_focused_view()); + focus_delegate.ClearAll(); + + // Go back to initial state and focus |child_child|. + root.SetVisible(true); + root.Add(&child); + focus_controller.SetFocusedView(&child_child); + EXPECT_EQ(0u, focus_delegate.change_count()); + + // Hide the focused view, focus should go to parent. + child_child.SetVisible(false); + EXPECT_EQ(1u, focus_delegate.change_count()); + EXPECT_EQ(&child, focus_delegate.new_focused_view()); + EXPECT_EQ(&child_child, focus_delegate.old_focused_view()); + focus_delegate.ClearAll(); + + child_child.SetVisible(true); + focus_controller.SetFocusedView(&child_child); + EXPECT_EQ(0u, focus_delegate.change_count()); + + // Hide the parent of the focused view. + child.SetVisible(false); + EXPECT_EQ(1u, focus_delegate.change_count()); + EXPECT_EQ(&root, focus_delegate.new_focused_view()); + EXPECT_EQ(&child_child, focus_delegate.old_focused_view()); + focus_delegate.ClearAll(); +} + +} // namespace view_manager diff --git a/components/view_manager/gesture_manager.cc b/components/view_manager/gesture_manager.cc new file mode 100644 index 0000000..399ee33 --- /dev/null +++ b/components/view_manager/gesture_manager.cc @@ -0,0 +1,701 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/gesture_manager.h" + +#include <algorithm> + +#include "components/view_manager/gesture_manager_delegate.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/view_coordinate_conversions.h" +#include "components/view_manager/view_locator.h" +#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/keys.h" +#include "ui/gfx/geometry/point_f.h" + +namespace view_manager { + +using Views = std::vector<const ServerView*>; + +namespace { + +GestureManager::GestureAndConnectionId MakeGestureAndConnectionId( + const ServerView* view, + uint32_t gesture_id) { + return (static_cast<GestureManager::GestureAndConnectionId>( + view->id().connection_id) + << 32) | + gesture_id; +} + +// Returns the views (deepest first) that should receive touch events. This only +// returns one view per connection. If multiple views from the same connection +// are interested in touch events the shallowest view is returned. +Views GetTouchTargets(const ServerView* deepest) { + Views result; + const ServerView* view = deepest; + while (view) { + if (view->properties().count(mojo::kViewManagerKeyWantsTouchEvents)) { + if (!result.empty() && + result.back()->id().connection_id == view->id().connection_id) { + result.pop_back(); + } + result.push_back(view); + } + view = view->parent(); + } + // TODO(sky): I'm doing this until things are converted. Seems as though we + // shouldn't do this long term. + if (result.empty()) + result.push_back(deepest); + return result; +} + +mojo::EventPtr CloneEventForView(const mojo::Event& event, + const ServerView* view) { + mojo::EventPtr result(event.Clone()); + const gfx::PointF location(event.pointer_data->x, event.pointer_data->y); + const gfx::PointF target_location( + ConvertPointFBetweenViews(view->GetRoot(), view, location)); + result->pointer_data->x = target_location.x(); + result->pointer_data->y = target_location.y(); + return result.Pass(); +} + +} // namespace + +// GestureStateChange ---------------------------------------------------------- + +GestureStateChange::GestureStateChange() + : chosen_gesture(GestureManager::kInvalidGestureId) { +} + +GestureStateChange::~GestureStateChange() { +} + +// ViewIterator ---------------------------------------------------------------- + +// Used to iterate over a set of views. +class ViewIterator { + public: + explicit ViewIterator(const Views& views) + : views_(views), current_(views_.begin()) {} + + // Advances to the next view. Returns true if there are no more views (at + // the end). + bool advance() { return ++current_ != views_.end(); } + + bool at_end() const { return current_ == views_.end(); } + + bool empty() const { return views_.empty(); } + + const ServerView* current() const { return *current_; } + + void reset_to_beginning() { current_ = views_.begin(); } + + void remove(const ServerView* view) { + Views::iterator iter = std::find(views_.begin(), views_.end(), view); + DCHECK(iter != views_.end()); + if (iter == current_) { + current_ = views_.erase(current_); + } else if (!at_end()) { + size_t index = current_ - views_.begin(); + if (current_ > iter) + index--; + views_.erase(iter); + current_ = views_.begin() + index; + } else { + views_.erase(iter); + current_ = views_.end(); + } + } + + bool contains(const ServerView* view) const { + return std::find(views_.begin(), views_.end(), view) != views_.end(); + } + + private: + Views views_; + Views::iterator current_; + + DISALLOW_COPY_AND_ASSIGN(ViewIterator); +}; + +// PointerAndView -------------------------------------------------------------- + +struct GestureManager::PointerAndView { + PointerAndView(); + PointerAndView(Pointer* pointer, const ServerView* view); + + // Compares two PointerAndView instances based on pointer id, then view id. + // This is really only interesting for unit tests so that they get a known + // order of events. + bool operator<(const PointerAndView& other) const; + + Pointer* pointer; + const ServerView* view; +}; + +// Gesture --------------------------------------------------------------------- + +// Gesture maintains the set of pointers and views it is attached to. +class GestureManager::Gesture { + public: + enum State { STATE_INITIAL, STATE_CANCELED, STATE_CHOSEN }; + + explicit Gesture(uint32_t id); + ~Gesture(); + + uint32_t id() const { return id_; } + + void Attach(Pointer* pointer, const ServerView* view); + void Detach(Pointer* pointer, const ServerView* view); + + void set_state(State state) { state_ = state; } + State state() const { return state_; } + + const std::set<PointerAndView>& pointers_and_views() const { + return pointers_and_views_; + } + + private: + const uint32_t id_; + State state_; + std::set<PointerAndView> pointers_and_views_; + + DISALLOW_COPY_AND_ASSIGN(Gesture); +}; + +GestureManager::Gesture::Gesture(uint32_t id) : id_(id), state_(STATE_INITIAL) { +} + +GestureManager::Gesture::~Gesture() { +} + +void GestureManager::Gesture::Attach(Pointer* pointer, const ServerView* view) { + pointers_and_views_.insert(PointerAndView(pointer, view)); +} + +void GestureManager::Gesture::Detach(Pointer* pointer, const ServerView* view) { + pointers_and_views_.erase(PointerAndView(pointer, view)); +} + +// Pointer --------------------------------------------------------------------- + +// Pointer manages the state associated with a particular pointer from the time +// of the POINTER_DOWN to the time of the POINTER_UP (or POINTER_CANCEL). This +// state includes a mapping from view to the set of gestures the view is +// interested in. It also manages choosing gestures at the appropriate point as +// well as which view to dispatch to and the events to dispatch. +// See description in GestureManager for more. +class GestureManager::Pointer { + public: + Pointer(GestureManager* gesture_manager, + int32_t pointer_id, + const mojo::Event& event, + const Views& views); + ~Pointer(); + + int32_t pointer_id() const { return pointer_id_; } + bool was_chosen_or_canceled() const { return was_chosen_or_canceled_; } + + // Sets the set of gestures for this pointer. + void SetGestures(const ServerView* view, + uint32_t chosen_gesture_id, + const std::set<uint32_t>& possible_gesture_ids, + const std::set<uint32_t>& canceled_ids); + + // Called when a Gesture we contain has been canceled. + void GestureCanceled(Gesture* gesture); + + // Called when a Gesture we contain has been chosen. + void GestureChosen(Gesture* gesture, const ServerView* view); + + // Process a move or up event. This may delay processing if we're waiting for + // previous results. + void ProcessEvent(const mojo::Event& event); + + private: + // Corresponds to the type of event we're dispatching. + enum Phase { + // We're dispatching the initial down. + PHASE_DOWN, + + // We're dispatching a move. + PHASE_MOVE, + }; + + // Sends the event for the current phase to the delegate. + void ForwardCurrentEvent(); + + // Moves |pending_event_| to |current_event_| and notifies the delegate. + void MovePendingToCurrentAndProcess(); + + // If |was_chosen_or_canceled_| is false and there is only one possible + // gesture and it is in the initial state, choose it. Otherwise do nothing. + void ChooseGestureIfPossible(); + + bool ScheduleDeleteIfNecessary(); + + GestureManager* gesture_manager_; + const int32_t pointer_id_; + Phase phase_; + + // Used to iterate over the set of views that potentially have gestures. + ViewIterator view_iter_; + + // Maps from the view to the set of possible gestures for the view. + std::map<const ServerView*, std::set<Gesture*>> view_to_gestures_; + + Gesture* chosen_gesture_; + + bool was_chosen_or_canceled_; + + // The event we're processing. When initially created this is the supplied + // down event. When in PHASE_MOVE this is a move event. + mojo::EventPtr current_event_; + + // Incoming events (move or up) are added here while while waiting for + // responses. + mojo::EventPtr pending_event_; + + DISALLOW_COPY_AND_ASSIGN(Pointer); +}; + +GestureManager::Pointer::Pointer(GestureManager* gesture_manager, + int32_t pointer_id, + const mojo::Event& event, + const Views& views) + : gesture_manager_(gesture_manager), + pointer_id_(pointer_id), + phase_(PHASE_DOWN), + view_iter_(views), + chosen_gesture_(nullptr), + was_chosen_or_canceled_(false), + current_event_(event.Clone()) { + ForwardCurrentEvent(); +} + +GestureManager::Pointer::~Pointer() { + for (auto& pair : view_to_gestures_) { + for (Gesture* gesture : pair.second) + gesture_manager_->DetachGesture(gesture, this, pair.first); + } +} + +void GestureManager::Pointer::SetGestures( + const ServerView* view, + uint32_t chosen_gesture_id, + const std::set<uint32_t>& possible_gesture_ids, + const std::set<uint32_t>& canceled_gesture_ids) { + if (!view_iter_.contains(view)) { + // We don't know about this view. + return; + } + + // True if this is the view we're waiting for a response from. + const bool was_waiting_on = + (!was_chosen_or_canceled_ && + (!view_iter_.at_end() && view_iter_.current() == view)); + + if (possible_gesture_ids.empty()) { + // The view no longer wants to be notified. + for (Gesture* gesture : view_to_gestures_[view]) + gesture_manager_->DetachGesture(gesture, this, view); + view_to_gestures_.erase(view); + view_iter_.remove(view); + if (view_iter_.empty()) { + gesture_manager_->PointerHasNoGestures(this); + // WARNING: we've been deleted. + return; + } + } else { + if (was_waiting_on) + view_iter_.advance(); + + Gesture* to_choose = nullptr; + std::set<Gesture*> gestures; + for (auto gesture_id : possible_gesture_ids) { + Gesture* gesture = gesture_manager_->GetGesture(view, gesture_id); + gesture_manager_->AttachGesture(gesture, this, view); + gestures.insert(gesture); + if (gesture->state() == Gesture::STATE_CHOSEN && + !was_chosen_or_canceled_) { + to_choose = gesture; + } + } + + // Give preference to the supplied |chosen_gesture_id|. + if (!was_chosen_or_canceled_ && chosen_gesture_id != kInvalidGestureId) { + Gesture* gesture = gesture_manager_->GetGesture(view, chosen_gesture_id); + if (gesture->state() != Gesture::STATE_CANCELED) + to_choose = gesture; + + DCHECK(possible_gesture_ids.count(gesture->id())); + gesture_manager_->AttachGesture(gesture, this, view); + } + + // Tell GestureManager of any Gestures we're no longer associated with. + std::set<Gesture*> removed_gestures; + std::set_difference( + view_to_gestures_[view].begin(), view_to_gestures_[view].end(), + gestures.begin(), gestures.end(), + std::inserter(removed_gestures, removed_gestures.begin())); + view_to_gestures_[view].swap(gestures); + for (Gesture* gesture : removed_gestures) + gesture_manager_->DetachGesture(gesture, this, view); + + if (chosen_gesture_ && removed_gestures.count(chosen_gesture_)) + chosen_gesture_ = nullptr; + + if (to_choose) { + gesture_manager_->ChooseGesture(to_choose, this, view); + } else { + // Choosing a gesture implicitly cancels all other gestures. If we didn't + // choose a gesture we need to update the state of any newly added + // gestures. + for (Gesture* gesture : gestures) { + if (gesture != chosen_gesture_ && + (was_chosen_or_canceled_ || + canceled_gesture_ids.count(gesture->id()))) { + gesture_manager_->CancelGesture(gesture, this, view); + } + } + } + } + + if (was_waiting_on && !was_chosen_or_canceled_) { + if (view_iter_.at_end()) { + if (ScheduleDeleteIfNecessary()) + return; + // If we're got all the responses, check if there is only one valid + // gesture. + ChooseGestureIfPossible(); + if (!was_chosen_or_canceled_) { + // There is more than one valid gesture and none chosen. Continue + // synchronous dispatch of move events. + phase_ = PHASE_MOVE; + MovePendingToCurrentAndProcess(); + } + } else { + ForwardCurrentEvent(); + } + } else if (!was_chosen_or_canceled_ && phase_ != PHASE_DOWN) { + // We weren't waiting on this view but we're in the move phase. The set of + // gestures may have changed such that we only have one valid gesture. Check + // for that. + ChooseGestureIfPossible(); + } +} + +void GestureManager::Pointer::GestureCanceled(Gesture* gesture) { + if (was_chosen_or_canceled_ && gesture == chosen_gesture_) { + chosen_gesture_ = nullptr; + // No need to cancel other gestures as they are already canceled by virtue + // of us having been chosen. + } else if (!was_chosen_or_canceled_ && phase_ == PHASE_MOVE) { + ChooseGestureIfPossible(); + } +} + +void GestureManager::Pointer::GestureChosen(Gesture* gesture, + const ServerView* view) { + DCHECK(!was_chosen_or_canceled_); + was_chosen_or_canceled_ = true; + chosen_gesture_ = gesture; + for (auto& pair : view_to_gestures_) { + for (Gesture* g : pair.second) { + if (g != gesture) + gesture_manager_->CancelGesture(g, this, pair.first); + } + } + + while (!view_iter_.at_end()) { + ForwardCurrentEvent(); + view_iter_.advance(); + } + if (ScheduleDeleteIfNecessary()) + return; + phase_ = PHASE_MOVE; + MovePendingToCurrentAndProcess(); +} + +void GestureManager::Pointer::ProcessEvent(const mojo::Event& event) { + // |event| is either a move or up. In either case it has the new coordinates + // and is safe to replace the existing one with. + pending_event_ = event.Clone(); + if (was_chosen_or_canceled_) { + MovePendingToCurrentAndProcess(); + } else if (view_iter_.at_end()) { + view_iter_.reset_to_beginning(); + MovePendingToCurrentAndProcess(); + } + // The else case is we are waiting on a response from a view before we + // continue dispatching. When we get the response for the last view in the + // stack we'll move pending to current and start dispatching it. +} + +void GestureManager::Pointer::ForwardCurrentEvent() { + DCHECK(!view_iter_.at_end()); + const ServerView* view = view_iter_.current(); + gesture_manager_->delegate_->ProcessEvent( + view, CloneEventForView(*current_event_, view), was_chosen_or_canceled_); +} + +void GestureManager::Pointer::MovePendingToCurrentAndProcess() { + if (!pending_event_.get()) { + current_event_ = nullptr; + return; + } + current_event_ = pending_event_.Pass(); + view_iter_.reset_to_beginning(); + ForwardCurrentEvent(); + if (was_chosen_or_canceled_) { + while (view_iter_.advance()) + ForwardCurrentEvent(); + if (ScheduleDeleteIfNecessary()) + return; + current_event_ = nullptr; + } +} + +void GestureManager::Pointer::ChooseGestureIfPossible() { + if (was_chosen_or_canceled_) + return; + + Gesture* gesture_to_choose = nullptr; + const ServerView* view = nullptr; + for (auto& pair : view_to_gestures_) { + for (Gesture* gesture : pair.second) { + if (gesture->state() == Gesture::STATE_INITIAL) { + if (gesture_to_choose) + return; + view = pair.first; + gesture_to_choose = gesture; + } + } + } + if (view) + gesture_manager_->ChooseGesture(gesture_to_choose, this, view); +} + +bool GestureManager::Pointer::ScheduleDeleteIfNecessary() { + if (current_event_ && + (current_event_->action == mojo::EVENT_TYPE_POINTER_UP || + current_event_->action == mojo::EVENT_TYPE_POINTER_CANCEL)) { + gesture_manager_->ScheduleDelete(this); + return true; + } + return false; +} + +// ScheduledDeleteProcessor --------------------------------------------------- + +class GestureManager::ScheduledDeleteProcessor { + public: + explicit ScheduledDeleteProcessor(GestureManager* manager) + : manager_(manager) {} + + ~ScheduledDeleteProcessor() { manager_->pointers_to_delete_.clear(); } + + private: + GestureManager* manager_; + + DISALLOW_COPY_AND_ASSIGN(ScheduledDeleteProcessor); +}; + +// PointerAndView -------------------------------------------------------------- + +GestureManager::PointerAndView::PointerAndView() + : pointer(nullptr), view(nullptr) { +} + +GestureManager::PointerAndView::PointerAndView(Pointer* pointer, + const ServerView* view) + : pointer(pointer), view(view) { +} + +bool GestureManager::PointerAndView::operator<( + const PointerAndView& other) const { + if (other.pointer->pointer_id() == pointer->pointer_id()) + return view->id().connection_id < other.view->id().connection_id; + return pointer->pointer_id() < other.pointer->pointer_id(); +} + +// GestureManager -------------------------------------------------------------- + +// static +const uint32_t GestureManager::kInvalidGestureId = 0u; + +GestureManager::GestureManager(GestureManagerDelegate* delegate, + const ServerView* root) + : delegate_(delegate), root_view_(root) { +} + +GestureManager::~GestureManager() { + // Explicitly delete the pointers first as this may result in calling back to + // us to cleanup and delete gestures. + active_pointers_.clear(); +} + +bool GestureManager::ProcessEvent(const mojo::Event& event) { + if (!event.pointer_data) + return false; + + ScheduledDeleteProcessor delete_processor(this); + const gfx::Point location(static_cast<int>(event.pointer_data->x), + static_cast<int>(event.pointer_data->y)); + switch (event.action) { + case mojo::EVENT_TYPE_POINTER_DOWN: { + if (GetPointerById(event.pointer_data->pointer_id)) { + DVLOG(1) << "received more than one down for a pointer without a " + << "corresponding up, id=" << event.pointer_data->pointer_id; + NOTREACHED(); + return true; + } + + const ServerView* deepest = FindDeepestVisibleView(root_view_, location); + Views targets(GetTouchTargets(deepest)); + if (targets.empty()) + return true; + + scoped_ptr<Pointer> pointer( + new Pointer(this, event.pointer_data->pointer_id, event, targets)); + active_pointers_.push_back(pointer.Pass()); + return true; + } + + case mojo::EVENT_TYPE_POINTER_CANCEL: + case mojo::EVENT_TYPE_POINTER_MOVE: + case mojo::EVENT_TYPE_POINTER_UP: { + Pointer* pointer = GetPointerById(event.pointer_data->pointer_id); + // We delete a pointer when it has no gestures, so it's possible to get + // here with no gestures. Additionally there is no need to explicitly + // delete |pointer| as it'll tell us when it's ready to be deleted. + if (pointer) + pointer->ProcessEvent(event); + return true; + } + + default: + break; + } + return false; +} + +scoped_ptr<ChangeMap> GestureManager::SetGestures( + const ServerView* view, + int32_t pointer_id, + uint32_t chosen_gesture_id, + const std::set<uint32_t>& possible_gesture_ids, + const std::set<uint32_t>& canceled_gesture_ids) { + // TODO(sky): caller should validate ids and make sure possible contains + // canceled and chosen. + DCHECK(!canceled_gesture_ids.count(kInvalidGestureId)); + DCHECK(!possible_gesture_ids.count(kInvalidGestureId)); + DCHECK(chosen_gesture_id == kInvalidGestureId || + possible_gesture_ids.count(chosen_gesture_id)); + DCHECK(chosen_gesture_id == kInvalidGestureId || + !canceled_gesture_ids.count(chosen_gesture_id)); + ScheduledDeleteProcessor delete_processor(this); + Pointer* pointer = GetPointerById(pointer_id); + current_change_.reset(new ChangeMap); + if (pointer) { + pointer->SetGestures(view, chosen_gesture_id, possible_gesture_ids, + canceled_gesture_ids); + } + return current_change_.Pass(); +} + +GestureManager::Pointer* GestureManager::GetPointerById(int32_t pointer_id) { + for (Pointer* pointer : active_pointers_) { + if (pointer->pointer_id() == pointer_id) + return pointer; + } + return nullptr; +} + +void GestureManager::PointerHasNoGestures(Pointer* pointer) { + auto iter = + std::find(active_pointers_.begin(), active_pointers_.end(), pointer); + CHECK(iter != active_pointers_.end()); + active_pointers_.erase(iter); +} + +GestureManager::Gesture* GestureManager::GetGesture(const ServerView* view, + uint32_t gesture_id) { + GestureAndConnectionId gesture_and_connection_id = + MakeGestureAndConnectionId(view, gesture_id); + Gesture* gesture = gesture_map_[gesture_and_connection_id]; + if (!gesture) { + gesture = new Gesture(gesture_id); + gesture_map_[gesture_and_connection_id] = gesture; + } + return gesture; +} + +void GestureManager::AttachGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view) { + gesture->Attach(pointer, view); +} + +void GestureManager::DetachGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view) { + gesture->Detach(pointer, view); + if (gesture->pointers_and_views().empty()) { + gesture_map_.erase(MakeGestureAndConnectionId(view, gesture->id())); + delete gesture; + } +} + +void GestureManager::CancelGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view) { + if (gesture->state() == Gesture::STATE_CANCELED) + return; + + gesture->set_state(Gesture::STATE_CANCELED); + for (auto& pointer_and_view : gesture->pointers_and_views()) { + (*current_change_)[pointer_and_view.view].canceled_gestures.insert( + gesture->id()); + if (pointer_and_view.pointer != pointer) + pointer_and_view.pointer->GestureCanceled(gesture); + } +} + +void GestureManager::ChooseGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view) { + if (gesture->state() == Gesture::STATE_CHOSEN) { + // This happens when |pointer| is supplied a gesture that is already + // chosen. + DCHECK((*current_change_)[view].chosen_gesture == kInvalidGestureId || + (*current_change_)[view].chosen_gesture == gesture->id()); + (*current_change_)[view].chosen_gesture = gesture->id(); + pointer->GestureChosen(gesture, view); + } else { + gesture->set_state(Gesture::STATE_CHOSEN); + for (auto& pointer_and_view : gesture->pointers_and_views()) { + DCHECK((*current_change_)[pointer_and_view.view].chosen_gesture == + kInvalidGestureId || + (*current_change_)[pointer_and_view.view].chosen_gesture == + gesture->id()); + (*current_change_)[pointer_and_view.view].chosen_gesture = gesture->id(); + pointer_and_view.pointer->GestureChosen(gesture, view); + } + } +} + +void GestureManager::ScheduleDelete(Pointer* pointer) { + auto iter = + std::find(active_pointers_.begin(), active_pointers_.end(), pointer); + if (iter != active_pointers_.end()) { + active_pointers_.weak_erase(iter); + pointers_to_delete_.push_back(pointer); + } +} + +} // namespace view_manager diff --git a/components/view_manager/gesture_manager.h b/components/view_manager/gesture_manager.h new file mode 100644 index 0000000..624bd2a --- /dev/null +++ b/components/view_manager/gesture_manager.h @@ -0,0 +1,188 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_H_ +#define COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_H_ + +#include <map> +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" + +namespace mojo { +class Event; +} + +namespace view_manager { + +class GestureManagerDelegate; +class GestureManagerTest; +class ServerView; + +struct GestureStateChange { + GestureStateChange(); + ~GestureStateChange(); + + uint32_t chosen_gesture; + std::set<uint32_t> canceled_gestures; +}; + +using ChangeMap = std::map<const ServerView*, GestureStateChange>; + +// GestureManager handles incoming pointer events. It determines the set of +// views (at most one per connection) that are interested in the event and +// informs the delegate at the appropriate time. +// +// Each pointer may have any number of views associated with it, and each view +// may have any number of gestures associated with it. Gesture are identified +// the by the pair of the connection id of the view and the client supplied +// gesture. The same gesture may be used in multiple pointers (see example +// below). +// +// Gestures have the following states: +// . initial: Initial state for new gestures. From this state the gesture may +// become chosen or canceled. Once a gesture moves out of this state it can +// never go back. +// . chosen: the gesture has been chosen. From this state the gesture may be +// canceled. +// . canceled: the gesture has been canceled. +// Gestures are active as long as they are included in the set of +// |possible_gesture_ids|. Gestures can be removed at any time by removing the +// gesture from |possible_gesture_ids|. +// +// A particular pointer has two distinct states: +// . initial: none of the gestures associated with the pointer have been +// chosen. +// . chosen: when a gesture associated with the pointer has been chosen. +// Pointers are removed when a POINTER_UP or POINTER_CANCEL event is received. +// +// When a pointer is chosen all other gestures associated with the pointer are +// implicitly canceled. If the chosen gesture is canceled the pointer remains +// in the chosen state and no gestures can be chosen. +// +// Event propagation (ProcessEvent()) is handled in two distinct ways: +// . Until a gesture has been chosen for the pointer, views are notified in +// order (deepest first). The next view (ancestor) is notified once +// SetGestures() has been invoked for the previous (descendant) view. +// . Once a gesture has been chosen, then incoming events are immediately +// dispatched. +// +// The following example highlights various states and transitions: +// . A POINTER_DOWN event is received for the pointer p1. The views that +// contain the location of the event (starting with the deepest) are v1, v2, +// v3 and v4. Both v1 and v2 have the property kViewManagerKeyWantsTouchEvents +// set, so only v1 and v2 are considered. v1 is the deepest view, so the +// touch event is set to it and only it first. +// . v1 responds with possible gestures g1 and g2. v1 does not specify either +// of the gestures as chosen. +// . As the response from v1 has been received and there is no chosen gesture +// the POINTER_DOWN event is sent to v2. +// . v2 responds with gestures g3 and g4, neither of which are chosen. +// . A POINTER_MOVE for p1 is received. As no gestures have been chosen event +// of the POINTER_MOVE continues with v1 first. +// . v1 returns g1 and g2 as possible gestures and does not choose one. +// . The POINTER_MOVE is sent to v2. +// . v2 returns g3 and g4 as possible gestures and does not choose one. +// At this point p1 has the possible gestures g1, g2, g3, g4. Gestures g1 and +// g2 are associated with v1. Gestures g3 and g4 are associated with v2. +// . A POINTER_DOWN event is received for the pointer p2. v1 and v2 again +// contain the location of the pointer. v1 is handed the event first. +// . A POINTER_MOVE event is received for the pointer p2. As the response from +// v1 has not been received yet, the event is not sent yet (it is queued up). +// . v1 responds to the POINTER_DOWN for p2 with g1 and g2 and chooses g1. +// At this point g2, g3 and g4 are all canceled with g1 chosen. p2 is in the +// chosen state, as is p1 (p1 enters the chosen state as it contains the chosen +// gesture as well). +// . The POINTER_DOWN event for p2 is sent to v2. As p2 is in the chosen state +// the POINTER_MOVE event that was queued up is sent to both v1 and v2 at the +// same time (no waiting for response). +// +// TODO(sky): add some sort of timeout to deal with hung processes. +class GestureManager { + public: + using GestureAndConnectionId = uint64_t; + + static const uint32_t kInvalidGestureId; + + GestureManager(GestureManagerDelegate* delegate, const ServerView* root); + ~GestureManager(); + + // Processes the current event. See GestureManager description for details. + bool ProcessEvent(const mojo::Event& event); + + // Sets the gestures for the specified view and pointer. + scoped_ptr<ChangeMap> SetGestures( + const ServerView* view, + int32_t pointer_id, + uint32_t chosen_gesture_id, + const std::set<uint32_t>& possible_gesture_ids, + const std::set<uint32_t>& canceled_gesture_ids); + + private: + friend class GestureManagerTest; + + class Gesture; + class Pointer; + struct PointerAndView; + class ScheduledDeleteProcessor; + + // Returns the Pointer for |pointer_id|, or null if one doesn't exist. + Pointer* GetPointerById(int32_t pointer_id); + + // Notification that |pointer| has no gestures. This deletes |pointer|. + void PointerHasNoGestures(Pointer* pointer); + + // Returns the Gesture for the specified arguments, creating if necessary. + Gesture* GetGesture(const ServerView* view, uint32_t gesture_id); + + // Called from Pointer when a gesture is associated with a pointer. + void AttachGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view); + + // Called from Pointer when a gesture is no longer associated with a + // pointer. + void DetachGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view); + + // Cancels the supplied gesture (if it isn't canceled already). Notifies all + // pointers containing |gesture| that |gesture| has been canceled. + void CancelGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view); + + // Chooses the supplied gesture. Notifies all pointers containing |gesture| + // that |gesture| has been chosen. + void ChooseGesture(Gesture* gesture, + Pointer* pointer, + const ServerView* view); + + // Deletes |pointer| after processing the current event. We delay deletion + // until after the event as immediate deletion can cause problems for Pointer + // (this is because the same Pointer may be on multiple frames of the stack). + void ScheduleDelete(Pointer* pointer); + + GestureManagerDelegate* delegate_; + const ServerView* root_view_; + + // Map for looking up gestures. Gestures are identified by the pair of + // connection id and supplied gesture id. + std::map<GestureAndConnectionId, Gesture*> gesture_map_; + + ScopedVector<Pointer> active_pointers_; + + // See comment in ScheduleDelete() for details. + ScopedVector<Pointer> pointers_to_delete_; + + // Accumulates changes as the result of SetGestures(). + scoped_ptr<ChangeMap> current_change_; + + DISALLOW_COPY_AND_ASSIGN(GestureManager); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_H_ diff --git a/components/view_manager/gesture_manager_delegate.h b/components/view_manager/gesture_manager_delegate.h new file mode 100644 index 0000000..685458e --- /dev/null +++ b/components/view_manager/gesture_manager_delegate.h @@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_DELEGATE_H_ + +#include <set> + +#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" + +namespace view_manager { + +class GestureManagerDelegate { + public: + // Informs the delegate of a pointer event. The delegate should asynchronously + // respond with the set of gestures appropriate for the view + // (GestureManager::SetGestures()). + // |has_chosen_gesture| is true if a gesture has been chosen. + virtual void ProcessEvent(const ServerView* view, + mojo::EventPtr event, + bool has_chosen_gesture) = 0; + + protected: + virtual ~GestureManagerDelegate() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_GESTURE_MANAGER_DELEGATE_H_ diff --git a/components/view_manager/gesture_manager_unittest.cc b/components/view_manager/gesture_manager_unittest.cc new file mode 100644 index 0000000..e65f2ae5 --- /dev/null +++ b/components/view_manager/gesture_manager_unittest.cc @@ -0,0 +1,467 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/gesture_manager.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "components/view_manager/gesture_manager_delegate.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/input_events/public/interfaces/input_events.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/keys.h" + +namespace view_manager { +namespace { + +const uint32_t kInvalidGestureId = GestureManager::kInvalidGestureId; + +void MarkAsRespondsToTouch(ServerView* view) { + std::vector<uint8_t> empty_vector; + view->SetProperty(mojo::kViewManagerKeyWantsTouchEvents, &empty_vector); +} + +std::set<uint32_t> SetWith(uint32_t v1) { + std::set<uint32_t> result; + result.insert(v1); + return result; +} + +std::set<uint32_t> SetWith(uint32_t v1, uint32_t v2) { + std::set<uint32_t> result; + result.insert(v1); + result.insert(v2); + return result; +} + +std::set<uint32_t> SetWith(uint32_t v1, uint32_t v2, uint32_t v3) { + std::set<uint32_t> result; + result.insert(v1); + result.insert(v2); + result.insert(v3); + return result; +} + +std::string EventTypeToString(mojo::EventType event_type) { + switch (event_type) { + case mojo::EVENT_TYPE_POINTER_CANCEL: + return "cancel"; + case mojo::EVENT_TYPE_POINTER_DOWN: + return "down"; + case mojo::EVENT_TYPE_POINTER_MOVE: + return "move"; + case mojo::EVENT_TYPE_POINTER_UP: + return "up"; + default: + break; + } + return std::string("unexpected event"); +} + +mojo::EventPtr CreateEvent(mojo::EventType type, + int32_t pointer_id, + int x, + int y) { + mojo::EventPtr event(mojo::Event::New()); + event->action = type; + event->pointer_data = mojo::PointerData::New(); + event->pointer_data->pointer_id = pointer_id; + event->pointer_data->x = x; + event->pointer_data->y = y; + return event.Pass(); +} + +struct CompareViewByConnectionId { + bool operator()(const ServerView* a, const ServerView* b) { + return a->id().connection_id < b->id().connection_id; + } +}; + +std::string IDsToString(const std::set<uint32_t>& ids) { + std::string result; + for (uint32_t id : ids) { + if (!result.empty()) + result += ","; + result += base::UintToString(id); + } + return result; +} + +std::string GestureStateChangeToString(const ServerView* view, + const GestureStateChange& change) { + std::string result = + "connection=" + base::IntToString(view->id().connection_id); + if (change.chosen_gesture != GestureManager::kInvalidGestureId) + result += " chosen=" + base::UintToString(change.chosen_gesture); + if (!change.canceled_gestures.empty()) + result += " canceled=" + IDsToString(change.canceled_gestures); + return result; +} + +} // namespace + +class TestGestureManagerDelegate : public GestureManagerDelegate { + public: + TestGestureManagerDelegate() {} + ~TestGestureManagerDelegate() override {} + + std::string GetAndClearDescriptions() { + const std::string result(JoinString(descriptions_, '\n')); + descriptions_.clear(); + return result; + } + + std::vector<std::string>& descriptions() { return descriptions_; } + + void AppendDescriptionsFromResults(const ChangeMap& change_map) { + std::set<const ServerView*, CompareViewByConnectionId> views_by_id; + for (const auto& pair : change_map) + views_by_id.insert(pair.first); + + for (auto* view : views_by_id) { + descriptions_.push_back( + GestureStateChangeToString(view, change_map.find(view)->second)); + } + } + + // GestureManagerDelegate: + void ProcessEvent(const ServerView* view, + mojo::EventPtr event, + bool has_chosen_gesture) override { + descriptions_.push_back( + EventTypeToString(event->action) + " pointer=" + + base::IntToString(event->pointer_data->pointer_id) + " connection=" + + base::UintToString(view->id().connection_id) + " chosen=" + + (has_chosen_gesture ? "true" : "false")); + } + + private: + std::vector<std::string> descriptions_; + + DISALLOW_COPY_AND_ASSIGN(TestGestureManagerDelegate); +}; + +class GestureManagerTest : public testing::Test { + public: + GestureManagerTest() + : root_(&view_delegate_, ViewId(1, 1)), + child_(&view_delegate_, ViewId(2, 2)), + gesture_manager_(&gesture_delegate_, &root_) { + root_.SetVisible(true); + MarkAsRespondsToTouch(&root_); + root_.SetBounds(gfx::Rect(0, 0, 100, 100)); + } + ~GestureManagerTest() override {} + + void SetGestures(const ServerView* view, + int32_t pointer_id, + uint32_t chosen_gesture_id, + const std::set<uint32_t>& possible_gesture_ids, + const std::set<uint32_t>& canceled_ids) { + scoped_ptr<ChangeMap> result( + gesture_manager_.SetGestures(view, pointer_id, chosen_gesture_id, + possible_gesture_ids, canceled_ids)); + gesture_delegate_.AppendDescriptionsFromResults(*result); + } + + void AddChildView() { + MarkAsRespondsToTouch(&child_); + child_.SetVisible(true); + root_.Add(&child_); + child_.SetBounds(gfx::Rect(0, 0, 100, 100)); + } + + protected: + TestServerViewDelegate view_delegate_; + ServerView root_; + ServerView child_; + TestGestureManagerDelegate gesture_delegate_; + GestureManager gesture_manager_; + + private: + DISALLOW_COPY_AND_ASSIGN(GestureManagerTest); +}; + +TEST_F(GestureManagerTest, SingleViewAndSingleGesture) { + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Choose this pointer. + SetGestures(&root_, pointer_id, 10u, SetWith(10u), std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=10", + gesture_delegate_.GetAndClearDescriptions()); +} + +TEST_F(GestureManagerTest, SingleViewAndTwoGestures) { + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + + // Delegate should have got nothing. + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Cancel 10, 5 should become active. + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + SetWith(10u)); + + EXPECT_EQ("connection=1 chosen=5 canceled=10", + gesture_delegate_.GetAndClearDescriptions()); +} + +TEST_F(GestureManagerTest, TwoViewsSingleGesture) { + AddChildView(); + + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + // Deepest child should be queried first. + EXPECT_EQ("down pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond from the first view, which triggers the second to be queried. + SetGestures(&child_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond with 5,10 for the second view. Should get nothing. + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Cancel 10 in the child. + SetGestures(&child_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + SetWith(10u)); + EXPECT_EQ("connection=2 canceled=10", + gesture_delegate_.GetAndClearDescriptions()); + + // Choose 5 in the root. This should choose 5 in the root and cancel 5 in + // the child. + SetGestures(&root_, pointer_id, 5u, SetWith(5u, 10u), SetWith(10u)); + ASSERT_EQ(2u, gesture_delegate_.descriptions().size()); + EXPECT_EQ("connection=1 chosen=5 canceled=10", + gesture_delegate_.descriptions()[0]); + EXPECT_EQ("connection=2 canceled=5", gesture_delegate_.descriptions()[1]); +} + +TEST_F(GestureManagerTest, TwoViewsWaitForMoveToChoose) { + AddChildView(); + + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + // Deepest child should be queried first. + EXPECT_EQ("down pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Send a move. The move should not be processed as GestureManager is + // still waiting for responses. + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_MOVE, pointer_id, 6, 6)); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Respond from the first view, which triggers the second to be queried. + SetGestures(&child_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond with 1,2 for the second view. + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(1u, 2u), + std::set<uint32_t>()); + // Now that we've responded to the down requests we should get a move for the + // child. + EXPECT_EQ("move pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond for the child, root should now get move. + SetGestures(&child_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + EXPECT_EQ("move pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond with nothing chosen for the root. Nothing should come in as no + // pending moves. + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(1u, 2u), + std::set<uint32_t>()); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Send another move event and respond with a chosen id. + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_MOVE, pointer_id, 7, 7)); + EXPECT_EQ("move pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + SetGestures(&child_, pointer_id, 5u, SetWith(5u, 10u), std::set<uint32_t>()); + ASSERT_EQ(3u, gesture_delegate_.descriptions().size()); + // Now that a gesture is chosen the move event is generated. + EXPECT_EQ("move pointer=1 connection=1 chosen=true", + gesture_delegate_.descriptions()[0]); + EXPECT_EQ("connection=1 canceled=1,2", gesture_delegate_.descriptions()[1]); + EXPECT_EQ("connection=2 chosen=5 canceled=10", + gesture_delegate_.descriptions()[2]); +} + +TEST_F(GestureManagerTest, SingleViewNewPointerAfterChoose) { + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Choose 5. + SetGestures(&root_, pointer_id, 5u, SetWith(5u, 10u), std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=5 canceled=10", + gesture_delegate_.GetAndClearDescriptions()); + + // Start another down event with a different pointer. + const int32_t pointer_id2 = 2; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id2, 5, 5)); + EXPECT_EQ("down pointer=2 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // For the new pointer supply the id of a gesture that has been chosen. + // Even though we didn't explicitly supply 5 as chosen, 5 is chosen because + // it's already in the chosen state for pointer 1. + SetGestures(&root_, pointer_id2, kInvalidGestureId, SetWith(5u, 11u), + std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=5 canceled=11", + gesture_delegate_.GetAndClearDescriptions()); +} + +TEST_F(GestureManagerTest, SingleViewChoosingConflictingGestures) { + // For pointer1 choose 1 with 1,2,3 as possibilities. + const int32_t pointer1 = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer1, 5, 5)); + gesture_delegate_.GetAndClearDescriptions(); + SetGestures(&root_, pointer1, 1u, SetWith(1u, 2u, 3u), std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=1 canceled=2,3", + gesture_delegate_.GetAndClearDescriptions()); + + // For pointer2 choose 11 with 11,12 as possibilities. + const int32_t pointer2 = 2; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer2, 5, 5)); + gesture_delegate_.GetAndClearDescriptions(); + SetGestures(&root_, pointer2, 11u, SetWith(11u, 12u), std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=11 canceled=12", + gesture_delegate_.GetAndClearDescriptions()); + + // For pointer3 choose 21 with 1,11,21 as possibilties. + const int32_t pointer3 = 3; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer3, 5, 5)); + gesture_delegate_.GetAndClearDescriptions(); + SetGestures(&root_, pointer3, 21u, SetWith(1u, 11u, 21u), + std::set<uint32_t>()); + EXPECT_EQ("connection=1 chosen=21 canceled=1,11", + gesture_delegate_.GetAndClearDescriptions()); +} + +TEST_F(GestureManagerTest, + TwoViewsRespondingWithChosenGestureSendsRemainingEvents) { + AddChildView(); + + // Start two pointer downs, don't respond to either. + const int32_t pointer1 = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer1, 5, 5)); + EXPECT_EQ("down pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + const int32_t pointer2 = 2; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer2, 5, 5)); + EXPECT_EQ("down pointer=2 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Queue up a move event for pointer1. The event should not be forwarded + // as we're still waiting. + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_MOVE, pointer1, 5, 5)); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Respond with 1,2 for pointer1 (nothing chosen yet). + SetGestures(&child_, pointer1, kInvalidGestureId, SetWith(1u, 2u), + std::set<uint32_t>()); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Respond with 1,2 and choose 1 for pointer2. This results in the following: + // down for pointer 1 (because we chose a gesture in common with pointer1), + // move for pointer 1 in both connections (because a gesture was chosen queued + // up events are sent), down for pointer2 for the root and finally + // notification of what was chosen. + SetGestures(&child_, pointer2, 1u, SetWith(1u, 2u), std::set<uint32_t>()); + ASSERT_EQ(5u, gesture_delegate_.descriptions().size()); + EXPECT_EQ("down pointer=1 connection=1 chosen=true", + gesture_delegate_.descriptions()[0]); + EXPECT_EQ("move pointer=1 connection=2 chosen=true", + gesture_delegate_.descriptions()[1]); + EXPECT_EQ("move pointer=1 connection=1 chosen=true", + gesture_delegate_.descriptions()[2]); + EXPECT_EQ("down pointer=2 connection=1 chosen=true", + gesture_delegate_.descriptions()[3]); + EXPECT_EQ("connection=2 chosen=1 canceled=2", + gesture_delegate_.descriptions()[4]); +} + +TEST_F(GestureManagerTest, TwoViewsSingleGestureUp) { + AddChildView(); + + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + // Deepest child should be queried first. + EXPECT_EQ("down pointer=1 connection=2 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Send an up, shouldn't result in anything. + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_UP, pointer_id, 5, 5)); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Respond from the first view, with a chosen gesture. + SetGestures(&child_, pointer_id, 5u, SetWith(5u, 10u), std::set<uint32_t>()); + ASSERT_EQ(4u, gesture_delegate_.descriptions().size()); + EXPECT_EQ("down pointer=1 connection=1 chosen=true", + gesture_delegate_.descriptions()[0]); + EXPECT_EQ("up pointer=1 connection=2 chosen=true", + gesture_delegate_.descriptions()[1]); + EXPECT_EQ("up pointer=1 connection=1 chosen=true", + gesture_delegate_.descriptions()[2]); + EXPECT_EQ("connection=2 chosen=5 canceled=10", + gesture_delegate_.descriptions()[3]); +} + +TEST_F(GestureManagerTest, SingleViewSingleGestureCancel) { + const int32_t pointer_id = 1; + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_DOWN, pointer_id, 5, 5)); + EXPECT_EQ("down pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); + + // Send a cancel, shouldn't result in anything. + gesture_manager_.ProcessEvent( + *CreateEvent(mojo::EVENT_TYPE_POINTER_CANCEL, pointer_id, 5, 5)); + EXPECT_EQ(std::string(), gesture_delegate_.GetAndClearDescriptions()); + + // Respond from the first view, with no gesture, should unblock cancel. + SetGestures(&root_, pointer_id, kInvalidGestureId, SetWith(5u, 10u), + std::set<uint32_t>()); + EXPECT_EQ("cancel pointer=1 connection=1 chosen=false", + gesture_delegate_.GetAndClearDescriptions()); +} + +} // namespace view_manager diff --git a/components/view_manager/ids.h b/components/view_manager/ids.h new file mode 100644 index 0000000..727ae05 --- /dev/null +++ b/components/view_manager/ids.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_IDS_H_ +#define COMPONENTS_VIEW_MANAGER_IDS_H_ + +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" + +namespace view_manager { + +// Connection id is used to indicate no connection. That is, no +// ViewManagerServiceImpl ever gets this id. +const mojo::ConnectionSpecificId kInvalidConnectionId = 0; + +// Adds a bit of type safety to view ids. +struct ViewId { + ViewId(mojo::ConnectionSpecificId connection_id, + mojo::ConnectionSpecificId view_id) + : connection_id(connection_id), view_id(view_id) {} + ViewId() : connection_id(0), view_id(0) {} + + bool operator==(const ViewId& other) const { + return other.connection_id == connection_id && + other.view_id == view_id; + } + + bool operator!=(const ViewId& other) const { + return !(*this == other); + } + + mojo::ConnectionSpecificId connection_id; + mojo::ConnectionSpecificId view_id; +}; + +inline ViewId ViewIdFromTransportId(mojo::Id id) { + return ViewId(mojo::HiWord(id), mojo::LoWord(id)); +} + +inline mojo::Id ViewIdToTransportId(const ViewId& id) { + return (id.connection_id << 16) | id.view_id; +} + +inline ViewId RootViewId() { + return ViewId(kInvalidConnectionId, 1); +} + +// Returns a ViewId that is reserved to indicate no view. That is, no view will +// ever be created with this id. +inline ViewId InvalidViewId() { + return ViewId(kInvalidConnectionId, 0); +} + +// All cloned views use this id. +inline ViewId ClonedViewId() { + return ViewId(kInvalidConnectionId, 2); +} + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_IDS_H_ diff --git a/components/view_manager/main.cc b/components/view_manager/main.cc new file mode 100644 index 0000000..5c8735c --- /dev/null +++ b/components/view_manager/main.cc @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_manager_app.h" +#include "mojo/application/application_runner_chromium.h" +#include "third_party/mojo/src/mojo/public/c/system/main.h" + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new view_manager::ViewManagerApp); + return runner.Run(shell_handle); +} diff --git a/components/view_manager/scheduled_animation_group.cc b/components/view_manager/scheduled_animation_group.cc new file mode 100644 index 0000000..210fa8e --- /dev/null +++ b/components/view_manager/scheduled_animation_group.cc @@ -0,0 +1,352 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/scheduled_animation_group.h" + +#include <set> + +#include "components/view_manager/server_view.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" + +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::AnimationProperty; + +namespace view_manager { +namespace { + +using Sequences = std::vector<ScheduledAnimationSequence>; + +// Gets the value of |property| from |view| into |value|. +void GetValueFromView(const ServerView* view, + AnimationProperty property, + ScheduledAnimationValue* value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + NOTREACHED(); + break; + case ANIMATION_PROPERTY_OPACITY: + value->float_value = view->opacity(); + break; + case ANIMATION_PROPERTY_TRANSFORM: + value->transform = view->transform(); + break; + } +} + +// Sets the value of |property| from |value| into |view|. +void SetViewPropertyFromValue(ServerView* view, + AnimationProperty property, + const ScheduledAnimationValue& value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + break; + case ANIMATION_PROPERTY_OPACITY: + view->SetOpacity(value.float_value); + break; + case ANIMATION_PROPERTY_TRANSFORM: + view->SetTransform(value.transform); + break; + } +} + +// Sets the value of |property| into |view| between two points. +void SetViewPropertyFromValueBetween(ServerView* view, + AnimationProperty property, + double value, + gfx::Tween::Type tween_type, + const ScheduledAnimationValue& start, + const ScheduledAnimationValue& target) { + const double tween_value = gfx::Tween::CalculateValue(tween_type, value); + switch (property) { + case ANIMATION_PROPERTY_NONE: + break; + case ANIMATION_PROPERTY_OPACITY: + view->SetOpacity(gfx::Tween::FloatValueBetween( + tween_value, start.float_value, target.float_value)); + break; + case ANIMATION_PROPERTY_TRANSFORM: + view->SetTransform(gfx::Tween::TransformValueBetween( + tween_value, start.transform, target.transform)); + break; + } +} + +gfx::Tween::Type AnimationTypeToTweenType(mojo::AnimationTweenType type) { + switch (type) { + case mojo::ANIMATION_TWEEN_TYPE_LINEAR: + return gfx::Tween::LINEAR; + case mojo::ANIMATION_TWEEN_TYPE_EASE_IN: + return gfx::Tween::EASE_IN; + case mojo::ANIMATION_TWEEN_TYPE_EASE_OUT: + return gfx::Tween::EASE_OUT; + case mojo::ANIMATION_TWEEN_TYPE_EASE_IN_OUT: + return gfx::Tween::EASE_IN_OUT; + } + return gfx::Tween::LINEAR; +} + +void ConvertToScheduledValue(const mojo::AnimationValue& transport_value, + ScheduledAnimationValue* value) { + value->float_value = transport_value.float_value; + value->transform = transport_value.transform.To<gfx::Transform>(); +} + +void ConvertToScheduledElement(const mojo::AnimationElement& transport_element, + ScheduledAnimationElement* element) { + element->property = transport_element.property; + element->duration = + base::TimeDelta::FromMicroseconds(transport_element.duration); + element->tween_type = AnimationTypeToTweenType(transport_element.tween_type); + if (transport_element.property != ANIMATION_PROPERTY_NONE) { + if (transport_element.start_value.get()) { + element->is_start_valid = true; + ConvertToScheduledValue(*transport_element.start_value, + &(element->start_value)); + } else { + element->is_start_valid = false; + } + ConvertToScheduledValue(*transport_element.target_value, + &(element->target_value)); + } +} + +bool IsAnimationValueValid(AnimationProperty property, + const mojo::AnimationValue& value) { + switch (property) { + case ANIMATION_PROPERTY_NONE: + NOTREACHED(); + return false; + case ANIMATION_PROPERTY_OPACITY: + return value.float_value >= 0.f && value.float_value <= 1.f; + case ANIMATION_PROPERTY_TRANSFORM: + return value.transform.get() && value.transform->matrix.size() == 16u; + } + return false; +} + +bool IsAnimationElementValid(const mojo::AnimationElement& element) { + if (element.property == ANIMATION_PROPERTY_NONE) + return true; // None is a pause and doesn't need any values. + if (element.start_value.get() && + !IsAnimationValueValid(element.property, *element.start_value)) + return false; + // For all other properties we require a target. + return element.target_value.get() && + IsAnimationValueValid(element.property, *element.target_value); +} + +bool IsAnimationSequenceValid(const mojo::AnimationSequence& sequence) { + if (sequence.elements.size() == 0u) + return false; + + for (size_t i = 0; i < sequence.elements.size(); ++i) { + if (!IsAnimationElementValid(*sequence.elements[i])) + return false; + } + return true; +} + +bool IsAnimationGroupValid(const mojo::AnimationGroup& transport_group) { + if (transport_group.sequences.size() == 0u) + return false; + for (size_t i = 0; i < transport_group.sequences.size(); ++i) { + if (!IsAnimationSequenceValid(*transport_group.sequences[i])) + return false; + } + return true; +} + +// If the start value for |element| isn't valid, the value for the property +// is obtained from |view| and placed into |element|. +void GetStartValueFromViewIfNecessary(const ServerView* view, + ScheduledAnimationElement* element) { + if (element->property != ANIMATION_PROPERTY_NONE && + !element->is_start_valid) { + GetValueFromView(view, element->property, &(element->start_value)); + } +} + +void GetScheduledAnimationProperties(const Sequences& sequences, + std::set<AnimationProperty>* properties) { + for (const ScheduledAnimationSequence& sequence : sequences) { + for (const ScheduledAnimationElement& element : sequence.elements) + properties->insert(element.property); + } +} + +void SetPropertyToTargetProperty(ServerView* view, + mojo::AnimationProperty property, + const Sequences& sequences) { + // NOTE: this doesn't deal with |cycle_count| quite right, but I'm honestly + // not sure we really want to support the same property in multiple sequences + // animating at once so I'm not dealing. + base::TimeDelta max_end_duration; + scoped_ptr<ScheduledAnimationValue> value; + for (const ScheduledAnimationSequence& sequence : sequences) { + base::TimeDelta duration; + for (const ScheduledAnimationElement& element : sequence.elements) { + if (element.property != property) + continue; + + duration += element.duration; + if (duration > max_end_duration) { + max_end_duration = duration; + value.reset(new ScheduledAnimationValue(element.target_value)); + } + } + } + if (value.get()) + SetViewPropertyFromValue(view, property, *value); +} + +void ConvertSequenceToScheduled( + const mojo::AnimationSequence& transport_sequence, + base::TimeTicks now, + ScheduledAnimationSequence* sequence) { + sequence->run_until_stopped = transport_sequence.cycle_count == 0u; + sequence->cycle_count = transport_sequence.cycle_count; + DCHECK_NE(0u, transport_sequence.elements.size()); + sequence->elements.resize(transport_sequence.elements.size()); + + base::TimeTicks element_start_time = now; + for (size_t i = 0; i < transport_sequence.elements.size(); ++i) { + ConvertToScheduledElement(*(transport_sequence.elements[i].get()), + &(sequence->elements[i])); + sequence->elements[i].start_time = element_start_time; + sequence->duration += sequence->elements[i].duration; + element_start_time += sequence->elements[i].duration; + } +} + +bool AdvanceSequence(ServerView* view, + ScheduledAnimationSequence* sequence, + base::TimeTicks now) { + ScheduledAnimationElement* element = + &(sequence->elements[sequence->current_index]); + while (element->start_time + element->duration < now) { + SetViewPropertyFromValue(view, element->property, element->target_value); + if (++sequence->current_index == sequence->elements.size()) { + if (!sequence->run_until_stopped && --sequence->cycle_count == 0) { + SetViewPropertyFromValue(view, element->property, + element->target_value); + return false; + } + + sequence->current_index = 0; + } + sequence->elements[sequence->current_index].start_time = + element->start_time + element->duration; + element = &(sequence->elements[sequence->current_index]); + GetStartValueFromViewIfNecessary(view, element); + + // It's possible for the delta between now and |last_tick_time_| to be very + // big (could happen if machine sleeps and is woken up much later). Normally + // the repeat count is smallish, so we don't bother optimizing it. OTOH if + // a sequence repeats forever we optimize it lest we get stuck in this loop + // for a very long time. + if (sequence->run_until_stopped && sequence->current_index == 0) { + element->start_time = + now - base::TimeDelta::FromMicroseconds( + (now - element->start_time).InMicroseconds() % + sequence->duration.InMicroseconds()); + } + } + return true; +} + +} // namespace + +ScheduledAnimationValue::ScheduledAnimationValue() { +} +ScheduledAnimationValue::~ScheduledAnimationValue() { +} + +ScheduledAnimationElement::ScheduledAnimationElement() + : property(ANIMATION_PROPERTY_OPACITY), + tween_type(gfx::Tween::EASE_IN), + is_start_valid(false) { +} +ScheduledAnimationElement::~ScheduledAnimationElement() { +} + +ScheduledAnimationSequence::ScheduledAnimationSequence() + : run_until_stopped(false), cycle_count(0), current_index(0u) { +} +ScheduledAnimationSequence::~ScheduledAnimationSequence() { +} + +ScheduledAnimationGroup::~ScheduledAnimationGroup() { +} + +// static +scoped_ptr<ScheduledAnimationGroup> ScheduledAnimationGroup::Create( + ServerView* view, + base::TimeTicks now, + uint32_t id, + const mojo::AnimationGroup& transport_group) { + if (!IsAnimationGroupValid(transport_group)) + return nullptr; + + scoped_ptr<ScheduledAnimationGroup> group( + new ScheduledAnimationGroup(view, id, now)); + group->sequences_.resize(transport_group.sequences.size()); + for (size_t i = 0; i < transport_group.sequences.size(); ++i) { + const mojo::AnimationSequence& transport_sequence( + *(transport_group.sequences[i])); + DCHECK_NE(0u, transport_sequence.elements.size()); + ConvertSequenceToScheduled(transport_sequence, now, &group->sequences_[i]); + } + return group.Pass(); +} + +void ScheduledAnimationGroup::ObtainStartValues() { + for (ScheduledAnimationSequence& sequence : sequences_) + GetStartValueFromViewIfNecessary(view_, &(sequence.elements[0])); +} + +void ScheduledAnimationGroup::SetValuesToTargetValuesForPropertiesNotIn( + const ScheduledAnimationGroup& other) { + std::set<AnimationProperty> our_properties; + GetScheduledAnimationProperties(sequences_, &our_properties); + + std::set<AnimationProperty> other_properties; + GetScheduledAnimationProperties(other.sequences_, &other_properties); + + for (AnimationProperty property : our_properties) { + if (other_properties.count(property) == 0 && + property != ANIMATION_PROPERTY_NONE) { + SetPropertyToTargetProperty(view_, property, sequences_); + } + } +} + +bool ScheduledAnimationGroup::Tick(base::TimeTicks time) { + for (Sequences::iterator i = sequences_.begin(); i != sequences_.end();) { + if (!AdvanceSequence(view_, &(*i), time)) { + i = sequences_.erase(i); + continue; + } + const ScheduledAnimationElement& active_element( + i->elements[i->current_index]); + const double percent = + (time - active_element.start_time).InMillisecondsF() / + active_element.duration.InMillisecondsF(); + SetViewPropertyFromValueBetween( + view_, active_element.property, percent, active_element.tween_type, + active_element.start_value, active_element.target_value); + ++i; + } + return sequences_.empty(); +} + +ScheduledAnimationGroup::ScheduledAnimationGroup(ServerView* view, + uint32_t id, + base::TimeTicks time_scheduled) + : view_(view), id_(id), time_scheduled_(time_scheduled) { +} + +} // namespace view_manager diff --git a/components/view_manager/scheduled_animation_group.h b/components/view_manager/scheduled_animation_group.h new file mode 100644 index 0000000..274912d --- /dev/null +++ b/components/view_manager/scheduled_animation_group.h @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ +#define COMPONENTS_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/transform.h" + +namespace view_manager { + +class ServerView; + +struct ScheduledAnimationValue { + ScheduledAnimationValue(); + ~ScheduledAnimationValue(); + + float float_value; + gfx::Transform transform; +}; + +struct ScheduledAnimationElement { + ScheduledAnimationElement(); + ~ScheduledAnimationElement(); + + mojo::AnimationProperty property; + base::TimeDelta duration; + gfx::Tween::Type tween_type; + bool is_start_valid; + ScheduledAnimationValue start_value; + ScheduledAnimationValue target_value; + // Start time is based on scheduled time and relative to any other elements + // in the sequence. + base::TimeTicks start_time; +}; + +struct ScheduledAnimationSequence { + ScheduledAnimationSequence(); + ~ScheduledAnimationSequence(); + + bool run_until_stopped; + std::vector<ScheduledAnimationElement> elements; + + // Sum of the duration of all elements. This does not take into account + // |cycle_count|. + base::TimeDelta duration; + + // The following values are updated as the animation progresses. + + // Number of cycles remaining. This is only used if |run_until_stopped| is + // false. + uint32_t cycle_count; + + // Index into |elements| of the element currently animating. + size_t current_index; +}; + +// Corresponds to a mojo::AnimationGroup and is responsible for running the +// actual animation. +class ScheduledAnimationGroup { + public: + ~ScheduledAnimationGroup(); + + // Returns a new ScheduledAnimationGroup from the supplied parameters, or + // null if |transport_group| isn't valid. + static scoped_ptr<ScheduledAnimationGroup> Create( + ServerView* view, + base::TimeTicks now, + uint32_t id, + const mojo::AnimationGroup& transport_group); + + uint32_t id() const { return id_; } + + // Gets the start value for any elements that don't have an explicit start. + // value. + void ObtainStartValues(); + + // Sets the values of any properties that are not in |other| to their final + // value. + void SetValuesToTargetValuesForPropertiesNotIn( + const ScheduledAnimationGroup& other); + + // Advances the group. |time| is the current time. Returns true if the group + // is done (nothing left to animate). + bool Tick(base::TimeTicks time); + + ServerView* view() { return view_; } + + private: + ScheduledAnimationGroup(ServerView* view, + uint32_t id, + base::TimeTicks time_scheduled); + + ServerView* view_; + const uint32_t id_; + base::TimeTicks time_scheduled_; + std::vector<ScheduledAnimationSequence> sequences_; + + DISALLOW_COPY_AND_ASSIGN(ScheduledAnimationGroup); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SCHEDULED_ANIMATION_GROUP_H_ diff --git a/components/view_manager/scheduled_animation_group_unittest.cc b/components/view_manager/scheduled_animation_group_unittest.cc new file mode 100644 index 0000000..ec30493 --- /dev/null +++ b/components/view_manager/scheduled_animation_group_unittest.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/scheduled_animation_group.h" + +#include "components/view_manager/server_view.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/transform/transform_type_converters.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/animations.mojom.h" + +using mojo::ANIMATION_PROPERTY_NONE; +using mojo::ANIMATION_PROPERTY_OPACITY; +using mojo::ANIMATION_PROPERTY_TRANSFORM; +using mojo::ANIMATION_TWEEN_TYPE_LINEAR; +using mojo::AnimationGroup; +using mojo::AnimationSequence; +using mojo::AnimationElement; +using mojo::AnimationValue; + +namespace view_manager { +namespace { + +bool IsAnimationGroupValid(const AnimationGroup& transport_group) { + TestServerViewDelegate view_delegate; + ServerView view(&view_delegate, ViewId()); + scoped_ptr<ScheduledAnimationGroup> group(ScheduledAnimationGroup::Create( + &view, base::TimeTicks::Now(), 1, transport_group)); + return group.get() != nullptr; +} + +} // namespace + +TEST(ScheduledAnimationGroupTest, IsAnimationGroupValid) { + AnimationGroup group; + + // AnimationGroup with no sequences is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + group.sequences.push_back(AnimationSequence::New()); + + // Sequence with no elements is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + AnimationSequence& sequence = *(group.sequences[0]); + sequence.elements.push_back(AnimationElement::New()); + AnimationElement& element = *(sequence.elements[0]); + element.property = ANIMATION_PROPERTY_OPACITY; + element.tween_type = ANIMATION_TWEEN_TYPE_LINEAR; + + // Element with no target_value is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + // Opacity must be between 0 and 1. + element.target_value = AnimationValue::New(); + element.target_value->float_value = 2.5f; + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element.target_value->float_value = .5f; + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Bogus start value. + element.start_value = AnimationValue::New(); + element.start_value->float_value = 2.5f; + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element.start_value->float_value = .5f; + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Bogus transform. + element.property = ANIMATION_PROPERTY_TRANSFORM; + EXPECT_FALSE(IsAnimationGroupValid(group)); + element.start_value->transform = mojo::Transform::From(gfx::Transform()); + EXPECT_FALSE(IsAnimationGroupValid(group)); + element.target_value->transform = mojo::Transform::From(gfx::Transform()); + EXPECT_TRUE(IsAnimationGroupValid(group)); + + // Add another empty sequence, should be invalid again. + group.sequences.push_back(AnimationSequence::New()); + EXPECT_FALSE(IsAnimationGroupValid(group)); + + AnimationSequence& sequence2 = *(group.sequences[1]); + sequence2.elements.push_back(AnimationElement::New()); + AnimationElement& element2 = *(sequence2.elements[0]); + element2.property = ANIMATION_PROPERTY_OPACITY; + element2.tween_type = ANIMATION_TWEEN_TYPE_LINEAR; + + // Element with no target_value is not valid. + EXPECT_FALSE(IsAnimationGroupValid(group)); + + element2.property = ANIMATION_PROPERTY_NONE; + EXPECT_TRUE(IsAnimationGroupValid(group)); +} + +} // namespace view_manager diff --git a/components/view_manager/server_view.cc b/components/view_manager/server_view.cc new file mode 100644 index 0000000..344d2a8 --- /dev/null +++ b/components/view_manager/server_view.cc @@ -0,0 +1,234 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/server_view.h" + +#include <inttypes.h> + +#include "base/strings/stringprintf.h" +#include "components/view_manager/server_view_delegate.h" +#include "components/view_manager/server_view_observer.h" + +namespace view_manager { + +ServerView::ServerView(ServerViewDelegate* delegate, const ViewId& id) + : delegate_(delegate), + id_(id), + parent_(nullptr), + visible_(false), + opacity_(1), + // Don't notify newly added observers during notification. This causes + // problems for code that adds an observer as part of an observer + // notification (such as ServerViewDrawTracker). + observers_(ObserverList<ServerViewObserver>::NOTIFY_EXISTING_ONLY) { + DCHECK(delegate); // Must provide a delegate. +} + +ServerView::~ServerView() { + delegate_->PrepareToDestroyView(this); + FOR_EACH_OBSERVER(ServerViewObserver, observers_, OnWillDestroyView(this)); + + while (!children_.empty()) + children_.front()->parent()->Remove(children_.front()); + + if (parent_) + parent_->Remove(this); + + FOR_EACH_OBSERVER(ServerViewObserver, observers_, OnViewDestroyed(this)); +} + +void ServerView::AddObserver(ServerViewObserver* observer) { + observers_.AddObserver(observer); +} + +void ServerView::RemoveObserver(ServerViewObserver* observer) { + observers_.RemoveObserver(observer); +} + +void ServerView::Add(ServerView* child) { + // We assume validation checks happened already. + DCHECK(child); + DCHECK(child != this); + DCHECK(!child->Contains(this)); + if (child->parent() == this) { + if (children_.size() == 1) + return; // Already in the right position. + Reorder(child, children_.back(), mojo::ORDER_DIRECTION_ABOVE); + return; + } + + ServerView* old_parent = child->parent(); + child->delegate_->PrepareToChangeViewHierarchy(child, this, old_parent); + FOR_EACH_OBSERVER(ServerViewObserver, child->observers_, + OnWillChangeViewHierarchy(child, this, old_parent)); + + if (child->parent()) + child->parent()->RemoveImpl(child); + + child->parent_ = this; + children_.push_back(child); + FOR_EACH_OBSERVER(ServerViewObserver, child->observers_, + OnViewHierarchyChanged(child, this, old_parent)); +} + +void ServerView::Remove(ServerView* child) { + // We assume validation checks happened else where. + DCHECK(child); + DCHECK(child != this); + DCHECK(child->parent() == this); + + child->delegate_->PrepareToChangeViewHierarchy(child, NULL, this); + FOR_EACH_OBSERVER(ServerViewObserver, child->observers_, + OnWillChangeViewHierarchy(child, nullptr, this)); + RemoveImpl(child); + FOR_EACH_OBSERVER(ServerViewObserver, child->observers_, + OnViewHierarchyChanged(child, nullptr, this)); +} + +void ServerView::Reorder(ServerView* child, + ServerView* relative, + mojo::OrderDirection direction) { + // We assume validation checks happened else where. + DCHECK(child); + DCHECK(child->parent() == this); + DCHECK_GT(children_.size(), 1u); + children_.erase(std::find(children_.begin(), children_.end(), child)); + Views::iterator i = std::find(children_.begin(), children_.end(), relative); + if (direction == mojo::ORDER_DIRECTION_ABOVE) { + DCHECK(i != children_.end()); + children_.insert(++i, child); + } else if (direction == mojo::ORDER_DIRECTION_BELOW) { + DCHECK(i != children_.end()); + children_.insert(i, child); + } + FOR_EACH_OBSERVER(ServerViewObserver, observers_, + OnViewReordered(this, relative, direction)); +} + +void ServerView::SetBounds(const gfx::Rect& bounds) { + if (bounds_ == bounds) + return; + + const gfx::Rect old_bounds = bounds_; + bounds_ = bounds; + FOR_EACH_OBSERVER(ServerViewObserver, observers_, + OnViewBoundsChanged(this, old_bounds, bounds)); +} + +const ServerView* ServerView::GetRoot() const { + const ServerView* view = this; + while (view && view->parent()) + view = view->parent(); + return view; +} + +std::vector<const ServerView*> ServerView::GetChildren() const { + std::vector<const ServerView*> children; + children.reserve(children_.size()); + for (size_t i = 0; i < children_.size(); ++i) + children.push_back(children_[i]); + return children; +} + +std::vector<ServerView*> ServerView::GetChildren() { + // TODO(sky): rename to children() and fix return type. + return children_; +} + +bool ServerView::Contains(const ServerView* view) const { + for (const ServerView* parent = view; parent; parent = parent->parent_) { + if (parent == this) + return true; + } + return false; +} + +void ServerView::SetVisible(bool value) { + if (visible_ == value) + return; + + delegate_->PrepareToChangeViewVisibility(this); + FOR_EACH_OBSERVER(ServerViewObserver, observers_, + OnWillChangeViewVisibility(this)); + visible_ = value; + FOR_EACH_OBSERVER(ServerViewObserver, observers_, + OnViewVisibilityChanged(this)); +} + +void ServerView::SetOpacity(float value) { + if (value == opacity_) + return; + opacity_ = value; + delegate_->OnScheduleViewPaint(this); +} + +void ServerView::SetTransform(const gfx::Transform& transform) { + if (transform_ == transform) + return; + + transform_ = transform; + delegate_->OnScheduleViewPaint(this); +} + +void ServerView::SetProperty(const std::string& name, + const std::vector<uint8_t>* value) { + auto it = properties_.find(name); + if (it != properties_.end()) { + if (value && it->second == *value) + return; + } else if (!value) { + // This property isn't set in |properties_| and |value| is NULL, so there's + // no change. + return; + } + + if (value) { + properties_[name] = *value; + } else if (it != properties_.end()) { + properties_.erase(it); + } + + FOR_EACH_OBSERVER(ServerViewObserver, observers_, + OnViewSharedPropertyChanged(this, name, value)); +} + +bool ServerView::IsDrawn(const ServerView* root) const { + if (!root->visible_) + return false; + const ServerView* view = this; + while (view && view != root && view->visible_) + view = view->parent_; + return view == root; +} + +void ServerView::SetSurfaceId(cc::SurfaceId surface_id) { + surface_id_ = surface_id; + delegate_->OnScheduleViewPaint(this); +} + +#if !defined(NDEBUG) +std::string ServerView::GetDebugWindowHierarchy() const { + std::string result; + BuildDebugInfo(std::string(), &result); + return result; +} + +void ServerView::BuildDebugInfo(const std::string& depth, + std::string* result) const { + *result += base::StringPrintf( + "%sid=%d,%d visible=%s bounds=%d,%d %dx%d surface_id=%" PRIu64 "\n", + depth.c_str(), static_cast<int>(id_.connection_id), + static_cast<int>(id_.view_id), visible_ ? "true" : "false", bounds_.x(), + bounds_.y(), bounds_.width(), bounds_.height(), surface_id_.id); + for (const ServerView* child : children_) + child->BuildDebugInfo(depth + " ", result); +} +#endif + +void ServerView::RemoveImpl(ServerView* view) { + view->parent_ = NULL; + children_.erase(std::find(children_.begin(), children_.end(), view)); +} + +} // namespace view_manager diff --git a/components/view_manager/server_view.h b/components/view_manager/server_view.h new file mode 100644 index 0000000..12d3107 --- /dev/null +++ b/components/view_manager/server_view.h @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SERVER_VIEW_H_ +#define COMPONENTS_VIEW_MANAGER_SERVER_VIEW_H_ + +#include <vector> + +#include "base/logging.h" +#include "base/observer_list.h" +#include "cc/surfaces/surface_id.h" +#include "components/view_manager/ids.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/transform.h" + +namespace view_manager { + +class ServerViewDelegate; +class ServerViewObserver; + +// Server side representation of a view. Delegate is informed of interesting +// events. +// +// It is assumed that all functions that mutate the tree have validated the +// mutation is possible before hand. For example, Reorder() assumes the supplied +// view is a child and not already in position. +// +// ServerViews do not own their children. If you delete a view that has children +// the children are implicitly removed. Similarly if a view has a parent and the +// view is deleted the deleted view is implicitly removed from the parent. +class ServerView { + public: + ServerView(ServerViewDelegate* delegate, const ViewId& id); + virtual ~ServerView(); + + void AddObserver(ServerViewObserver* observer); + void RemoveObserver(ServerViewObserver* observer); + + const ViewId& id() const { return id_; } + + void Add(ServerView* child); + void Remove(ServerView* child); + void Reorder(ServerView* child, + ServerView* relative, + mojo::OrderDirection direction); + + const gfx::Rect& bounds() const { return bounds_; } + void SetBounds(const gfx::Rect& bounds); + + const ServerView* parent() const { return parent_; } + ServerView* parent() { return parent_; } + + const ServerView* GetRoot() const; + ServerView* GetRoot() { + return const_cast<ServerView*>( + const_cast<const ServerView*>(this)->GetRoot()); + } + + std::vector<const ServerView*> GetChildren() const; + std::vector<ServerView*> GetChildren(); + + // Returns true if this contains |view| or is |view|. + bool Contains(const ServerView* view) const; + + // Returns true if the window is visible. This does not consider visibility + // of any ancestors. + bool visible() const { return visible_; } + void SetVisible(bool value); + + float opacity() const { return opacity_; } + void SetOpacity(float value); + + const gfx::Transform& transform() const { return transform_; } + void SetTransform(const gfx::Transform& transform); + + const std::map<std::string, std::vector<uint8_t>>& properties() const { + return properties_; + } + void SetProperty(const std::string& name, const std::vector<uint8_t>* value); + + // Returns true if this view is attached to |root| and all ancestors are + // visible. + bool IsDrawn(const ServerView* root) const; + + void SetSurfaceId(cc::SurfaceId surface_id); + const cc::SurfaceId& surface_id() const { return surface_id_; } + +#if !defined(NDEBUG) + std::string GetDebugWindowHierarchy() const; + void BuildDebugInfo(const std::string& depth, std::string* result) const; +#endif + + private: + typedef std::vector<ServerView*> Views; + + // Implementation of removing a view. Doesn't send any notification. + void RemoveImpl(ServerView* view); + + ServerViewDelegate* delegate_; + const ViewId id_; + ServerView* parent_; + Views children_; + bool visible_; + gfx::Rect bounds_; + cc::SurfaceId surface_id_; + float opacity_; + gfx::Transform transform_; + + std::map<std::string, std::vector<uint8_t>> properties_; + + ObserverList<ServerViewObserver> observers_; + + DISALLOW_COPY_AND_ASSIGN(ServerView); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SERVER_VIEW_H_ diff --git a/components/view_manager/server_view_delegate.h b/components/view_manager/server_view_delegate.h new file mode 100644 index 0000000..71a8750 --- /dev/null +++ b/components/view_manager/server_view_delegate.h @@ -0,0 +1,48 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ + +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +namespace gfx { +class Rect; +} + +namespace mojo { +class ViewportMetrics; +} + +namespace view_manager { + +class ServerView; + +// ServerViewDelegate is notified at key points in the lifetime of a +// ServerView. Some of the functions are similar to that of +// ServerViewObserver. For example, ServerViewDelegate::PrepareToDestroyView() +// and ServerViewObserver::OnWillDestroyView(). The key difference between +// the two are the ServerViewDelegate ones are always notified first, and +// ServerViewDelegate gets non-const arguments. +class ServerViewDelegate { + public: + // Invoked when a view is about to be destroyed; before any of the children + // have been removed and before the view has been removed from its parent. + virtual void PrepareToDestroyView(ServerView* view) = 0; + + virtual void PrepareToChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) = 0; + + virtual void PrepareToChangeViewVisibility(ServerView* view) = 0; + + virtual void OnScheduleViewPaint(const ServerView* view) = 0; + + protected: + virtual ~ServerViewDelegate() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DELEGATE_H_ diff --git a/components/view_manager/server_view_drawn_tracker.cc b/components/view_manager/server_view_drawn_tracker.cc new file mode 100644 index 0000000..4ddf4df --- /dev/null +++ b/components/view_manager/server_view_drawn_tracker.cc @@ -0,0 +1,76 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/server_view_drawn_tracker.h" + +#include "components/view_manager/server_view.h" +#include "components/view_manager/server_view_drawn_tracker_observer.h" + +namespace view_manager { + +ServerViewDrawnTracker::ServerViewDrawnTracker( + ServerView* root, + ServerView* view, + ServerViewDrawnTrackerObserver* observer) + : root_(root), + view_(view), + observer_(observer), + drawn_(view->IsDrawn(root)) { + AddObservers(); +} + +ServerViewDrawnTracker::~ServerViewDrawnTracker() { + RemoveObservers(); +} + +void ServerViewDrawnTracker::SetDrawn(ServerView* ancestor, bool drawn) { + if (drawn == drawn_) + return; + + drawn_ = drawn; + observer_->OnDrawnStateChanged(ancestor, view_, drawn); +} + +void ServerViewDrawnTracker::AddObservers() { + if (!view_) + return; + + for (ServerView* v = view_; v; v = v->parent()) { + v->AddObserver(this); + views_.insert(v); + } +} + +void ServerViewDrawnTracker::RemoveObservers() { + for (ServerView* view : views_) + view->RemoveObserver(this); + + views_.clear(); +} + +void ServerViewDrawnTracker::OnViewDestroyed(ServerView* view) { + // As views are removed before being destroyed, resulting in + // OnViewHierarchyChanged() and us removing ourself as an observer, the only + // view we should ever get notified of destruction on is |view_|. + DCHECK_EQ(view, view_); + RemoveObservers(); + view_ = nullptr; + SetDrawn(nullptr, false); +} + +void ServerViewDrawnTracker::OnViewHierarchyChanged(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { + RemoveObservers(); + AddObservers(); + const bool is_drawn = view_->IsDrawn(root_); + SetDrawn(is_drawn ? nullptr : old_parent, is_drawn); +} + +void ServerViewDrawnTracker::OnViewVisibilityChanged(ServerView* view) { + const bool is_drawn = view_->IsDrawn(root_); + SetDrawn(is_drawn ? nullptr : view->parent(), is_drawn); +} + +} // namespace view_manager diff --git a/components/view_manager/server_view_drawn_tracker.h b/components/view_manager/server_view_drawn_tracker.h new file mode 100644 index 0000000..46a9b0d --- /dev/null +++ b/components/view_manager/server_view_drawn_tracker.h @@ -0,0 +1,58 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_H_ +#define COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_H_ + +#include <set> + +#include "base/basictypes.h" +#include "components/view_manager/server_view_observer.h" + +namespace view_manager { + +class ServerViewDrawnTrackerObserver; + +// ServerViewDrawnTracker notifies its observer any time the drawn state of +// the supplied view changes. +// +// NOTE: you must ensure this class is destroyed before the root. +class ServerViewDrawnTracker : public ServerViewObserver { + public: + ServerViewDrawnTracker(ServerView* root, + ServerView* view, + ServerViewDrawnTrackerObserver* observer); + ~ServerViewDrawnTracker() override; + + ServerView* view() { return view_; } + + private: + void SetDrawn(ServerView* ancestor, bool drawn); + + // Adds |this| as an observer to |view_| and its ancestors. + void AddObservers(); + + // Stops observerving any views we added as an observer in AddObservers(). + void RemoveObservers(); + + // ServerViewObserver: + void OnViewDestroyed(ServerView* view) override; + void OnViewHierarchyChanged(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void OnViewVisibilityChanged(ServerView* view) override; + + ServerView* root_; + ServerView* view_; + ServerViewDrawnTrackerObserver* observer_; + bool drawn_; + // Set of views we're observing. This is |view_| and all its ancestors. + std::set<ServerView*> views_; + + DISALLOW_COPY_AND_ASSIGN(ServerViewDrawnTracker); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_H_ diff --git a/components/view_manager/server_view_drawn_tracker_observer.h b/components/view_manager/server_view_drawn_tracker_observer.h new file mode 100644 index 0000000..3d0f2bf --- /dev/null +++ b/components/view_manager/server_view_drawn_tracker_observer.h @@ -0,0 +1,28 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_OBSERVER_H_ +#define COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_OBSERVER_H_ + +namespace view_manager { + +class ServerView; + +class ServerViewDrawnTrackerObserver { + public: + // Invoked when the drawn state changes. If |is_drawn| is false |ancestor| + // identifies where the change occurred. In the case of a remove |ancestor| is + // the parent of the view that was removed. In the case of a visibility change + // |ancestor| is the parent of the view whose visibility changed. + virtual void OnDrawnStateChanged(ServerView* ancestor, + ServerView* view, + bool is_drawn) {} + + protected: + virtual ~ServerViewDrawnTrackerObserver() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SERVER_VIEW_DRAWN_TRACKER_OBSERVER_H_ diff --git a/components/view_manager/server_view_drawn_tracker_unittest.cc b/components/view_manager/server_view_drawn_tracker_unittest.cc new file mode 100644 index 0000000..58e34c8 --- /dev/null +++ b/components/view_manager/server_view_drawn_tracker_unittest.cc @@ -0,0 +1,136 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/server_view_drawn_tracker.h" + +#include "components/view_manager/server_view.h" +#include "components/view_manager/server_view_drawn_tracker_observer.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace view_manager { +namespace { + +class TestServerViewDrawnTrackerObserver + : public ServerViewDrawnTrackerObserver { + public: + TestServerViewDrawnTrackerObserver() + : change_count_(0u), + ancestor_(nullptr), + view_(nullptr), + is_drawn_(false) {} + + void clear_change_count() { change_count_ = 0u; } + size_t change_count() const { return change_count_; } + const ServerView* ancestor() const { return ancestor_; } + const ServerView* view() const { return view_; } + bool is_drawn() const { return is_drawn_; } + + private: + // ServerViewDrawnTrackerObserver: + void OnDrawnStateChanged(ServerView* ancestor, + ServerView* view, + bool is_drawn) override { + change_count_++; + ancestor_ = ancestor; + view_ = view; + is_drawn_ = is_drawn; + } + + size_t change_count_; + const ServerView* ancestor_; + const ServerView* view_; + bool is_drawn_; + + DISALLOW_COPY_AND_ASSIGN(TestServerViewDrawnTrackerObserver); +}; + +} // namespace + +TEST(ServerViewDrawnTrackerTest, ChangeBecauseOfDeletionAndVisibility) { + TestServerViewDelegate server_view_delegate; + scoped_ptr<ServerView> view(new ServerView(&server_view_delegate, ViewId())); + TestServerViewDrawnTrackerObserver drawn_observer; + ServerViewDrawnTracker tracker(view.get(), view.get(), &drawn_observer); + view->SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(view.get(), drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + view->SetVisible(false); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(view.get(), drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + view->SetVisible(true); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(view.get(), drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + view.reset(); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(view.get(), drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); +} + +TEST(ServerViewDrawnTrackerTest, ChangeBecauseOfRemovingFromRoot) { + TestServerViewDelegate server_view_delegate; + ServerView root(&server_view_delegate, ViewId()); + root.SetVisible(true); + ServerView child(&server_view_delegate, ViewId()); + child.SetVisible(true); + root.Add(&child); + + TestServerViewDrawnTrackerObserver drawn_observer; + ServerViewDrawnTracker tracker(&root, &child, &drawn_observer); + root.Remove(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child, drawn_observer.view()); + EXPECT_EQ(&root, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + root.Add(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child, drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); +} + +TEST(ServerViewDrawnTrackerTest, ChangeBecauseOfRemovingAncestorFromRoot) { + TestServerViewDelegate server_view_delegate; + ServerView root(&server_view_delegate, ViewId()); + root.SetVisible(true); + ServerView child(&server_view_delegate, ViewId()); + child.SetVisible(true); + root.Add(&child); + + ServerView child_child(&server_view_delegate, ViewId()); + child_child.SetVisible(true); + child.Add(&child_child); + + TestServerViewDrawnTrackerObserver drawn_observer; + ServerViewDrawnTracker tracker(&root, &child_child, &drawn_observer); + root.Remove(&child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child_child, drawn_observer.view()); + EXPECT_EQ(&root, drawn_observer.ancestor()); + EXPECT_FALSE(drawn_observer.is_drawn()); + drawn_observer.clear_change_count(); + + root.Add(&child_child); + EXPECT_EQ(1u, drawn_observer.change_count()); + EXPECT_EQ(&child_child, drawn_observer.view()); + EXPECT_EQ(nullptr, drawn_observer.ancestor()); + EXPECT_TRUE(drawn_observer.is_drawn()); +} + +} // namespace view_manager diff --git a/components/view_manager/server_view_observer.h b/components/view_manager/server_view_observer.h new file mode 100644 index 0000000..b686a2b --- /dev/null +++ b/components/view_manager/server_view_observer.h @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_SERVER_VIEW_OBSERVER_H_ +#define COMPONENTS_VIEW_MANAGER_SERVER_VIEW_OBSERVER_H_ + +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager_constants.mojom.h" + +namespace gfx { +class Rect; +} + +namespace mojo { +class ViewportMetrics; +} + +namespace view_manager { + +class ServerView; + +// TODO(sky): rename to OnDid and OnWill everywhere. +class ServerViewObserver { + public: + // Invoked when a view is about to be destroyed; before any of the children + // have been removed and before the view has been removed from its parent. + virtual void OnWillDestroyView(ServerView* view) {} + + // Invoked at the end of the View's destructor (after it has been removed from + // the hierarchy). + virtual void OnViewDestroyed(ServerView* view) {} + + virtual void OnWillChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) {} + + virtual void OnViewHierarchyChanged(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) {} + + virtual void OnViewBoundsChanged(ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) {} + + virtual void OnViewReordered(ServerView* view, + ServerView* relative, + mojo::OrderDirection direction) {} + + virtual void OnWillChangeViewVisibility(ServerView* view) {} + virtual void OnViewVisibilityChanged(ServerView* view) {} + + virtual void OnViewSharedPropertyChanged( + ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data) {} + + protected: + virtual ~ServerViewObserver() {} +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_SERVER_VIEW_OBSERVER_H_ diff --git a/components/view_manager/test_change_tracker.cc b/components/view_manager/test_change_tracker.cc new file mode 100644 index 0000000..217ddca --- /dev/null +++ b/components/view_manager/test_change_tracker.cc @@ -0,0 +1,314 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/test_change_tracker.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "mojo/common/common_type_converters.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" + +using mojo::Array; +using mojo::Id; +using mojo::ViewDataPtr; +using mojo::String; + +namespace view_manager { + +std::string ViewIdToString(Id id) { + return (id == 0) ? "null" : base::StringPrintf("%d,%d", mojo::HiWord(id), + mojo::LoWord(id)); +} + +namespace { + +std::string RectToString(const mojo::Rect& rect) { + return base::StringPrintf("%d,%d %dx%d", rect.x, rect.y, rect.width, + rect.height); +} + +std::string DirectionToString(mojo::OrderDirection direction) { + return direction == mojo::ORDER_DIRECTION_ABOVE ? "above" : "below"; +} + +std::string ChangeToDescription1(const Change& change) { + switch (change.type) { + case CHANGE_TYPE_EMBED: + return base::StringPrintf("OnEmbed creator=%s", + change.creator_url.data()); + + case CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED: + return base::StringPrintf("OnEmbeddedAppDisconnected view=%s", + ViewIdToString(change.view_id).c_str()); + + case CHANGE_TYPE_NODE_BOUNDS_CHANGED: + return base::StringPrintf( + "BoundsChanged view=%s old_bounds=%s new_bounds=%s", + ViewIdToString(change.view_id).c_str(), + RectToString(change.bounds).c_str(), + RectToString(change.bounds2).c_str()); + + case CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED: + // TODO(sky): Not implemented. + return "ViewportMetricsChanged"; + + case CHANGE_TYPE_NODE_HIERARCHY_CHANGED: + return base::StringPrintf( + "HierarchyChanged view=%s new_parent=%s old_parent=%s", + ViewIdToString(change.view_id).c_str(), + ViewIdToString(change.view_id2).c_str(), + ViewIdToString(change.view_id3).c_str()); + + case CHANGE_TYPE_NODE_REORDERED: + return base::StringPrintf("Reordered view=%s relative=%s direction=%s", + ViewIdToString(change.view_id).c_str(), + ViewIdToString(change.view_id2).c_str(), + DirectionToString(change.direction).c_str()); + + case CHANGE_TYPE_NODE_DELETED: + return base::StringPrintf("ViewDeleted view=%s", + ViewIdToString(change.view_id).c_str()); + + case CHANGE_TYPE_NODE_VISIBILITY_CHANGED: + return base::StringPrintf("VisibilityChanged view=%s visible=%s", + ViewIdToString(change.view_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED: + return base::StringPrintf("DrawnStateChanged view=%s drawn=%s", + ViewIdToString(change.view_id).c_str(), + change.bool_value ? "true" : "false"); + + case CHANGE_TYPE_INPUT_EVENT: + return base::StringPrintf("InputEvent view=%s event_action=%d", + ViewIdToString(change.view_id).c_str(), + change.event_action); + + case CHANGE_TYPE_PROPERTY_CHANGED: + return base::StringPrintf("PropertyChanged view=%s key=%s value=%s", + ViewIdToString(change.view_id).c_str(), + change.property_key.c_str(), + change.property_value.c_str()); + + case CHANGE_TYPE_DELEGATE_EMBED: + return base::StringPrintf("DelegateEmbed url=%s", + change.embed_url.data()); + } + return std::string(); +} + +} // namespace + +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes) { + std::vector<std::string> strings(changes.size()); + for (size_t i = 0; i < changes.size(); ++i) + strings[i] = ChangeToDescription1(changes[i]); + return strings; +} + +std::string SingleChangeToDescription(const std::vector<Change>& changes) { + if (changes.size() != 1u) + return std::string(); + return ChangeToDescription1(changes[0]); +} + +std::string SingleViewDescription(const std::vector<TestView>& views) { + if (views.size() != 1u) + return std::string(); + return views[0].ToString(); +} + +std::string ChangeViewDescription(const std::vector<Change>& changes) { + if (changes.size() != 1) + return std::string(); + std::vector<std::string> view_strings(changes[0].views.size()); + for (size_t i = 0; i < changes[0].views.size(); ++i) + view_strings[i] = "[" + changes[0].views[i].ToString() + "]"; + return JoinString(view_strings, ','); +} + +TestView ViewDataToTestView(const ViewDataPtr& data) { + TestView view; + view.parent_id = data->parent_id; + view.view_id = data->view_id; + view.visible = data->visible; + view.drawn = data->drawn; + view.properties = + data->properties.To<std::map<std::string, std::vector<uint8_t>>>(); + return view; +} + +void ViewDatasToTestViews(const Array<ViewDataPtr>& data, + std::vector<TestView>* test_views) { + for (size_t i = 0; i < data.size(); ++i) + test_views->push_back(ViewDataToTestView(data[i])); +} + +Change::Change() + : type(CHANGE_TYPE_EMBED), + connection_id(0), + view_id(0), + view_id2(0), + view_id3(0), + event_action(0), + direction(mojo::ORDER_DIRECTION_ABOVE), + bool_value(false) { +} + +Change::~Change() { +} + +TestChangeTracker::TestChangeTracker() + : delegate_(NULL) { +} + +TestChangeTracker::~TestChangeTracker() { +} + +void TestChangeTracker::OnEmbed(mojo::ConnectionSpecificId connection_id, + const String& creator_url, + ViewDataPtr root) { + Change change; + change.type = CHANGE_TYPE_EMBED; + change.connection_id = connection_id; + change.creator_url = creator_url; + change.views.push_back(ViewDataToTestView(root)); + AddChange(change); +} + +void TestChangeTracker::OnEmbeddedAppDisconnected(Id view_id) { + Change change; + change.type = CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED; + change.view_id = view_id; + AddChange(change); +} + +void TestChangeTracker::OnViewBoundsChanged(Id view_id, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds) { + Change change; + change.type = CHANGE_TYPE_NODE_BOUNDS_CHANGED; + change.view_id = view_id; + change.bounds.x = old_bounds->x; + change.bounds.y = old_bounds->y; + change.bounds.width = old_bounds->width; + change.bounds.height = old_bounds->height; + change.bounds2.x = new_bounds->x; + change.bounds2.y = new_bounds->y; + change.bounds2.width = new_bounds->width; + change.bounds2.height = new_bounds->height; + AddChange(change); +} + +void TestChangeTracker::OnViewViewportMetricsChanged( + mojo::ViewportMetricsPtr old_metrics, + mojo::ViewportMetricsPtr new_metrics) { + Change change; + change.type = CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED; + // NOT IMPLEMENTED + AddChange(change); +} + +void TestChangeTracker::OnViewHierarchyChanged(Id view_id, + Id new_parent_id, + Id old_parent_id, + Array<ViewDataPtr> views) { + Change change; + change.type = CHANGE_TYPE_NODE_HIERARCHY_CHANGED; + change.view_id = view_id; + change.view_id2 = new_parent_id; + change.view_id3 = old_parent_id; + ViewDatasToTestViews(views, &change.views); + AddChange(change); +} + +void TestChangeTracker::OnViewReordered(Id view_id, + Id relative_view_id, + mojo::OrderDirection direction) { + Change change; + change.type = CHANGE_TYPE_NODE_REORDERED; + change.view_id = view_id; + change.view_id2 = relative_view_id; + change.direction = direction; + AddChange(change); +} + +void TestChangeTracker::OnViewDeleted(Id view_id) { + Change change; + change.type = CHANGE_TYPE_NODE_DELETED; + change.view_id = view_id; + AddChange(change); +} + +void TestChangeTracker::OnViewVisibilityChanged(Id view_id, bool visible) { + Change change; + change.type = CHANGE_TYPE_NODE_VISIBILITY_CHANGED; + change.view_id = view_id; + change.bool_value = visible; + AddChange(change); +} + +void TestChangeTracker::OnViewDrawnStateChanged(Id view_id, bool drawn) { + Change change; + change.type = CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED; + change.view_id = view_id; + change.bool_value = drawn; + AddChange(change); +} + +void TestChangeTracker::OnViewInputEvent(Id view_id, mojo::EventPtr event) { + Change change; + change.type = CHANGE_TYPE_INPUT_EVENT; + change.view_id = view_id; + change.event_action = event->action; + AddChange(change); +} + +void TestChangeTracker::OnViewSharedPropertyChanged(Id view_id, + String name, + Array<uint8_t> data) { + Change change; + change.type = CHANGE_TYPE_PROPERTY_CHANGED; + change.view_id = view_id; + change.property_key = name; + if (data.is_null()) + change.property_value = "NULL"; + else + change.property_value = data.To<std::string>(); + AddChange(change); +} + +void TestChangeTracker::DelegateEmbed(const String& url) { + Change change; + change.type = CHANGE_TYPE_DELEGATE_EMBED; + change.embed_url = url; + AddChange(change); +} + +void TestChangeTracker::AddChange(const Change& change) { + changes_.push_back(change); + if (delegate_) + delegate_->OnChangeAdded(); +} + +TestView::TestView() {} + +TestView::~TestView() {} + +std::string TestView::ToString() const { + return base::StringPrintf("view=%s parent=%s", + ViewIdToString(view_id).c_str(), + ViewIdToString(parent_id).c_str()); +} + +std::string TestView::ToString2() const { + return base::StringPrintf("view=%s parent=%s visible=%s drawn=%s", + ViewIdToString(view_id).c_str(), + ViewIdToString(parent_id).c_str(), + visible ? "true" : "false", + drawn ? "true" : "false"); +} + +} // namespace view_manager diff --git a/components/view_manager/test_change_tracker.h b/components/view_manager/test_change_tracker.h new file mode 100644 index 0000000..bb1b55e --- /dev/null +++ b/components/view_manager/test_change_tracker.h @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ +#define COMPONENTS_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/array.h" +#include "third_party/mojo_services/src/geometry/public/interfaces/geometry.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace view_manager { + +enum ChangeType { + CHANGE_TYPE_EMBED, + CHANGE_TYPE_EMBEDDED_APP_DISCONNECTED, + // TODO(sky): NODE->VIEW. + CHANGE_TYPE_NODE_BOUNDS_CHANGED, + CHANGE_TYPE_NODE_VIEWPORT_METRICS_CHANGED, + CHANGE_TYPE_NODE_HIERARCHY_CHANGED, + CHANGE_TYPE_NODE_REORDERED, + CHANGE_TYPE_NODE_VISIBILITY_CHANGED, + CHANGE_TYPE_NODE_DRAWN_STATE_CHANGED, + CHANGE_TYPE_NODE_DELETED, + CHANGE_TYPE_INPUT_EVENT, + CHANGE_TYPE_PROPERTY_CHANGED, + CHANGE_TYPE_DELEGATE_EMBED, +}; + +// TODO(sky): consider nuking and converting directly to ViewData. +struct TestView { + TestView(); + ~TestView(); + + // Returns a string description of this. + std::string ToString() const; + + // Returns a string description that includes visible and drawn. + std::string ToString2() const; + + mojo::Id parent_id; + mojo::Id view_id; + bool visible; + bool drawn; + std::map<std::string, std::vector<uint8_t>> properties; +}; + +// Tracks a call to ViewManagerClient. See the individual functions for the +// fields that are used. +struct Change { + Change(); + ~Change(); + + ChangeType type; + mojo::ConnectionSpecificId connection_id; + std::vector<TestView> views; + mojo::Id view_id; + mojo::Id view_id2; + mojo::Id view_id3; + mojo::Rect bounds; + mojo::Rect bounds2; + int32_t event_action; + mojo::String creator_url; + mojo::String embed_url; + mojo::OrderDirection direction; + bool bool_value; + std::string property_key; + std::string property_value; +}; + +// Converts Changes to string descriptions. +std::vector<std::string> ChangesToDescription1( + const std::vector<Change>& changes); + +// Convenience for returning the description of the first item in |changes|. +// Returns an empty string if |changes| has something other than one entry. +std::string SingleChangeToDescription(const std::vector<Change>& changes); + +// Convenience for returning the description of the first item in |views|. +// Returns an empty string if |views| has something other than one entry. +std::string SingleViewDescription(const std::vector<TestView>& views); + +// Returns a string description of |changes[0].views|. Returns an empty string +// if change.size() != 1. +std::string ChangeViewDescription(const std::vector<Change>& changes); + +// Converts ViewDatas to TestViews. +void ViewDatasToTestViews(const mojo::Array<mojo::ViewDataPtr>& data, + std::vector<TestView>* test_views); + +// TestChangeTracker is used to record ViewManagerClient functions. It notifies +// a delegate any time a change is added. +class TestChangeTracker { + public: + // Used to notify the delegate when a change is added. A change corresponds to + // a single ViewManagerClient function. + class Delegate { + public: + virtual void OnChangeAdded() = 0; + + protected: + virtual ~Delegate() {} + }; + + TestChangeTracker(); + ~TestChangeTracker(); + + void set_delegate(Delegate* delegate) { + delegate_ = delegate; + } + + std::vector<Change>* changes() { return &changes_; } + + // Each of these functions generate a Change. There is one per + // ViewManagerClient function. + void OnEmbed(mojo::ConnectionSpecificId connection_id, + const mojo::String& creator_url, + mojo::ViewDataPtr root); + void OnEmbeddedAppDisconnected(mojo::Id view_id); + void OnViewBoundsChanged(mojo::Id view_id, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds); + void OnViewViewportMetricsChanged(mojo::ViewportMetricsPtr old_bounds, + mojo::ViewportMetricsPtr new_bounds); + void OnViewHierarchyChanged(mojo::Id view_id, + mojo::Id new_parent_id, + mojo::Id old_parent_id, + mojo::Array<mojo::ViewDataPtr> views); + void OnViewReordered(mojo::Id view_id, + mojo::Id relative_view_id, + mojo::OrderDirection direction); + void OnViewDeleted(mojo::Id view_id); + void OnViewVisibilityChanged(mojo::Id view_id, bool visible); + void OnViewDrawnStateChanged(mojo::Id view_id, bool drawn); + void OnViewInputEvent(mojo::Id view_id, mojo::EventPtr event); + void OnViewSharedPropertyChanged(mojo::Id view_id, + mojo::String name, + mojo::Array<uint8_t> data); + void DelegateEmbed(const mojo::String& url); + + private: + void AddChange(const Change& change); + + Delegate* delegate_; + std::vector<Change> changes_; + + DISALLOW_COPY_AND_ASSIGN(TestChangeTracker); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_TEST_CHANGE_TRACKER_H_ diff --git a/components/view_manager/test_server_view_delegate.cc b/components/view_manager/test_server_view_delegate.cc new file mode 100644 index 0000000..9a03712 --- /dev/null +++ b/components/view_manager/test_server_view_delegate.cc @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/test_server_view_delegate.h" + +namespace view_manager { + +TestServerViewDelegate::TestServerViewDelegate() { +} + +TestServerViewDelegate::~TestServerViewDelegate() { +} + +void TestServerViewDelegate::PrepareToDestroyView(ServerView* view) { +} + +void TestServerViewDelegate::PrepareToChangeViewHierarchy( + ServerView* view, + ServerView* new_parent, + ServerView* old_parent) { +} + +void TestServerViewDelegate::PrepareToChangeViewVisibility(ServerView* view) { +} + +void TestServerViewDelegate::OnScheduleViewPaint(const ServerView* view) { +} + +} // namespace view_manager diff --git a/components/view_manager/test_server_view_delegate.h b/components/view_manager/test_server_view_delegate.h new file mode 100644 index 0000000..b432752 --- /dev/null +++ b/components/view_manager/test_server_view_delegate.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ +#define COMPONENTS_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ + +#include "base/basictypes.h" +#include "components/view_manager/server_view_delegate.h" + +namespace view_manager { + +class TestServerViewDelegate : public ServerViewDelegate { + public: + TestServerViewDelegate(); + ~TestServerViewDelegate() override; + + private: + // ServerViewDelegate: + void PrepareToDestroyView(ServerView* view) override; + void PrepareToChangeViewHierarchy(ServerView* view, + ServerView* new_parent, + ServerView* old_parent) override; + void PrepareToChangeViewVisibility(ServerView* view) override; + void OnScheduleViewPaint(const ServerView* view) override; + + DISALLOW_COPY_AND_ASSIGN(TestServerViewDelegate); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_TEST_SERVER_VIEW_DELEGATE_H_ diff --git a/components/view_manager/view_coordinate_conversions.cc b/components/view_manager/view_coordinate_conversions.cc new file mode 100644 index 0000000..8237875 --- /dev/null +++ b/components/view_manager/view_coordinate_conversions.cc @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_coordinate_conversions.h" + +#include "components/view_manager/server_view.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/geometry/vector2d_f.h" + +namespace view_manager { + +namespace { + +gfx::Vector2dF CalculateOffsetToAncestor(const ServerView* view, + const ServerView* ancestor) { + DCHECK(ancestor->Contains(view)); + gfx::Vector2d result; + for (const ServerView* v = view; v != ancestor; v = v->parent()) + result += v->bounds().OffsetFromOrigin(); + return gfx::Vector2dF(result.x(), result.y()); +} + +} // namespace + +gfx::Point ConvertPointBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Point& point) { + return gfx::ToFlooredPoint( + ConvertPointFBetweenViews(from, to, gfx::PointF(point.x(), point.y()))); +} + +gfx::PointF ConvertPointFBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::PointF& point) { + DCHECK(from); + DCHECK(to); + if (from == to) + return point; + + if (from->Contains(to)) { + const gfx::Vector2dF offset(CalculateOffsetToAncestor(to, from)); + return point - offset; + } + DCHECK(to->Contains(from)); + const gfx::Vector2dF offset(CalculateOffsetToAncestor(from, to)); + return point + offset; +} + +gfx::Rect ConvertRectBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Rect& rect) { + DCHECK(from); + DCHECK(to); + if (from == to) + return rect; + + const gfx::Point top_left(ConvertPointBetweenViews(from, to, rect.origin())); + const gfx::Point bottom_right(gfx::ToCeiledPoint(ConvertPointFBetweenViews( + from, to, gfx::PointF(rect.right(), rect.bottom())))); + return gfx::Rect(top_left.x(), top_left.y(), bottom_right.x() - top_left.x(), + bottom_right.y() - top_left.y()); +} + +} // namespace view_manager diff --git a/components/view_manager/view_coordinate_conversions.h b/components/view_manager/view_coordinate_conversions.h new file mode 100644 index 0000000..e232510 --- /dev/null +++ b/components/view_manager/view_coordinate_conversions.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 COMPONENTS_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ +#define COMPONENTS_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ + +namespace gfx { +class Point; +class PointF; +class Rect; +} + +namespace view_manager { + +class ServerView; + +// Converts |point| from the coordinates of |from| to the coordinates of |to|. +// |from| and |to| must be an ancestors or descendants of each other. +gfx::Point ConvertPointBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Point& point); +gfx::PointF ConvertPointFBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::PointF& point); + +// Converts |rect| from the coordinates of |from| to the coordinates of |to|. +// |from| and |to| must be an ancestors or descendants of each other. +gfx::Rect ConvertRectBetweenViews(const ServerView* from, + const ServerView* to, + const gfx::Rect& rect); + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_VIEW_COORDINATE_CONVERSIONS_H_ diff --git a/components/view_manager/view_coordinate_conversions_unittest.cc b/components/view_manager/view_coordinate_conversions_unittest.cc new file mode 100644 index 0000000..addd466 --- /dev/null +++ b/components/view_manager/view_coordinate_conversions_unittest.cc @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_coordinate_conversions.h" + +#include "components/view_manager/server_view.h" +#include "components/view_manager/server_view_delegate.h" +#include "components/view_manager/test_server_view_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" + +namespace view_manager { + +using ViewCoordinateConversionsTest = testing::Test; + +TEST_F(ViewCoordinateConversionsTest, ConvertRectBetweenViews) { + TestServerViewDelegate d1, d2, d3; + ServerView v1(&d1, ViewId()), v2(&d2, ViewId()), v3(&d3, ViewId()); + v1.SetBounds(gfx::Rect(1, 2, 100, 100)); + v2.SetBounds(gfx::Rect(3, 4, 100, 100)); + v3.SetBounds(gfx::Rect(5, 6, 100, 100)); + v1.Add(&v2); + v2.Add(&v3); + + EXPECT_EQ(gfx::Rect(2, 1, 8, 9), + ConvertRectBetweenViews(&v1, &v3, gfx::Rect(10, 11, 8, 9))); + + EXPECT_EQ(gfx::Rect(18, 21, 8, 9), + ConvertRectBetweenViews(&v3, &v1, gfx::Rect(10, 11, 8, 9))); +} + +TEST_F(ViewCoordinateConversionsTest, ConvertPointFBetweenViews) { + TestServerViewDelegate d1, d2, d3; + ServerView v1(&d1, ViewId()), v2(&d2, ViewId()), v3(&d3, ViewId()); + v1.SetBounds(gfx::Rect(1, 2, 100, 100)); + v2.SetBounds(gfx::Rect(3, 4, 100, 100)); + v3.SetBounds(gfx::Rect(5, 6, 100, 100)); + v1.Add(&v2); + v2.Add(&v3); + + { + const gfx::PointF result( + ConvertPointFBetweenViews(&v1, &v3, gfx::PointF(10.5f, 11.9f))); + EXPECT_FLOAT_EQ(2.5f, result.x()); + EXPECT_FLOAT_EQ(1.9f, result.y()); + } + + { + const gfx::PointF result( + ConvertPointFBetweenViews(&v3, &v1, gfx::PointF(10.2f, 11.4f))); + EXPECT_FLOAT_EQ(18.2f, result.x()); + EXPECT_FLOAT_EQ(21.4f, result.y()); + } +} + +} // namespace view_manager diff --git a/components/view_manager/view_locator.cc b/components/view_manager/view_locator.cc new file mode 100644 index 0000000..415eac8 --- /dev/null +++ b/components/view_manager/view_locator.cc @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_locator.h" + +#include "components/view_manager/server_view.h" + +namespace view_manager { + +const ServerView* FindDeepestVisibleView(const ServerView* view, + const gfx::Point& location) { + for (const ServerView* child : view->GetChildren()) { + if (!child->visible()) + continue; + + // TODO(sky): support transform. + const gfx::Point child_location(location.x() - child->bounds().x(), + location.y() - child->bounds().y()); + if (child_location.x() >= 0 && child_location.y() >= 0 && + child_location.x() < child->bounds().width() && + child_location.y() < child->bounds().height()) { + return FindDeepestVisibleView(child, child_location); + } + } + return view; +} + +ServerView* FindDeepestVisibleView(ServerView* view, + const gfx::Point& location) { + return const_cast<ServerView*>( + FindDeepestVisibleView(const_cast<const ServerView*>(view), location)); +} + +} // namespace view_manager diff --git a/components/view_manager/view_locator.h b/components/view_manager/view_locator.h new file mode 100644 index 0000000..13a1f0c --- /dev/null +++ b/components/view_manager/view_locator.h @@ -0,0 +1,24 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_VIEW_LOCATOR_H_ +#define COMPONENTS_VIEW_MANAGER_VIEW_LOCATOR_H_ + +namespace gfx { +class Point; +} + +namespace view_manager { + +class ServerView; + +// Finds the deepest visible view that contains the specified location. +const ServerView* FindDeepestVisibleView(const ServerView* view, + const gfx::Point& location); +ServerView* FindDeepestVisibleView(ServerView* view, + const gfx::Point& location); + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_VIEW_LOCATOR_H_ diff --git a/components/view_manager/view_manager_app.cc b/components/view_manager/view_manager_app.cc new file mode 100644 index 0000000..a37119c --- /dev/null +++ b/components/view_manager/view_manager_app.cc @@ -0,0 +1,137 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_manager_app.h" + +#include "components/view_manager/client_connection.h" +#include "components/view_manager/connection_manager.h" +#include "components/view_manager/display_manager.h" +#include "components/view_manager/view_manager_service_impl.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/common/tracing_impl.h" +#include "third_party/mojo/src/mojo/public/c/system/main.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" + +using mojo::ApplicationConnection; +using mojo::ApplicationImpl; +using mojo::InterfaceRequest; +using mojo::ViewManagerService; +using mojo::WindowManagerInternalClient; + +namespace view_manager { + +ViewManagerApp::ViewManagerApp() + : app_impl_(nullptr), wm_app_connection_(nullptr) { +} + +ViewManagerApp::~ViewManagerApp() {} + +void ViewManagerApp::Initialize(ApplicationImpl* app) { + app_impl_ = app; + tracing_.Initialize(app); +} + +bool ViewManagerApp::ConfigureIncomingConnection( + ApplicationConnection* connection) { + if (connection_manager_.get()) { + VLOG(1) << "ViewManager allows only one window manager connection."; + return false; + } + wm_app_connection_ = connection; + // |connection| originates from the WindowManager. Let it connect directly + // to the ViewManager and WindowManagerInternalClient. + connection->AddService<ViewManagerService>(this); + connection->AddService<WindowManagerInternalClient>(this); + connection->ConnectToService(&wm_internal_); + // If no ServiceProvider has been sent, refuse the connection. + if (!wm_internal_) + return false; + wm_internal_.set_error_handler(this); + + scoped_ptr<DefaultDisplayManager> display_manager(new DefaultDisplayManager( + app_impl_, connection, + base::Bind(&ViewManagerApp::OnLostConnectionToWindowManager, + base::Unretained(this)))); + connection_manager_.reset( + new ConnectionManager(this, display_manager.Pass(), wm_internal_.get())); + return true; +} + +void ViewManagerApp::OnLostConnectionToWindowManager() { + ApplicationImpl::Terminate(); +} + +ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) { + mojo::ViewManagerClientPtr client; + app_impl_->ConnectToService(url, &client); + + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, url, root_id)); + return new DefaultClientConnection(service.Pass(), connection_manager, + service_request.Pass(), client.Pass()); +} + +ClientConnection* ViewManagerApp::CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) { + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, std::string(), root_id)); + return new DefaultClientConnection(service.Pass(), connection_manager, + service_request.Pass(), + view_manager_client.Pass()); +} + +void ViewManagerApp::Create(ApplicationConnection* connection, + InterfaceRequest<ViewManagerService> request) { + if (connection_manager_->has_window_manager_client_connection()) { + VLOG(1) << "ViewManager interface requested more than once."; + return; + } + + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager_.get(), kInvalidConnectionId, std::string(), + std::string("mojo:window_manager"), RootViewId())); + mojo::ViewManagerClientPtr client; + wm_internal_client_request_ = GetProxy(&client); + scoped_ptr<ClientConnection> client_connection( + new DefaultClientConnection(service.Pass(), connection_manager_.get(), + request.Pass(), client.Pass())); + connection_manager_->SetWindowManagerClientConnection( + client_connection.Pass()); +} + +void ViewManagerApp::Create( + ApplicationConnection* connection, + InterfaceRequest<WindowManagerInternalClient> request) { + if (wm_internal_client_binding_.get()) { + VLOG(1) << "WindowManagerInternalClient requested more than once."; + return; + } + + // ConfigureIncomingConnection() must have been called before getting here. + DCHECK(connection_manager_.get()); + wm_internal_client_binding_.reset( + new mojo::Binding<WindowManagerInternalClient>(connection_manager_.get(), + request.Pass())); + wm_internal_client_binding_->set_error_handler(this); + wm_internal_->SetViewManagerClient( + wm_internal_client_request_.PassMessagePipe()); +} + +void ViewManagerApp::OnConnectionError() { + ApplicationImpl::Terminate(); +} + +} // namespace view_manager diff --git a/components/view_manager/view_manager_app.h b/components/view_manager/view_manager_app.h new file mode 100644 index 0000000..3039a8a --- /dev/null +++ b/components/view_manager/view_manager_app.h @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_APP_H_ +#define COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_APP_H_ + +#include "base/memory/scoped_ptr.h" +#include "components/view_manager/connection_manager_delegate.h" +#include "mojo/common/tracing_impl.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_delegate.h" +#include "third_party/mojo/src/mojo/public/cpp/application/interface_factory.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h" +#include "third_party/mojo/src/mojo/public/cpp/bindings/error_handler.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +namespace mojo { +class ApplicationImpl; +} + +namespace view_manager { + +class ConnectionManager; + +class ViewManagerApp + : public mojo::ApplicationDelegate, + public ConnectionManagerDelegate, + public mojo::ErrorHandler, + public mojo::InterfaceFactory<mojo::ViewManagerService>, + public mojo::InterfaceFactory<mojo::WindowManagerInternalClient> { + public: + ViewManagerApp(); + ~ViewManagerApp() override; + + private: + // ApplicationDelegate: + void Initialize(mojo::ApplicationImpl* app) override; + bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) override; + + // ConnectionManagerDelegate: + void OnLostConnectionToWindowManager() override; + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) override; + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr view_manager_client) override; + + // mojo::InterfaceFactory<mojo::ViewManagerService>: + void Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::ViewManagerService> request) override; + + // mojo::InterfaceFactory<mojo::WindowManagerInternalClient>: + void Create(mojo::ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternalClient> request) + override; + + // ErrorHandler (for |wm_internal_| and |wm_internal_client_binding_|). + void OnConnectionError() override; + + mojo::ApplicationImpl* app_impl_; + mojo::ApplicationConnection* wm_app_connection_; + scoped_ptr<mojo::Binding<mojo::WindowManagerInternalClient>> + wm_internal_client_binding_; + mojo::InterfaceRequest<mojo::ViewManagerClient> wm_internal_client_request_; + mojo::WindowManagerInternalPtr wm_internal_; + scoped_ptr<ConnectionManager> connection_manager_; + mojo::TracingImpl tracing_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerApp); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_APP_H_ diff --git a/components/view_manager/view_manager_client_apptest.cc b/components/view_manager/view_manager_client_apptest.cc new file mode 100644 index 0000000..2131a88 --- /dev/null +++ b/components/view_manager/view_manager_client_apptest.cc @@ -0,0 +1,613 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager.h" + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/test_timeouts.h" +#include "mojo/application/application_test_base_chromium.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_connection.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_delegate.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" +#include "third_party/mojo/src/mojo/public/cpp/application/service_provider_impl.h" +#include "third_party/mojo_services/src/geometry/public/cpp/geometry_util.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_manager_client_impl.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_client_factory.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_context.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_delegate.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/view_observer.h" + +namespace mojo { + +namespace { + +base::RunLoop* current_run_loop = nullptr; + +void TimeoutRunLoop(const base::Closure& timeout_task, bool* timeout) { + CHECK(current_run_loop); + *timeout = true; + timeout_task.Run(); +} + +bool DoRunLoopWithTimeout() { + if (current_run_loop != nullptr) + return false; + + bool timeout = false; + base::RunLoop run_loop; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(&TimeoutRunLoop, run_loop.QuitClosure(), &timeout), + TestTimeouts::action_timeout()); + + current_run_loop = &run_loop; + current_run_loop->Run(); + current_run_loop = nullptr; + return !timeout; +} + +void QuitRunLoop() { + current_run_loop->Quit(); + current_run_loop = nullptr; +} + +class BoundsChangeObserver : public ViewObserver { + public: + explicit BoundsChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~BoundsChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewBoundsChanged(View* view, + const Rect& old_bounds, + const Rect& new_bounds) override { + DCHECK_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver); +}; + +// Wait until the bounds of the supplied view change; returns false on timeout. +bool WaitForBoundsToChange(View* view) { + BoundsChangeObserver observer(view); + return DoRunLoopWithTimeout(); +} + +// Spins a run loop until the tree beginning at |root| has |tree_size| views +// (including |root|). +class TreeSizeMatchesObserver : public ViewObserver { + public: + TreeSizeMatchesObserver(View* tree, size_t tree_size) + : tree_(tree), tree_size_(tree_size) { + tree_->AddObserver(this); + } + ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); } + + bool IsTreeCorrectSize() { return CountViews(tree_) == tree_size_; } + + private: + // Overridden from ViewObserver: + void OnTreeChanged(const TreeChangeParams& params) override { + if (IsTreeCorrectSize()) + QuitRunLoop(); + } + + size_t CountViews(const View* view) const { + size_t count = 1; + View::Children::const_iterator it = view->children().begin(); + for (; it != view->children().end(); ++it) + count += CountViews(*it); + return count; + } + + View* tree_; + size_t tree_size_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver); +}; + +// Wait until |view|'s tree size matches |tree_size|; returns false on timeout. +bool WaitForTreeSizeToMatch(View* view, size_t tree_size) { + TreeSizeMatchesObserver observer(view, tree_size); + return observer.IsTreeCorrectSize() || DoRunLoopWithTimeout(); +} + +class OrderChangeObserver : public ViewObserver { + public: + OrderChangeObserver(View* view) : view_(view) { view_->AddObserver(this); } + ~OrderChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewReordered(View* view, + View* relative_view, + OrderDirection direction) override { + DCHECK_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver); +}; + +// Wait until |view|'s tree size matches |tree_size|; returns false on timeout. +bool WaitForOrderChange(ViewManager* view_manager, View* view) { + OrderChangeObserver observer(view); + return DoRunLoopWithTimeout(); +} + +// Tracks a view's destruction. Query is_valid() for current state. +class ViewTracker : public ViewObserver { + public: + explicit ViewTracker(View* view) : view_(view) { view_->AddObserver(this); } + ~ViewTracker() override { + if (view_) + view_->RemoveObserver(this); + } + + bool is_valid() const { return !!view_; } + + private: + // Overridden from ViewObserver: + void OnViewDestroyed(View* view) override { + DCHECK_EQ(view, view_); + view_ = nullptr; + } + + int id_; + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker); +}; + +} // namespace + +// ViewManager ----------------------------------------------------------------- + +// These tests model synchronization of two peer connections to the view manager +// service, that are given access to some root view. + +class ViewManagerTest : public test::ApplicationTestBase, + public ApplicationDelegate, + public ViewManagerDelegate { + public: + ViewManagerTest() + : most_recent_view_manager_(nullptr), window_manager_(nullptr) {} + + // Overridden from ApplicationDelegate: + void Initialize(ApplicationImpl* app) override { + view_manager_client_factory_.reset( + new ViewManagerClientFactory(app->shell(), this)); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(view_manager_client_factory_.get()); + return true; + } + + ViewManager* window_manager() { return window_manager_; } + + // Embeds another version of the test app @ view; returns nullptr on timeout. + ViewManager* Embed(ViewManager* view_manager, View* view) { + DCHECK_EQ(view_manager, view->view_manager()); + most_recent_view_manager_ = nullptr; + view->Embed(application_impl()->url()); + if (!DoRunLoopWithTimeout()) + return nullptr; + ViewManager* vm = nullptr; + std::swap(vm, most_recent_view_manager_); + return vm; + } + + ApplicationDelegate* GetApplicationDelegate() override { return this; } + + // Overridden from ViewManagerDelegate: + void OnEmbed(View* root, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) override { + most_recent_view_manager_ = root->view_manager(); + QuitRunLoop(); + } + void OnViewManagerDisconnected(ViewManager* view_manager) override {} + + private: + // Overridden from testing::Test: + void SetUp() override { + ApplicationTestBase::SetUp(); + + view_manager_context_.reset(new ViewManagerContext(application_impl())); + view_manager_context_->Embed(application_impl()->url()); + ASSERT_TRUE(DoRunLoopWithTimeout()); + std::swap(window_manager_, most_recent_view_manager_); + } + + // Overridden from testing::Test: + void TearDown() override { ApplicationTestBase::TearDown(); } + + scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; + + scoped_ptr<ViewManagerContext> view_manager_context_; + + // Used to receive the most recent view manager loaded by an embed action. + ViewManager* most_recent_view_manager_; + // The View Manager connection held by the window manager (app running at the + // root view). + ViewManager* window_manager_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); +}; + +TEST_F(ViewManagerTest, RootView) { + ASSERT_NE(nullptr, window_manager()); + EXPECT_NE(nullptr, window_manager()->GetRoot()); + EXPECT_EQ("mojo:window_manager", window_manager()->GetEmbedderURL()); +} + +TEST_F(ViewManagerTest, Embed) { + View* view = window_manager()->CreateView(); + ASSERT_NE(nullptr, view); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetRoot(); + ASSERT_NE(nullptr, view_in_embedded); + EXPECT_EQ(view->id(), view_in_embedded->id()); + EXPECT_EQ(nullptr, view_in_embedded->parent()); + EXPECT_TRUE(view_in_embedded->children().empty()); +} + +// Window manager has two views, N1 and N11. Embeds A at N1. A should not see +// N11. +TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) { + View* view = window_manager()->CreateView(); + ASSERT_NE(nullptr, view); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + View* nested = window_manager()->CreateView(); + ASSERT_NE(nullptr, nested); + nested->SetVisible(true); + view->AddChild(nested); + + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + View* view_in_embedded = embedded->GetRoot(); + EXPECT_EQ(view->id(), view_in_embedded->id()); + EXPECT_EQ(nullptr, view_in_embedded->parent()); + EXPECT_TRUE(view_in_embedded->children().empty()); +} + +// TODO(beng): write a replacement test for the one that once existed here: +// This test validates the following scenario: +// - a view originating from one connection +// - a view originating from a second connection +// + the connection originating the view is destroyed +// -> the view should still exist (since the second connection is live) but +// should be disconnected from any views. +// http://crbug.com/396300 +// +// TODO(beng): The new test should validate the scenario as described above +// except that the second connection still has a valid tree. + +// Verifies that bounds changes applied to a view hierarchy in one connection +// are reflected to another. +TEST_F(ViewManagerTest, SetBounds) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); + + Rect rect; + rect.width = rect.height = 100; + view->SetBounds(rect); + ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); +} + +// Verifies that bounds changes applied to a view owned by a different +// connection are refused. +TEST_F(ViewManagerTest, SetBoundsSecurity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + Rect rect; + rect.width = 800; + rect.height = 600; + view->SetBounds(rect); + ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); + + rect.width = 1024; + rect.height = 768; + view_in_embedded->SetBounds(rect); + // Bounds change should have been rejected. + EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); +} + +// Verifies that a view can only be destroyed by the connection that created it. +TEST_F(ViewManagerTest, DestroySecurity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + + View* view_in_embedded = embedded->GetViewById(view->id()); + + ViewTracker tracker2(view_in_embedded); + view_in_embedded->Destroy(); + // View should not have been destroyed. + EXPECT_TRUE(tracker2.is_valid()); + + ViewTracker tracker1(view); + view->Destroy(); + EXPECT_FALSE(tracker1.is_valid()); +} + +TEST_F(ViewManagerTest, MultiRoots) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + View* view2 = window_manager()->CreateView(); + view2->SetVisible(true); + window_manager()->GetRoot()->AddChild(view2); + ViewManager* embedded1 = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded1); + ViewManager* embedded2 = Embed(window_manager(), view2); + ASSERT_NE(nullptr, embedded2); + EXPECT_NE(embedded1, embedded2); +} + +TEST_F(ViewManagerTest, EmbeddingIdentity) { + View* view = window_manager()->CreateView(); + view->SetVisible(true); + window_manager()->GetRoot()->AddChild(view); + ViewManager* embedded = Embed(window_manager(), view); + ASSERT_NE(nullptr, embedded); + EXPECT_EQ(application_impl()->url(), embedded->GetEmbedderURL()); +} + +// TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change. +// Debug and re-enable this. +TEST_F(ViewManagerTest, DISABLED_Reorder) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + + View* view11 = embedded->CreateView(); + view11->SetVisible(true); + embedded->GetRoot()->AddChild(view11); + View* view12 = embedded->CreateView(); + view12->SetVisible(true); + embedded->GetRoot()->AddChild(view12); + + View* root_in_embedded = embedded->GetRoot(); + + { + ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u)); + view11->MoveToFront(); + ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded)); + + EXPECT_EQ(root_in_embedded->children().front(), + embedded->GetViewById(view12->id())); + EXPECT_EQ(root_in_embedded->children().back(), + embedded->GetViewById(view11->id())); + } + + { + view11->MoveToBack(); + ASSERT_TRUE(WaitForOrderChange(embedded, + embedded->GetViewById(view11->id()))); + + EXPECT_EQ(root_in_embedded->children().front(), + embedded->GetViewById(view11->id())); + EXPECT_EQ(root_in_embedded->children().back(), + embedded->GetViewById(view12->id())); + } +} + +namespace { + +class VisibilityChangeObserver : public ViewObserver { + public: + explicit VisibilityChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~VisibilityChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewVisibilityChanged(View* view) override { + EXPECT_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Visible) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + // Embed another app and verify initial state. + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, embedded->GetRoot()); + View* embedded_root = embedded->GetRoot(); + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); + + // Change the visible state from the first connection and verify its mirrored + // correctly to the embedded app. + { + VisibilityChangeObserver observer(embedded_root); + view1->SetVisible(false); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_FALSE(view1->visible()); + EXPECT_FALSE(view1->IsDrawn()); + + EXPECT_FALSE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); + + // Make the node visible again. + { + VisibilityChangeObserver observer(embedded_root); + view1->SetVisible(true); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(view1->visible()); + EXPECT_TRUE(view1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); +} + +namespace { + +class DrawnChangeObserver : public ViewObserver { + public: + explicit DrawnChangeObserver(View* view) : view_(view) { + view_->AddObserver(this); + } + ~DrawnChangeObserver() override { view_->RemoveObserver(this); } + + private: + // Overridden from ViewObserver: + void OnViewDrawnChanged(View* view) override { + EXPECT_EQ(view, view_); + QuitRunLoop(); + } + + View* view_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Drawn) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + // Embed another app and verify initial state. + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + ASSERT_NE(nullptr, embedded->GetRoot()); + View* embedded_root = embedded->GetRoot(); + EXPECT_TRUE(embedded_root->visible()); + EXPECT_TRUE(embedded_root->IsDrawn()); + + // Change the visibility of the root, this should propagate a drawn state + // change to |embedded|. + { + DrawnChangeObserver observer(embedded_root); + window_manager()->GetRoot()->SetVisible(false); + ASSERT_TRUE(DoRunLoopWithTimeout()); + } + + EXPECT_TRUE(view1->visible()); + EXPECT_FALSE(view1->IsDrawn()); + + EXPECT_TRUE(embedded_root->visible()); + EXPECT_FALSE(embedded_root->IsDrawn()); +} + +// TODO(beng): tests for view event dispatcher. +// - verify that we see events for all views. + +namespace { + +class FocusChangeObserver : public ViewObserver { + public: + explicit FocusChangeObserver(View* view) + : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) { + view_->AddObserver(this); + } + ~FocusChangeObserver() override { view_->RemoveObserver(this); } + + View* last_gained_focus() { return last_gained_focus_; } + + View* last_lost_focus() { return last_lost_focus_; } + + private: + // Overridden from ViewObserver. + void OnViewFocusChanged(View* gained_focus, View* lost_focus) override { + last_gained_focus_ = gained_focus; + last_lost_focus_ = lost_focus; + QuitRunLoop(); + } + + View* view_; + View* last_gained_focus_; + View* last_lost_focus_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); +}; + +} // namespace + +TEST_F(ViewManagerTest, Focus) { + View* view1 = window_manager()->CreateView(); + view1->SetVisible(true); + window_manager()->GetRoot()->AddChild(view1); + + ViewManager* embedded = Embed(window_manager(), view1); + ASSERT_NE(nullptr, embedded); + View* view11 = embedded->CreateView(); + view11->SetVisible(true); + embedded->GetRoot()->AddChild(view11); + + // TODO(alhaad): Figure out why switching focus between views from different + // connections is causing the tests to crash and add tests for that. + { + View* embedded_root = embedded->GetRoot(); + FocusChangeObserver observer(embedded_root); + embedded_root->SetFocus(); + ASSERT_TRUE(DoRunLoopWithTimeout()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id()); + } + { + FocusChangeObserver observer(view11); + view11->SetFocus(); + ASSERT_TRUE(DoRunLoopWithTimeout()); + ASSERT_NE(nullptr, observer.last_gained_focus()); + ASSERT_NE(nullptr, observer.last_lost_focus()); + EXPECT_EQ(view11->id(), observer.last_gained_focus()->id()); + EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id()); + } +} + +} // namespace mojo diff --git a/components/view_manager/view_manager_service_apptest.cc b/components/view_manager/view_manager_service_apptest.cc new file mode 100644 index 0000000..7be0716 --- /dev/null +++ b/components/view_manager/view_manager_service_apptest.cc @@ -0,0 +1,1499 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "components/view_manager/ids.h" +#include "components/view_manager/test_change_tracker.h" +#include "mojo/application/application_test_base_chromium.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_delegate.h" +#include "third_party/mojo/src/mojo/public/cpp/application/application_impl.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +using mojo::ApplicationConnection; +using mojo::ApplicationDelegate; +using mojo::Array; +using mojo::Callback; +using mojo::ConnectionSpecificId; +using mojo::ERROR_CODE_NONE; +using mojo::ErrorCode; +using mojo::EventPtr; +using mojo::Id; +using mojo::InterfaceRequest; +using mojo::ORDER_DIRECTION_ABOVE; +using mojo::ORDER_DIRECTION_BELOW; +using mojo::OrderDirection; +using mojo::RectPtr; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; +using mojo::ViewManagerClient; +using mojo::ViewManagerService; +using mojo::ViewportMetricsPtr; + +namespace view_manager { + +// Creates an id used for transport from the specified parameters. +Id BuildViewId(ConnectionSpecificId connection_id, + ConnectionSpecificId view_id) { + return (connection_id << 16) | view_id; +} + +// Callback function from ViewManagerService functions. ------------------------ + +void BoolResultCallback(base::RunLoop* run_loop, + bool* result_cache, + bool result) { + *result_cache = result; + run_loop->Quit(); +} + +void ErrorCodeResultCallback(base::RunLoop* run_loop, + ErrorCode* result_cache, + ErrorCode result) { + *result_cache = result; + run_loop->Quit(); +} + +void ViewTreeResultCallback(base::RunLoop* run_loop, + std::vector<TestView>* views, + Array<ViewDataPtr> results) { + ViewDatasToTestViews(results, views); + run_loop->Quit(); +} + +// ----------------------------------------------------------------------------- + +// The following functions call through to the supplied ViewManagerService. They +// block until call completes and return the result. +bool CreateView(ViewManagerService* vm, Id view_id) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(view_id, + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result == ERROR_CODE_NONE; +} + +bool EmbedUrl(ViewManagerService* vm, const String& url, Id root_id) { + bool result = false; + base::RunLoop run_loop; + { + vm->EmbedUrl(url, root_id, nullptr, nullptr, + base::Bind(&BoolResultCallback, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +bool Embed(ViewManagerService* vm, + Id root_id, + mojo::ViewManagerClientPtr client) { + bool result = false; + base::RunLoop run_loop; + { + vm->Embed(root_id, client.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + } + run_loop.Run(); + return result; +} + +ErrorCode CreateViewWithErrorCode(ViewManagerService* vm, Id view_id) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(view_id, + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool AddView(ViewManagerService* vm, Id parent, Id child) { + bool result = false; + base::RunLoop run_loop; + vm->AddView(parent, child, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool RemoveViewFromParent(ViewManagerService* vm, Id view_id) { + bool result = false; + base::RunLoop run_loop; + vm->RemoveViewFromParent(view_id, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool ReorderView(ViewManagerService* vm, + Id view_id, + Id relative_view_id, + OrderDirection direction) { + bool result = false; + base::RunLoop run_loop; + vm->ReorderView(view_id, relative_view_id, direction, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +void GetViewTree(ViewManagerService* vm, + Id view_id, + std::vector<TestView>* views) { + base::RunLoop run_loop; + vm->GetViewTree(view_id, + base::Bind(&ViewTreeResultCallback, &run_loop, views)); + run_loop.Run(); +} + +bool DeleteView(ViewManagerService* vm, Id view_id) { + base::RunLoop run_loop; + bool result = false; + vm->DeleteView(view_id, base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewBounds(ViewManagerService* vm, + Id view_id, + int x, + int y, + int w, + int h) { + base::RunLoop run_loop; + bool result = false; + RectPtr rect(mojo::Rect::New()); + rect->x = x; + rect->y = y; + rect->width = w; + rect->height = h; + vm->SetViewBounds(view_id, rect.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewVisibility(ViewManagerService* vm, Id view_id, bool visible) { + base::RunLoop run_loop; + bool result = false; + vm->SetViewVisibility(view_id, visible, + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +bool SetViewProperty(ViewManagerService* vm, + Id view_id, + const std::string& name, + const std::vector<uint8_t>* data) { + base::RunLoop run_loop; + bool result = false; + Array<uint8_t> mojo_data; + if (data) + mojo_data = Array<uint8_t>::From(*data); + vm->SetViewProperty(view_id, name, mojo_data.Pass(), + base::Bind(&BoolResultCallback, &run_loop, &result)); + run_loop.Run(); + return result; +} + +// Utility functions ----------------------------------------------------------- + +// Waits for all messages to be received by |vm|. This is done by attempting to +// create a bogus view. When we get the response we know all messages have been +// processed. +bool WaitForAllMessages(ViewManagerService* vm) { + ErrorCode result = ERROR_CODE_NONE; + base::RunLoop run_loop; + vm->CreateView(ViewIdToTransportId(InvalidViewId()), + base::Bind(&ErrorCodeResultCallback, &run_loop, &result)); + run_loop.Run(); + return result != ERROR_CODE_NONE; +} + +bool HasClonedView(const std::vector<TestView>& views) { + for (size_t i = 0; i < views.size(); ++i) + if (views[i].view_id == ViewIdToTransportId(ClonedViewId())) + return true; + return false; +} + +// ----------------------------------------------------------------------------- + +// A ViewManagerClient implementation that logs all changes to a tracker. +class ViewManagerClientImpl : public mojo::ViewManagerClient, + public TestChangeTracker::Delegate { + public: + ViewManagerClientImpl() : binding_(this) { tracker_.set_delegate(this); } + + void Bind(mojo::InterfaceRequest<mojo::ViewManagerClient> request) { + binding_.Bind(request.Pass()); + } + + mojo::ViewManagerService* service() { return service_.get(); } + TestChangeTracker* tracker() { return &tracker_; } + + // Runs a nested MessageLoop until |count| changes (calls to + // ViewManagerClient functions) have been received. + void WaitForChangeCount(size_t count) { + if (count == tracker_.changes()->size()) + return; + + ASSERT_TRUE(wait_state_.get() == nullptr); + wait_state_.reset(new WaitState); + wait_state_->change_count = count; + wait_state_->run_loop.Run(); + wait_state_.reset(); + } + + // Runs a nested MessageLoop until OnEmbed() has been encountered. + void WaitForOnEmbed() { + if (service_) + return; + embed_run_loop_.reset(new base::RunLoop); + embed_run_loop_->Run(); + embed_run_loop_.reset(); + } + + bool WaitForIncomingMethodCall() { + return binding_.WaitForIncomingMethodCall(); + } + + private: + // Used when running a nested MessageLoop. + struct WaitState { + WaitState() : change_count(0) {} + + // Number of changes waiting for. + size_t change_count; + base::RunLoop run_loop; + }; + + // TestChangeTracker::Delegate: + void OnChangeAdded() override { + if (wait_state_.get() && + wait_state_->change_count == tracker_.changes()->size()) { + wait_state_->run_loop.Quit(); + } + } + + // ViewManagerClient: + void OnEmbed(ConnectionSpecificId connection_id, + const String& creator_url, + ViewDataPtr root, + mojo::ViewManagerServicePtr view_manager_service, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + mojo::ScopedMessagePipeHandle window_manager_pipe) override { + service_ = view_manager_service.Pass(); + tracker()->OnEmbed(connection_id, creator_url, root.Pass()); + if (embed_run_loop_) + embed_run_loop_->Quit(); + } + void OnEmbeddedAppDisconnected(Id view_id) override { + tracker()->OnEmbeddedAppDisconnected(view_id); + } + void OnViewBoundsChanged(Id view_id, + RectPtr old_bounds, + RectPtr new_bounds) override { + tracker()->OnViewBoundsChanged(view_id, old_bounds.Pass(), + new_bounds.Pass()); + } + void OnViewViewportMetricsChanged(ViewportMetricsPtr old_metrics, + ViewportMetricsPtr new_metrics) override { + tracker()->OnViewViewportMetricsChanged(old_metrics.Pass(), + new_metrics.Pass()); + } + void OnViewHierarchyChanged(Id view, + Id new_parent, + Id old_parent, + Array<ViewDataPtr> views) override { + tracker()->OnViewHierarchyChanged(view, new_parent, old_parent, + views.Pass()); + } + void OnViewReordered(Id view_id, + Id relative_view_id, + OrderDirection direction) override { + tracker()->OnViewReordered(view_id, relative_view_id, direction); + } + void OnViewDeleted(Id view) override { tracker()->OnViewDeleted(view); } + void OnViewVisibilityChanged(uint32_t view, bool visible) override { + tracker()->OnViewVisibilityChanged(view, visible); + } + void OnViewDrawnStateChanged(uint32_t view, bool drawn) override { + tracker()->OnViewDrawnStateChanged(view, drawn); + } + void OnViewInputEvent(Id view_id, + EventPtr event, + const Callback<void()>& callback) override { + tracker()->OnViewInputEvent(view_id, event.Pass()); + callback.Run(); + } + void OnViewSharedPropertyChanged(uint32_t view, + const String& name, + Array<uint8_t> new_data) override { + tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass()); + } + void OnPerformAction(uint32_t view, + const String& name, + const Callback<void(bool)>& callback) override {} + + TestChangeTracker tracker_; + + mojo::ViewManagerServicePtr service_; + + // If non-null we're waiting for OnEmbed() using this RunLoop. + scoped_ptr<base::RunLoop> embed_run_loop_; + + // If non-null we're waiting for a certain number of change notifications to + // be encountered. + scoped_ptr<WaitState> wait_state_; + + mojo::Binding<ViewManagerClient> binding_; + DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl); +}; + +// ----------------------------------------------------------------------------- + +// InterfaceFactory for vending ViewManagerClientImpls. +class ViewManagerClientFactory + : public mojo::InterfaceFactory<ViewManagerClient> { + public: + ViewManagerClientFactory() {} + ~ViewManagerClientFactory() override {} + + // Runs a nested MessageLoop until a new instance has been created. + scoped_ptr<ViewManagerClientImpl> WaitForInstance() { + if (!client_impl_.get()) { + DCHECK(!run_loop_.get()); + run_loop_.reset(new base::RunLoop); + run_loop_->Run(); + run_loop_.reset(); + } + return client_impl_.Pass(); + } + + private: + // InterfaceFactory<ViewManagerClient>: + void Create(ApplicationConnection* connection, + InterfaceRequest<ViewManagerClient> request) override { + client_impl_.reset(new ViewManagerClientImpl); + client_impl_->Bind(request.Pass()); + if (run_loop_.get()) + run_loop_->Quit(); + } + + scoped_ptr<ViewManagerClientImpl> client_impl_; + scoped_ptr<base::RunLoop> run_loop_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerClientFactory); +}; + +class ViewManagerServiceAppTest + : public mojo::test::ApplicationTestBase, + public ApplicationDelegate, + public mojo::InterfaceFactory<mojo::WindowManagerInternal>, + public mojo::WindowManagerInternal { + public: + ViewManagerServiceAppTest() : wm_internal_binding_(this) {} + ~ViewManagerServiceAppTest() override {} + + protected: + // Returns the changes from the various connections. + std::vector<Change>* changes1() { return vm_client1_.tracker()->changes(); } + std::vector<Change>* changes2() { return vm_client2_->tracker()->changes(); } + std::vector<Change>* changes3() { return vm_client3_->tracker()->changes(); } + + // Various connections. |vm1()|, being the first connection, has special + // permissions (it's treated as the window manager). + ViewManagerService* vm1() { return vm1_.get(); } + ViewManagerService* vm2() { return vm_client2_->service(); } + ViewManagerService* vm3() { return vm_client3_->service(); } + + void EstablishSecondConnectionWithRoot(Id root_id) { + ASSERT_TRUE(vm_client2_.get() == nullptr); + vm_client2_ = EstablishConnectionViaEmbed(vm1(), root_id); + ASSERT_TRUE(vm_client2_.get() != nullptr); + } + + void EstablishSecondConnection(bool create_initial_view) { + if (create_initial_view) + ASSERT_TRUE(CreateView(vm1_.get(), BuildViewId(1, 1))); + ASSERT_NO_FATAL_FAILURE( + EstablishSecondConnectionWithRoot(BuildViewId(1, 1))); + + if (create_initial_view) + EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2())); + } + + void EstablishThirdConnection(ViewManagerService* owner, Id root_id) { + ASSERT_TRUE(vm_client3_.get() == nullptr); + vm_client3_ = EstablishConnectionViaEmbed(owner, root_id); + ASSERT_TRUE(vm_client3_.get() != nullptr); + } + + // Establishes a new connection by way of Embed() on the specified + // ViewManagerService. + scoped_ptr<ViewManagerClientImpl> EstablishConnectionViaEmbed( + ViewManagerService* owner, + Id root_id) { + if (!EmbedUrl(owner, application_impl()->url(), root_id)) { + ADD_FAILURE() << "Embed() failed"; + return nullptr; + } + scoped_ptr<ViewManagerClientImpl> client = + client_factory_.WaitForInstance(); + if (!client.get()) { + ADD_FAILURE() << "WaitForInstance failed"; + return nullptr; + } + client->WaitForOnEmbed(); + + const std::string expected_creator = + owner == vm1() ? "mojo:window_manager" : application_impl()->url(); + EXPECT_EQ("OnEmbed creator=" + expected_creator, + SingleChangeToDescription(*client->tracker()->changes())); + return client.Pass(); + } + + // ApplicationTestBase: + ApplicationDelegate* GetApplicationDelegate() override { return this; } + void SetUp() override { + ApplicationTestBase::SetUp(); + ApplicationConnection* vm_connection = + application_impl()->ConnectToApplication("mojo:view_manager"); + vm_connection->AddService(this); + vm_connection->ConnectToService(&vm1_); + vm_connection->ConnectToService(&wm_internal_client_); + // Spin a run loop until the view manager service sends us the + // ViewManagerClient pipe to use for the "window manager" connection. + view_manager_setup_run_loop_.reset(new base::RunLoop); + view_manager_setup_run_loop_->Run(); + view_manager_setup_run_loop_ = nullptr; + // Next we should get an embed call on the "window manager" client. + vm_client1_.WaitForIncomingMethodCall(); + ASSERT_EQ(1u, changes1()->size()); + EXPECT_EQ(CHANGE_TYPE_EMBED, (*changes1())[0].type); + // All these tests assume 1 for the client id. The only real assertion here + // is the client id is not zero, but adding this as rest of code here + // assumes 1. + ASSERT_EQ(1, (*changes1())[0].connection_id); + changes1()->clear(); + } + + // ApplicationDelegate implementation. + bool ConfigureIncomingConnection(ApplicationConnection* connection) override { + connection->AddService(&client_factory_); + return true; + } + + // mojo::InterfaceFactory<mojo::WindowManagerInternal> implementation. + void Create( + ApplicationConnection* connection, + mojo::InterfaceRequest<mojo::WindowManagerInternal> request) override { + DCHECK(!wm_internal_binding_.is_bound()); + wm_internal_binding_.Bind(request.Pass()); + } + + // mojo::WindowManagerInternal implementation. + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override {} + void SetViewManagerClient( + mojo::ScopedMessagePipeHandle view_manager_client_request) override { + auto typed_request = mojo::MakeRequest<mojo::ViewManagerClient>( + view_manager_client_request.Pass()); + vm_client1_.Bind(typed_request.Pass()); + view_manager_setup_run_loop_->Quit(); + } + + mojo::Binding<mojo::WindowManagerInternal> wm_internal_binding_; + mojo::WindowManagerInternalClientPtr wm_internal_client_; + ViewManagerClientImpl vm_client1_; + scoped_ptr<ViewManagerClientImpl> vm_client2_; + scoped_ptr<ViewManagerClientImpl> vm_client3_; + + private: + mojo::ViewManagerServicePtr vm1_; + ViewManagerClientFactory client_factory_; + scoped_ptr<base::RunLoop> view_manager_setup_run_loop_; + + MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceAppTest); +}; + +// Verifies two clients/connections get different ids. +TEST_F(ViewManagerServiceAppTest, TwoClientsGetDifferentConnectionIds) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // It isn't strictly necessary that the second connection gets 2, but these + // tests are written assuming that is the case. The key thing is the + // connection ids of |connection_| and |connection2_| differ. + ASSERT_EQ(1u, changes2()->size()); + ASSERT_EQ(2, (*changes2())[0].connection_id); +} + +// Verifies when Embed() is invoked any child views are removed. +TEST_F(ViewManagerServiceAppTest, ViewsRemovedWhenEmbedding) { + // Two views 1 and 2. 2 is parented to 1. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + EXPECT_EQ("[view=1,1 parent=null]", ChangeViewDescription(*changes2())); + + // Embed() removed view 2. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 2), &views); + EXPECT_EQ("view=1,2 parent=null", SingleViewDescription(views)); + } + + // vm2 should not see view 2. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_EQ("view=1,1 parent=null", SingleViewDescription(views)); + } + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 2), &views); + EXPECT_TRUE(views.empty()); + } + + // Views 3 and 4 in connection 2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 4))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 3), BuildViewId(2, 4))); + + // Connection 3 rooted at 2. + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3))); + + // View 4 should no longer have a parent. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(2, 3), &views); + EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views)); + + views.clear(); + GetViewTree(vm2(), BuildViewId(2, 4), &views); + EXPECT_EQ("view=2,4 parent=null", SingleViewDescription(views)); + } + + // And view 4 should not be visible to connection 3. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(2, 3), &views); + EXPECT_EQ("view=2,3 parent=null", SingleViewDescription(views)); + } +} + +// Verifies once Embed() has been invoked the parent connection can't see any +// children. +TEST_F(ViewManagerServiceAppTest, CantAccessChildrenOfEmbeddedView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3))); + + // Even though 3 is a child of 2 connection 2 can't see 3 as it's from a + // different connection. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(2, 2), &views); + EXPECT_EQ("view=2,2 parent=1,1", SingleViewDescription(views)); + } + + // Connection 2 shouldn't be able to get view 3 at all. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(3, 3), &views); + EXPECT_TRUE(views.empty()); + } + + // Connection 1 should be able to see it all (its the root). + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString()); + EXPECT_EQ("view=3,3 parent=2,2", views[2].ToString()); + } +} + +// Verifies once Embed() has been invoked the parent can't mutate the children. +TEST_F(ViewManagerServiceAppTest, CantModifyChildrenOfEmbeddedView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + // Connection 2 shouldn't be able to add anything to the view anymore. + ASSERT_FALSE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + + // Create view 3 in connection 3 and add it to view 3. + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 3))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(2, 2), BuildViewId(3, 3))); + + // Connection 2 shouldn't be able to remove view 3. + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(3, 3))); +} + +// Verifies client gets a valid id. +TEST_F(ViewManagerServiceAppTest, CreateView) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a view with the same id. + ASSERT_EQ(mojo::ERROR_CODE_VALUE_IN_USE, + CreateViewWithErrorCode(vm1(), BuildViewId(1, 1))); + EXPECT_TRUE(changes1()->empty()); + + // Can't create a view with a bogus connection id. + EXPECT_EQ(mojo::ERROR_CODE_ILLEGAL_ARGUMENT, + CreateViewWithErrorCode(vm1(), BuildViewId(2, 1))); + EXPECT_TRUE(changes1()->empty()); +} + +// Verifies AddView fails when view is already in position. +TEST_F(ViewManagerServiceAppTest, AddViewWithNoChange) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); + + // Try again, this should fail. + EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); +} + +// Verifies AddView fails when view is already in position. +TEST_F(ViewManagerServiceAppTest, AddAncestorFails) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Make 3 a child of 2. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 3))); + + // Try to make 2 a child of 3, this should fail since 2 is an ancestor of 3. + EXPECT_FALSE(AddView(vm1(), BuildViewId(1, 3), BuildViewId(1, 2))); +} + +// Verifies adding to root sends right notifications. +TEST_F(ViewManagerServiceAppTest, AddToRoot) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 21))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 3))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + // Make 3 a child of 21. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 21), BuildViewId(1, 3))); + + // Make 21 a child of 1. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 21))); + + // Connection 2 should not be told anything (because the view is from a + // different connection). Create a view to ensure we got a response from + // the server. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 100))); + EXPECT_TRUE(changes2()->empty()); +} + +// Verifies HierarchyChanged is correctly sent for various adds/removes. +TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedViews) { + // 1,2->1,11. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 11), true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 2), BuildViewId(1, 11))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + + ASSERT_TRUE(WaitForAllMessages(vm2())); + changes2()->clear(); + + // 1,1->1,2->1,11 + { + // Client 2 should not get anything (1,2 is from another connection). + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + ASSERT_TRUE(WaitForAllMessages(vm2())); + EXPECT_TRUE(changes2()->empty()); + } + + // 0,1->1,1->1,2->1,11. + { + // Client 2 is now connected to the root, so it should have gotten a drawn + // notification. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1u); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true", + SingleChangeToDescription(*changes2())); + } + + // 1,1->1,2->1,11. + { + // Client 2 is no longer connected to the root, should get drawn state + // changed. + changes2()->clear(); + ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=false", + SingleChangeToDescription(*changes2())); + } + + // 1,1->1,2->1,11->1,111. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 111))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 111), true)); + { + changes2()->clear(); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 11), BuildViewId(1, 111))); + ASSERT_TRUE(WaitForAllMessages(vm2())); + EXPECT_TRUE(changes2()->empty()); + } + + // 0,1->1,1->1,2->1,11->1,111 + { + changes2()->clear(); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,1 drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, ViewHierarchyChangedAddingKnownToUnknown) { + // Create the following structure: root -> 1 -> 11 and 2->21 (2 has no + // parent). + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 21))); + + // Set up the hierarchy. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 11))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 21))); + + // Remove 11, should result in a hierarchy change for the root. + { + changes1()->clear(); + ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,11 new_parent=null old_parent=1,1", + SingleChangeToDescription(*changes1())); + } + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ( + "[view=2,2 parent=1,1]," + "[view=2,21 parent=2,2]", + ChangeViewDescription(*changes1())); + } +} + +TEST_F(ViewManagerServiceAppTest, ReorderView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + Id view1_id = BuildViewId(2, 1); + Id view2_id = BuildViewId(2, 2); + Id view3_id = BuildViewId(2, 3); + Id view4_id = BuildViewId(1, 4); // Peer to 1,1 + Id view5_id = BuildViewId(1, 5); // Peer to 1,1 + Id view6_id = BuildViewId(2, 6); // Child of 1,2. + Id view7_id = BuildViewId(2, 7); // Unparented. + Id view8_id = BuildViewId(2, 8); // Unparented. + ASSERT_TRUE(CreateView(vm2(), view1_id)); + ASSERT_TRUE(CreateView(vm2(), view2_id)); + ASSERT_TRUE(CreateView(vm2(), view3_id)); + ASSERT_TRUE(CreateView(vm1(), view4_id)); + ASSERT_TRUE(CreateView(vm1(), view5_id)); + ASSERT_TRUE(CreateView(vm2(), view6_id)); + ASSERT_TRUE(CreateView(vm2(), view7_id)); + ASSERT_TRUE(CreateView(vm2(), view8_id)); + ASSERT_TRUE(AddView(vm2(), view1_id, view2_id)); + ASSERT_TRUE(AddView(vm2(), view2_id, view6_id)); + ASSERT_TRUE(AddView(vm2(), view1_id, view3_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view4_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view5_id)); + ASSERT_TRUE(AddView(vm1(), ViewIdToTransportId(RootViewId()), view1_id)); + + { + changes1()->clear(); + ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_ABOVE)); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=above", + SingleChangeToDescription(*changes1())); + } + + { + changes1()->clear(); + ASSERT_TRUE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW)); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("Reordered view=2,2 relative=2,3 direction=below", + SingleChangeToDescription(*changes1())); + } + + // view2 is already below view3. + EXPECT_FALSE(ReorderView(vm2(), view2_id, view3_id, ORDER_DIRECTION_BELOW)); + + // view4 & 5 are unknown to connection2_. + EXPECT_FALSE(ReorderView(vm2(), view4_id, view5_id, ORDER_DIRECTION_ABOVE)); + + // view6 & view3 have different parents. + EXPECT_FALSE(ReorderView(vm1(), view3_id, view6_id, ORDER_DIRECTION_ABOVE)); + + // Non-existent view-ids + EXPECT_FALSE(ReorderView(vm1(), BuildViewId(1, 27), BuildViewId(1, 28), + ORDER_DIRECTION_ABOVE)); + + // view7 & view8 are un-parented. + EXPECT_FALSE(ReorderView(vm1(), view7_id, view8_id, ORDER_DIRECTION_ABOVE)); +} + +// Verifies DeleteView works. +TEST_F(ViewManagerServiceAppTest, DeleteView) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + + // Make 2 a child of 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + changes2()->clear(); + ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2))); + EXPECT_TRUE(changes2()->empty()); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1())); + } +} + +// Verifies DeleteView isn't allowed from a separate connection. +TEST_F(ViewManagerServiceAppTest, DeleteViewFromAnotherConnectionDisallowed) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + EXPECT_FALSE(DeleteView(vm2(), BuildViewId(1, 1))); +} + +// Verifies if a view was deleted and then reused that other clients are +// properly notified. +TEST_F(ViewManagerServiceAppTest, ReuseDeletedViewId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + + // Add 2 to 1. + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1())); + } + + // Delete 2. + { + changes1()->clear(); + ASSERT_TRUE(DeleteView(vm2(), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,2", SingleChangeToDescription(*changes1())); + } + + // Create 2 again, and add it back to 1. Should get the same notification. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + { + changes1()->clear(); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=2,2 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + EXPECT_EQ("[view=2,2 parent=1,1]", ChangeViewDescription(*changes1())); + } +} + +// Assertions for GetViewTree. +TEST_F(ViewManagerServiceAppTest, GetViewTree) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + + // Create 11 in first connection and make it a child of 1. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 11))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 11))); + + // Create two views in second connection, 2 and 3, both children of 1. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 3))); + + // Verifies GetViewTree() on the root. The root connection sees all. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(5u, views.size()); + EXPECT_EQ("view=0,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString()); + EXPECT_EQ("view=1,11 parent=1,1", views[2].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[3].ToString()); + EXPECT_EQ("view=2,3 parent=1,1", views[4].ToString()); + } + + // Verifies GetViewTree() on the view 1,1 from vm2(). vm2() sees 1,1 as 1,1 + // is vm2()'s root and vm2() sees all the views it created. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=2,2 parent=1,1", views[1].ToString()); + EXPECT_EQ("view=2,3 parent=1,1", views[2].ToString()); + } + + // Connection 2 shouldn't be able to get the root tree. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(0, 1), &views); + ASSERT_EQ(0u, views.size()); + } +} + +TEST_F(ViewManagerServiceAppTest, SetViewBounds) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + changes2()->clear(); + ASSERT_TRUE(SetViewBounds(vm1(), BuildViewId(1, 1), 0, 0, 100, 100)); + + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("BoundsChanged view=1,1 old_bounds=0,0 0x0 new_bounds=0,0 100x100", + SingleChangeToDescription(*changes2())); + + // Should not be possible to change the bounds of a view created by another + // connection. + ASSERT_FALSE(SetViewBounds(vm2(), BuildViewId(1, 1), 0, 0, 0, 0)); +} + +// Verify AddView fails when trying to manipulate views in other roots. +TEST_F(ViewManagerServiceAppTest, CantMoveViewsFromOtherRoot) { + // Create 1 and 2 in the first connection. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Try to move 2 to be a child of 1 from connection 2. This should fail as 2 + // should not be able to access 1. + ASSERT_FALSE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(1, 2))); + + // Try to reparent 1 to the root. A connection is not allowed to reparent its + // roots. + ASSERT_FALSE(AddView(vm2(), BuildViewId(0, 1), BuildViewId(1, 1))); +} + +// Verify RemoveViewFromParent fails for views that are descendants of the +// roots. +TEST_F(ViewManagerServiceAppTest, CantRemoveViewsInOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2))); + + // Establish the second connection and give it the root 1. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + // Connection 2 should not be able to remove view 2 or 1 from its parent. + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 2))); + ASSERT_FALSE(RemoveViewFromParent(vm2(), BuildViewId(1, 1))); + + // Create views 10 and 11 in 2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 10))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 11))); + + // Parent 11 to 10. + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 10), BuildViewId(2, 11))); + // Remove 11 from 10. + ASSERT_TRUE(RemoveViewFromParent(vm2(), BuildViewId(2, 11))); + + // Verify nothing was actually removed. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(3u, views.size()); + EXPECT_EQ("view=0,1 parent=null", views[0].ToString()); + EXPECT_EQ("view=1,1 parent=0,1", views[1].ToString()); + EXPECT_EQ("view=1,2 parent=0,1", views[2].ToString()); + } +} + +// Verify GetViewTree fails for views that are not descendants of the roots. +TEST_F(ViewManagerServiceAppTest, CantGetViewTreeOfOtherRoots) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 2))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + + std::vector<TestView> views; + + // Should get nothing for the root. + GetViewTree(vm2(), BuildViewId(0, 1), &views); + ASSERT_TRUE(views.empty()); + + // Should get nothing for view 2. + GetViewTree(vm2(), BuildViewId(1, 2), &views); + ASSERT_TRUE(views.empty()); + + // Should get view 1 if asked for. + GetViewTree(vm2(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); +} + +TEST_F(ViewManagerServiceAppTest, OnViewInputEvent) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + // Dispatch an event to the view and verify it's received. + { + EventPtr event(mojo::Event::New()); + event->action = static_cast<mojo::EventType>(1); + wm_internal_client_->DispatchInputEventToView(BuildViewId(1, 1), + event.Pass()); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("InputEvent view=1,1 event_action=1", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1))); + + // Connection2 should have been told the view was deleted. + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes2())); + } + + // Connection2 has no root. Verify it can't see view 1,1 anymore. + { + std::vector<TestView> views; + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_TRUE(views.empty()); + } +} + +TEST_F(ViewManagerServiceAppTest, EmbedWithSameViewId2) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + changes2()->clear(); + + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm1(), BuildViewId(1, 1))); + + // Connection2 should have been told the view was deleted. + vm_client2_->WaitForChangeCount(1); + changes2()->clear(); + + // Create a view in the third connection and parent it to the root. + ASSERT_TRUE(CreateView(vm3(), BuildViewId(3, 1))); + ASSERT_TRUE(AddView(vm3(), BuildViewId(1, 1), BuildViewId(3, 1))); + + // Connection 1 should have been told about the add (it owns the view). + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("HierarchyChanged view=3,1 new_parent=1,1 old_parent=null", + SingleChangeToDescription(*changes1())); + } + + // Embed 1,1 again. + { + changes3()->clear(); + + // We should get a new connection for the new embedding. + scoped_ptr<ViewManagerClientImpl> connection4( + EstablishConnectionViaEmbed(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(connection4.get()); + EXPECT_EQ("[view=1,1 parent=null]", + ChangeViewDescription(*connection4->tracker()->changes())); + + // And 3 should get a delete. + vm_client3_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=1,1", SingleChangeToDescription(*changes3())); + } + + // vm3() has no root. Verify it can't see view 1,1 anymore. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(1, 1), &views); + EXPECT_TRUE(views.empty()); + } + + // Verify 3,1 is no longer parented to 1,1. We have to do this from 1,1 as + // vm3() can no longer see 1,1. + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=null", views[0].ToString()); + } + + // Verify vm3() can still see the view it created 3,1. + { + std::vector<TestView> views; + GetViewTree(vm3(), BuildViewId(3, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=3,1 parent=null", views[0].ToString()); + } +} + +// Assertions for SetViewVisibility. +TEST_F(ViewManagerServiceAppTest, SetViewVisibility) { + // Create 1 and 2 in the first connection and parent both to the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=0,1 parent=null visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[1].ToString2()); + } + + // Show all the views. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=0,1 parent=null visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true", + views[1].ToString2()); + } + + // Hide 1. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[0].ToString2()); + } + + // Attach 2 to 1. + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=false drawn=false", + views[0].ToString2()); + EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=false", + views[1].ToString2()); + } + + // Show 1. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ("view=1,1 parent=0,1 visible=true drawn=true", + views[0].ToString2()); + EXPECT_EQ("view=1,2 parent=1,1 visible=true drawn=true", + views[1].ToString2()); + } +} + +// Assertions for SetViewVisibility sending notifications. +TEST_F(ViewManagerServiceAppTest, SetViewVisibilityNotifications) { + // Create 1,1 and 1,2. 1,2 is made a child of 1,1 and 1,1 a child of the root. + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 2))); + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(AddView(vm1(), BuildViewId(1, 1), BuildViewId(1, 2))); + + // Establish the second connection at 1,2. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnectionWithRoot(BuildViewId(1, 2))); + + // Add 2,3 as a child of 1,2. + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), true)); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 2), BuildViewId(2, 3))); + WaitForAllMessages(vm1()); + + changes2()->clear(); + // Hide 1,2 from connection 1. Connection 2 should see this. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 2), false)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=1,2 visible=false", + SingleChangeToDescription(*changes2())); + } + + changes1()->clear(); + // Show 1,2 from connection 2, connection 1 should be notified. + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(1, 2), true)); + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=1,2 visible=true", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Hide 1,1, connection 2 should be told the draw state changed. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), false)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Show 1,1 from connection 1. Connection 2 should see this. + ASSERT_TRUE(SetViewVisibility(vm1(), BuildViewId(1, 1), true)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true", + SingleChangeToDescription(*changes2())); + } + + // Change visibility of 2,3, connection 1 should see this. + changes1()->clear(); + ASSERT_TRUE(SetViewVisibility(vm2(), BuildViewId(2, 3), false)); + { + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("VisibilityChanged view=2,3 visible=false", + SingleChangeToDescription(*changes1())); + } + + changes2()->clear(); + // Remove 1,1 from the root, connection 2 should see drawn state changed. + ASSERT_TRUE(RemoveViewFromParent(vm1(), BuildViewId(1, 1))); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=false", + SingleChangeToDescription(*changes2())); + } + + changes2()->clear(); + // Add 1,1 back to the root, connection 2 should see drawn state changed. + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("DrawnStateChanged view=1,2 drawn=true", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, SetViewProperty) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(false)); + changes2()->clear(); + + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(0, 1), &views); + ASSERT_EQ(2u, views.size()); + EXPECT_EQ(BuildViewId(0, 1), views[0].view_id); + EXPECT_EQ(BuildViewId(1, 1), views[1].view_id); + ASSERT_EQ(0u, views[1].properties.size()); + } + + // Set properties on 1. + changes2()->clear(); + std::vector<uint8_t> one(1, '1'); + ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", &one)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("PropertyChanged view=1,1 key=one value=1", + SingleChangeToDescription(*changes2())); + } + + // Test that our properties exist in the view tree + { + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + ASSERT_EQ(1u, views.size()); + ASSERT_EQ(1u, views[0].properties.size()); + EXPECT_EQ(one, views[0].properties["one"]); + } + + changes2()->clear(); + // Set back to null. + ASSERT_TRUE(SetViewProperty(vm1(), BuildViewId(1, 1), "one", NULL)); + { + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("PropertyChanged view=1,1 key=one value=NULL", + SingleChangeToDescription(*changes2())); + } +} + +TEST_F(ViewManagerServiceAppTest, OnEmbeddedAppDisconnected) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 2))); + + // Close connection 3. Connection 2 (which had previously embedded 3) should + // be notified of this. + vm_client3_.reset(); + vm_client2_->WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected view=2,2", + SingleChangeToDescription(*changes2())); +} + +// Verifies when the parent of an Embed() is destroyed the embedded app gets +// a ViewDeleted (and doesn't trigger a DCHECK). +TEST_F(ViewManagerServiceAppTest, OnParentOfEmbedDisconnects) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + changes2()->clear(); + ASSERT_NO_FATAL_FAILURE(EstablishThirdConnection(vm2(), BuildViewId(2, 3))); + changes3()->clear(); + + // Close connection 2. Connection 3 should get a delete (for its root). + vm_client2_.reset(); + vm_client3_->WaitForChangeCount(1); + EXPECT_EQ("ViewDeleted view=2,3", SingleChangeToDescription(*changes3())); +} + +// Verifies ViewManagerServiceImpl doesn't incorrectly erase from its internal +// map when a view from another connection with the same view_id is removed. +TEST_F(ViewManagerServiceAppTest, DontCleanMapOnDestroy) { + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 1))); + changes1()->clear(); + vm_client2_.reset(); + vm_client1_.WaitForChangeCount(1); + EXPECT_EQ("OnEmbeddedAppDisconnected view=1,1", + SingleChangeToDescription(*changes1())); + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + EXPECT_FALSE(views.empty()); +} + +TEST_F(ViewManagerServiceAppTest, CloneAndAnimate) { + // Create connection 2 and 3. + ASSERT_NO_FATAL_FAILURE(EstablishSecondConnection(true)); + ASSERT_TRUE(AddView(vm1(), BuildViewId(0, 1), BuildViewId(1, 1))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 2))); + ASSERT_TRUE(CreateView(vm2(), BuildViewId(2, 3))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(1, 1), BuildViewId(2, 2))); + ASSERT_TRUE(AddView(vm2(), BuildViewId(2, 2), BuildViewId(2, 3))); + changes2()->clear(); + + ASSERT_TRUE(WaitForAllMessages(vm1())); + changes1()->clear(); + + wm_internal_client_->CloneAndAnimate(BuildViewId(2, 3)); + ASSERT_TRUE(WaitForAllMessages(vm1())); + + ASSERT_TRUE(WaitForAllMessages(vm1())); + ASSERT_TRUE(WaitForAllMessages(vm2())); + + // No messages should have been received. + EXPECT_TRUE(changes1()->empty()); + EXPECT_TRUE(changes2()->empty()); + + // No one should be able to see the cloned tree. + std::vector<TestView> views; + GetViewTree(vm1(), BuildViewId(1, 1), &views); + EXPECT_FALSE(HasClonedView(views)); + views.clear(); + + GetViewTree(vm2(), BuildViewId(1, 1), &views); + EXPECT_FALSE(HasClonedView(views)); +} + +// Verifies Embed() works when supplying a ViewManagerClient. +TEST_F(ViewManagerServiceAppTest, EmbedSupplyingViewManagerClient) { + ASSERT_TRUE(CreateView(vm1(), BuildViewId(1, 1))); + + ViewManagerClientImpl client2; + mojo::ViewManagerClientPtr client2_ptr; + mojo::Binding<ViewManagerClient> client2_binding(&client2, &client2_ptr); + ASSERT_TRUE(Embed(vm1(), BuildViewId(1, 1), client2_ptr.Pass())); + client2.WaitForOnEmbed(); + EXPECT_EQ("OnEmbed creator=mojo:window_manager", + SingleChangeToDescription(*client2.tracker()->changes())); +} + +// TODO(sky): need to better track changes to initial connection. For example, +// that SetBounsdViews/AddView and the like don't result in messages to the +// originating connection. + +// TODO(sky): make sure coverage of what was +// ViewManagerTest.SecondEmbedRoot_InitService and +// ViewManagerTest.MultipleEmbedRootsBeforeWTHReady gets added to window manager +// tests. + +} // namespace view_manager diff --git a/components/view_manager/view_manager_service_impl.cc b/components/view_manager/view_manager_service_impl.cc new file mode 100644 index 0000000..1283fca --- /dev/null +++ b/components/view_manager/view_manager_service_impl.cc @@ -0,0 +1,654 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/view_manager_service_impl.h" + +#include "base/bind.h" +#include "base/stl_util.h" +#include "components/view_manager/connection_manager.h" +#include "components/view_manager/default_access_policy.h" +#include "components/view_manager/display_manager.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/window_manager_access_policy.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/converters/input_events/input_events_type_converters.h" +#include "mojo/converters/surfaces/surfaces_type_converters.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" + +using mojo::Array; +using mojo::Callback; +using mojo::Id; +using mojo::InterfaceRequest; +using mojo::OrderDirection; +using mojo::Rect; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; + +namespace view_manager { + +ViewManagerServiceImpl::ViewManagerServiceImpl( + ConnectionManager* connection_manager, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) + : connection_manager_(connection_manager), + id_(connection_manager_->GetAndAdvanceNextConnectionId()), + url_(url), + creator_id_(creator_id), + creator_url_(creator_url), + client_(nullptr) { + CHECK(GetView(root_id)); + root_.reset(new ViewId(root_id)); + if (root_id == RootViewId()) + access_policy_.reset(new WindowManagerAccessPolicy(id_, this)); + else + access_policy_.reset(new DefaultAccessPolicy(id_, this)); +} + +ViewManagerServiceImpl::~ViewManagerServiceImpl() { + DestroyViews(); +} + +void ViewManagerServiceImpl::Init(mojo::ViewManagerClient* client, + mojo::ViewManagerServicePtr service_ptr, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + DCHECK(!client_); + client_ = client; + std::vector<const ServerView*> to_send; + if (root_.get()) + GetUnknownViewsFrom(GetView(*root_), &to_send); + + mojo::MessagePipe pipe; + connection_manager_->wm_internal()->CreateWindowManagerForViewManagerClient( + id_, pipe.handle1.Pass()); + client->OnEmbed(id_, creator_url_, ViewToViewData(to_send.front()), + service_ptr.Pass(), services.Pass(), exposed_services.Pass(), + pipe.handle0.Pass()); +} + +const ServerView* ViewManagerServiceImpl::GetView(const ViewId& id) const { + if (id_ == id.connection_id) { + ViewMap::const_iterator i = view_map_.find(id.view_id); + return i == view_map_.end() ? NULL : i->second; + } + return connection_manager_->GetView(id); +} + +bool ViewManagerServiceImpl::IsRoot(const ViewId& id) const { + return root_.get() && *root_ == id; +} + +void ViewManagerServiceImpl::OnWillDestroyViewManagerServiceImpl( + ViewManagerServiceImpl* connection) { + if (creator_id_ == connection->id()) + creator_id_ = kInvalidConnectionId; + if (connection->root_ && connection->root_->connection_id == id_ && + view_map_.count(connection->root_->view_id) > 0) { + client()->OnEmbeddedAppDisconnected( + ViewIdToTransportId(*connection->root_)); + } + if (root_.get() && root_->connection_id == connection->id()) + root_.reset(); +} + +mojo::ErrorCode ViewManagerServiceImpl::CreateView(const ViewId& view_id) { + if (view_id.connection_id != id_) + return mojo::ERROR_CODE_ILLEGAL_ARGUMENT; + if (view_map_.find(view_id.view_id) != view_map_.end()) + return mojo::ERROR_CODE_VALUE_IN_USE; + view_map_[view_id.view_id] = connection_manager_->CreateServerView(view_id); + known_views_.insert(ViewIdToTransportId(view_id)); + return mojo::ERROR_CODE_NONE; +} + +bool ViewManagerServiceImpl::AddView(const ViewId& parent_id, + const ViewId& child_id) { + ServerView* parent = GetView(parent_id); + ServerView* child = GetView(child_id); + if (parent && child && child->parent() != parent && + !child->Contains(parent) && access_policy_->CanAddView(parent, child)) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + parent->Add(child); + return true; + } + return false; +} + +std::vector<const ServerView*> ViewManagerServiceImpl::GetViewTree( + const ViewId& view_id) const { + const ServerView* view = GetView(view_id); + std::vector<const ServerView*> views; + if (view) + GetViewTreeImpl(view, &views); + return views; +} + +bool ViewManagerServiceImpl::SetViewVisibility(const ViewId& view_id, + bool visible) { + ServerView* view = GetView(view_id); + if (!view || view->visible() == visible || + !access_policy_->CanChangeViewVisibility(view)) { + return false; + } + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->SetVisible(visible); + return true; +} + +bool ViewManagerServiceImpl::EmbedUrl( + const std::string& url, + const ViewId& view_id, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services) { + if (!PrepareForEmbed(view_id)) + return false; + connection_manager_->EmbedAtView(id_, url, view_id, services.Pass(), + exposed_services.Pass()); + return true; +} + +bool ViewManagerServiceImpl::Embed(const ViewId& view_id, + mojo::ViewManagerClientPtr client) { + if (!client.get() || !PrepareForEmbed(view_id)) + return false; + connection_manager_->EmbedAtView(id_, view_id, client.Pass()); + return true; +} + +void ViewManagerServiceImpl::ProcessViewBoundsChanged( + const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change) { + if (originated_change || !IsViewKnown(view)) + return; + client()->OnViewBoundsChanged(ViewIdToTransportId(view->id()), + Rect::From(old_bounds), + Rect::From(new_bounds)); +} + +void ViewManagerServiceImpl::ProcessViewportMetricsChanged( + const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics, + bool originated_change) { + client()->OnViewViewportMetricsChanged(old_metrics.Clone(), + new_metrics.Clone()); +} + +void ViewManagerServiceImpl::ProcessWillChangeViewHierarchy( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change) { + if (originated_change) + return; + + const bool old_drawn = view->IsDrawn(connection_manager_->root()); + const bool new_drawn = view->visible() && new_parent && + new_parent->IsDrawn(connection_manager_->root()); + if (old_drawn == new_drawn) + return; + + NotifyDrawnStateChanged(view, new_drawn); +} + +void ViewManagerServiceImpl::ProcessViewPropertyChanged( + const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change) { + if (originated_change) + return; + + Array<uint8_t> data; + if (new_data) + data = Array<uint8_t>::From(*new_data); + + client()->OnViewSharedPropertyChanged(ViewIdToTransportId(view->id()), + String(name), data.Pass()); +} + +void ViewManagerServiceImpl::ProcessViewHierarchyChanged( + const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change) { + if (originated_change && !IsViewKnown(view) && new_parent && + IsViewKnown(new_parent)) { + std::vector<const ServerView*> unused; + GetUnknownViewsFrom(view, &unused); + } + if (originated_change || connection_manager_->is_processing_delete_view() || + connection_manager_->DidConnectionMessageClient(id_)) { + return; + } + + if (!access_policy_->ShouldNotifyOnHierarchyChange( + view, &new_parent, &old_parent)) { + return; + } + // Inform the client of any new views and update the set of views we know + // about. + std::vector<const ServerView*> to_send; + if (!IsViewKnown(view)) + GetUnknownViewsFrom(view, &to_send); + const ViewId new_parent_id(new_parent ? new_parent->id() : ViewId()); + const ViewId old_parent_id(old_parent ? old_parent->id() : ViewId()); + client()->OnViewHierarchyChanged(ViewIdToTransportId(view->id()), + ViewIdToTransportId(new_parent_id), + ViewIdToTransportId(old_parent_id), + ViewsToViewDatas(to_send)); + connection_manager_->OnConnectionMessagedClient(id_); +} + +void ViewManagerServiceImpl::ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + OrderDirection direction, + bool originated_change) { + if (originated_change || !IsViewKnown(view) || !IsViewKnown(relative_view)) + return; + + client()->OnViewReordered(ViewIdToTransportId(view->id()), + ViewIdToTransportId(relative_view->id()), + direction); +} + +void ViewManagerServiceImpl::ProcessViewDeleted(const ViewId& view, + bool originated_change) { + if (view.connection_id == id_) + view_map_.erase(view.view_id); + + const bool in_known = known_views_.erase(ViewIdToTransportId(view)) > 0; + + if (IsRoot(view)) + root_.reset(); + + if (originated_change) + return; + + if (in_known) { + client()->OnViewDeleted(ViewIdToTransportId(view)); + connection_manager_->OnConnectionMessagedClient(id_); + } +} + +void ViewManagerServiceImpl::ProcessWillChangeViewVisibility( + const ServerView* view, + bool originated_change) { + if (originated_change) + return; + + if (IsViewKnown(view)) { + client()->OnViewVisibilityChanged(ViewIdToTransportId(view->id()), + !view->visible()); + return; + } + + bool view_target_drawn_state; + if (view->visible()) { + // View is being hidden, won't be drawn. + view_target_drawn_state = false; + } else { + // View is being shown. View will be drawn if its parent is drawn. + view_target_drawn_state = + view->parent() && view->parent()->IsDrawn(connection_manager_->root()); + } + + NotifyDrawnStateChanged(view, view_target_drawn_state); +} + +bool ViewManagerServiceImpl::IsViewKnown(const ServerView* view) const { + return known_views_.count(ViewIdToTransportId(view->id())) > 0; +} + +bool ViewManagerServiceImpl::CanReorderView(const ServerView* view, + const ServerView* relative_view, + OrderDirection direction) const { + if (!view || !relative_view) + return false; + + if (!view->parent() || view->parent() != relative_view->parent()) + return false; + + if (!access_policy_->CanReorderView(view, relative_view, direction)) + return false; + + std::vector<const ServerView*> children = view->parent()->GetChildren(); + const size_t child_i = + std::find(children.begin(), children.end(), view) - children.begin(); + const size_t target_i = + std::find(children.begin(), children.end(), relative_view) - + children.begin(); + if ((direction == mojo::ORDER_DIRECTION_ABOVE && child_i == target_i + 1) || + (direction == mojo::ORDER_DIRECTION_BELOW && child_i + 1 == target_i)) { + return false; + } + + return true; +} + +bool ViewManagerServiceImpl::DeleteViewImpl(ViewManagerServiceImpl* source, + ServerView* view) { + DCHECK(view); + DCHECK_EQ(view->id().connection_id, id_); + ConnectionManager::ScopedChange change(source, connection_manager_, true); + delete view; + return true; +} + +void ViewManagerServiceImpl::GetUnknownViewsFrom( + const ServerView* view, + std::vector<const ServerView*>* views) { + if (IsViewKnown(view) || !access_policy_->CanGetViewTree(view)) + return; + views->push_back(view); + known_views_.insert(ViewIdToTransportId(view->id())); + if (!access_policy_->CanDescendIntoViewForViewTree(view)) + return; + std::vector<const ServerView*> children(view->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetUnknownViewsFrom(children[i], views); +} + +void ViewManagerServiceImpl::RemoveFromKnown( + const ServerView* view, + std::vector<ServerView*>* local_views) { + if (view->id().connection_id == id_) { + if (local_views) + local_views->push_back(GetView(view->id())); + return; + } + known_views_.erase(ViewIdToTransportId(view->id())); + std::vector<const ServerView*> children = view->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + RemoveFromKnown(children[i], local_views); +} + +void ViewManagerServiceImpl::RemoveRoot() { + CHECK(root_.get()); + const ViewId root_id(*root_); + root_.reset(); + // No need to do anything if we created the view. + if (root_id.connection_id == id_) + return; + + client()->OnViewDeleted(ViewIdToTransportId(root_id)); + connection_manager_->OnConnectionMessagedClient(id_); + + // This connection no longer knows about the view. Unparent any views that + // were parented to views in the root. + std::vector<ServerView*> local_views; + RemoveFromKnown(GetView(root_id), &local_views); + for (size_t i = 0; i < local_views.size(); ++i) + local_views[i]->parent()->Remove(local_views[i]); +} + +void ViewManagerServiceImpl::RemoveChildrenAsPartOfEmbed( + const ViewId& view_id) { + ServerView* view = GetView(view_id); + CHECK(view); + CHECK(view->id().connection_id == view_id.connection_id); + std::vector<ServerView*> children = view->GetChildren(); + for (size_t i = 0; i < children.size(); ++i) + view->Remove(children[i]); +} + +Array<ViewDataPtr> ViewManagerServiceImpl::ViewsToViewDatas( + const std::vector<const ServerView*>& views) { + Array<ViewDataPtr> array(views.size()); + for (size_t i = 0; i < views.size(); ++i) + array[i] = ViewToViewData(views[i]).Pass(); + return array.Pass(); +} + +ViewDataPtr ViewManagerServiceImpl::ViewToViewData(const ServerView* view) { + DCHECK(IsViewKnown(view)); + const ServerView* parent = view->parent(); + // If the parent isn't known, it means the parent is not visible to us (not + // in roots), and should not be sent over. + if (parent && !IsViewKnown(parent)) + parent = NULL; + ViewDataPtr view_data(mojo::ViewData::New()); + view_data->parent_id = ViewIdToTransportId(parent ? parent->id() : ViewId()); + view_data->view_id = ViewIdToTransportId(view->id()); + view_data->bounds = Rect::From(view->bounds()); + view_data->properties = + mojo::Map<String, Array<uint8_t>>::From(view->properties()); + view_data->visible = view->visible(); + view_data->drawn = view->IsDrawn(connection_manager_->root()); + view_data->viewport_metrics = + connection_manager_->display_manager()->GetViewportMetrics().Clone(); + return view_data.Pass(); +} + +void ViewManagerServiceImpl::GetViewTreeImpl( + const ServerView* view, + std::vector<const ServerView*>* views) const { + DCHECK(view); + + if (!access_policy_->CanGetViewTree(view)) + return; + + views->push_back(view); + + if (!access_policy_->CanDescendIntoViewForViewTree(view)) + return; + + std::vector<const ServerView*> children(view->GetChildren()); + for (size_t i = 0 ; i < children.size(); ++i) + GetViewTreeImpl(children[i], views); +} + +void ViewManagerServiceImpl::NotifyDrawnStateChanged(const ServerView* view, + bool new_drawn_value) { + // Even though we don't know about view, it may be an ancestor of our root, in + // which case the change may effect our roots drawn state. + if (!root_.get()) + return; + + const ServerView* root = GetView(*root_); + DCHECK(root); + if (view->Contains(root) && + (new_drawn_value != root->IsDrawn(connection_manager_->root()))) { + client()->OnViewDrawnStateChanged(ViewIdToTransportId(root->id()), + new_drawn_value); + } +} + +void ViewManagerServiceImpl::DestroyViews() { + if (!view_map_.empty()) { + ConnectionManager::ScopedChange change(this, connection_manager_, true); + // If we get here from the destructor we're not going to get + // ProcessViewDeleted(). Copy the map and delete from the copy so that we + // don't have to worry about whether |view_map_| changes or not. + ViewMap view_map_copy; + view_map_.swap(view_map_copy); + STLDeleteValues(&view_map_copy); + } +} + +bool ViewManagerServiceImpl::PrepareForEmbed(const ViewId& view_id) { + const ServerView* view = GetView(view_id); + if (!view || !access_policy_->CanEmbed(view)) + return false; + + // Only allow a node to be the root for one connection. + ViewManagerServiceImpl* existing_owner = + connection_manager_->GetConnectionWithRoot(view_id); + + ConnectionManager::ScopedChange change(this, connection_manager_, true); + RemoveChildrenAsPartOfEmbed(view_id); + if (existing_owner) { + // Never message the originating connection. + connection_manager_->OnConnectionMessagedClient(id_); + existing_owner->RemoveRoot(); + } + return true; +} + +void ViewManagerServiceImpl::CreateView( + Id transport_view_id, + const Callback<void(mojo::ErrorCode)>& callback) { + callback.Run(CreateView(ViewIdFromTransportId(transport_view_id))); +} + +void ViewManagerServiceImpl::DeleteView( + Id transport_view_id, + const Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(transport_view_id)); + bool success = false; + if (view && access_policy_->CanDeleteView(view)) { + ViewManagerServiceImpl* connection = + connection_manager_->GetConnection(view->id().connection_id); + success = connection && connection->DeleteViewImpl(this, view); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::AddView( + Id parent_id, + Id child_id, + const Callback<void(bool)>& callback) { + callback.Run(AddView(ViewIdFromTransportId(parent_id), + ViewIdFromTransportId(child_id))); +} + +void ViewManagerServiceImpl::RemoveViewFromParent( + Id view_id, + const Callback<void(bool)>& callback) { + bool success = false; + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + if (view && view->parent() && access_policy_->CanRemoveViewFromParent(view)) { + success = true; + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->parent()->Remove(view); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::ReorderView(Id view_id, + Id relative_view_id, + OrderDirection direction, + const Callback<void(bool)>& callback) { + bool success = false; + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + ServerView* relative_view = GetView(ViewIdFromTransportId(relative_view_id)); + if (CanReorderView(view, relative_view, direction)) { + success = true; + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->parent()->Reorder(view, relative_view, direction); + connection_manager_->ProcessViewReorder(view, relative_view, direction); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::GetViewTree( + Id view_id, + const Callback<void(Array<ViewDataPtr>)>& callback) { + std::vector<const ServerView*> views( + GetViewTree(ViewIdFromTransportId(view_id))); + callback.Run(ViewsToViewDatas(views)); +} + +void ViewManagerServiceImpl::SetViewSurfaceId( + Id view_id, + mojo::SurfaceIdPtr surface_id, + const Callback<void(bool)>& callback) { + // TODO(sky): add coverage of not being able to set for random node. + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + if (!view || !access_policy_->CanSetViewSurfaceId(view)) { + callback.Run(false); + return; + } + view->SetSurfaceId(surface_id.To<cc::SurfaceId>()); + callback.Run(true); +} + +void ViewManagerServiceImpl::SetViewBounds( + Id view_id, + mojo::RectPtr bounds, + const Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + const bool success = view && access_policy_->CanSetViewBounds(view); + if (success) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + view->SetBounds(bounds.To<gfx::Rect>()); + } + callback.Run(success); +} + +void ViewManagerServiceImpl::SetViewVisibility( + Id transport_view_id, + bool visible, + const Callback<void(bool)>& callback) { + callback.Run( + SetViewVisibility(ViewIdFromTransportId(transport_view_id), visible)); +} + +void ViewManagerServiceImpl::SetViewProperty( + uint32_t view_id, + const mojo::String& name, + mojo::Array<uint8_t> value, + const mojo::Callback<void(bool)>& callback) { + ServerView* view = GetView(ViewIdFromTransportId(view_id)); + const bool success = view && access_policy_->CanSetViewProperties(view); + if (success) { + ConnectionManager::ScopedChange change(this, connection_manager_, false); + + if (value.is_null()) { + view->SetProperty(name, nullptr); + } else { + std::vector<uint8_t> data = value.To<std::vector<uint8_t>>(); + view->SetProperty(name, &data); + } + } + callback.Run(success); +} + +void ViewManagerServiceImpl::EmbedUrl( + const String& url, + Id transport_view_id, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + const Callback<void(bool)>& callback) { + callback.Run(EmbedUrl(url.To<std::string>(), + ViewIdFromTransportId(transport_view_id), + services.Pass(), exposed_services.Pass())); +} + +void ViewManagerServiceImpl::Embed(mojo::Id transport_view_id, + mojo::ViewManagerClientPtr client, + const mojo::Callback<void(bool)>& callback) { + callback.Run(Embed(ViewIdFromTransportId(transport_view_id), client.Pass())); +} + +void ViewManagerServiceImpl::PerformAction( + mojo::Id transport_view_id, + const mojo::String& action, + const mojo::Callback<void(bool)>& callback) { + connection_manager_->GetWindowManagerViewManagerClient()->OnPerformAction( + transport_view_id, action, callback); +} + +bool ViewManagerServiceImpl::IsRootForAccessPolicy(const ViewId& id) const { + return IsRoot(id); +} + +bool ViewManagerServiceImpl::IsViewKnownForAccessPolicy( + const ServerView* view) const { + return IsViewKnown(view); +} + +bool ViewManagerServiceImpl::IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const { + ViewManagerServiceImpl* connection = + connection_manager_->GetConnectionWithRoot(view->id()); + return connection && connection != this; +} + +} // namespace view_manager diff --git a/components/view_manager/view_manager_service_impl.h b/components/view_manager/view_manager_service_impl.h new file mode 100644 index 0000000..0341b7e --- /dev/null +++ b/components/view_manager/view_manager_service_impl.h @@ -0,0 +1,260 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ +#define COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/scoped_ptr.h" +#include "components/view_manager/access_policy_delegate.h" +#include "components/view_manager/ids.h" +#include "third_party/mojo_services/src/surfaces/public/interfaces/surface_id.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" + +namespace gfx { +class Rect; +} + +namespace view_manager { + +class AccessPolicy; +class ConnectionManager; +class ServerView; + +// An instance of ViewManagerServiceImpl is created for every ViewManagerService +// request. ViewManagerServiceImpl tracks all the state and views created by a +// client. ViewManagerServiceImpl coordinates with ConnectionManager to update +// the client (and internal state) as necessary. +class ViewManagerServiceImpl : public mojo::ViewManagerService, + public AccessPolicyDelegate { + public: + using ViewIdSet = base::hash_set<mojo::Id>; + + ViewManagerServiceImpl(ConnectionManager* connection_manager, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id); + ~ViewManagerServiceImpl() override; + + // |services| and |exposed_services| are the ServiceProviders to pass to the + // client via OnEmbed(). + void Init(mojo::ViewManagerClient* client, + mojo::ViewManagerServicePtr service_ptr, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + + mojo::ConnectionSpecificId id() const { return id_; } + mojo::ConnectionSpecificId creator_id() const { return creator_id_; } + const std::string& url() const { return url_; } + + mojo::ViewManagerClient* client() { return client_; } + + // Returns the View with the specified id. + ServerView* GetView(const ViewId& id) { + return const_cast<ServerView*>( + const_cast<const ViewManagerServiceImpl*>(this)->GetView(id)); + } + const ServerView* GetView(const ViewId& id) const; + + // Returns true if this connection's root is |id|. + bool IsRoot(const ViewId& id) const; + + // Returns the id of the root node. This is null if the root has been + // destroyed but the connection is still valid. + const ViewId* root() const { return root_.get(); } + + // Invoked when a connection is about to be destroyed. + void OnWillDestroyViewManagerServiceImpl(ViewManagerServiceImpl* connection); + + // These functions are synchronous variants of those defined in the mojom. The + // ViewManagerService implementations all call into these. See the mojom for + // details. + mojo::ErrorCode CreateView(const ViewId& view_id); + bool AddView(const ViewId& parent_id, const ViewId& child_id); + std::vector<const ServerView*> GetViewTree(const ViewId& view_id) const; + bool SetViewVisibility(const ViewId& view_id, bool visible); + bool EmbedUrl(const std::string& url, + const ViewId& view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services); + bool Embed(const ViewId& view_id, mojo::ViewManagerClientPtr client); + + // The following methods are invoked after the corresponding change has been + // processed. They do the appropriate bookkeeping and update the client as + // necessary. + void ProcessViewBoundsChanged(const ServerView* view, + const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds, + bool originated_change); + void ProcessViewportMetricsChanged(const mojo::ViewportMetrics& old_metrics, + const mojo::ViewportMetrics& new_metrics, + bool originated_change); + void ProcessWillChangeViewHierarchy(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change); + void ProcessViewPropertyChanged(const ServerView* view, + const std::string& name, + const std::vector<uint8_t>* new_data, + bool originated_change); + void ProcessViewHierarchyChanged(const ServerView* view, + const ServerView* new_parent, + const ServerView* old_parent, + bool originated_change); + void ProcessViewReorder(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction, + bool originated_change); + void ProcessViewDeleted(const ViewId& view, bool originated_change); + void ProcessWillChangeViewVisibility(const ServerView* view, + bool originated_change); + void ProcessViewPropertiesChanged(const ServerView* view, + bool originated_change); + + private: + typedef std::map<mojo::ConnectionSpecificId, ServerView*> ViewMap; + + bool IsViewKnown(const ServerView* view) const; + + // These functions return true if the corresponding mojom function is allowed + // for this connection. + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const; + + // Deletes a view owned by this connection. Returns true on success. |source| + // is the connection that originated the change. + bool DeleteViewImpl(ViewManagerServiceImpl* source, ServerView* view); + + // If |view| is known (in |known_views_|) does nothing. Otherwise adds |view| + // to |views|, marks |view| as known and recurses. + void GetUnknownViewsFrom(const ServerView* view, + std::vector<const ServerView*>* views); + + // Removes |view| and all its descendants from |known_views_|. This does not + // recurse through views that were created by this connection. All views owned + // by this connection are added to |local_views|. + void RemoveFromKnown(const ServerView* view, + std::vector<ServerView*>* local_views); + + // Resets the root of this connection. + void RemoveRoot(); + + void RemoveChildrenAsPartOfEmbed(const ViewId& view_id); + + // Converts View(s) to ViewData(s) for transport. This assumes all the views + // are valid for the client. The parent of views the client is not allowed to + // see are set to NULL (in the returned ViewData(s)). + mojo::Array<mojo::ViewDataPtr> ViewsToViewDatas( + const std::vector<const ServerView*>& views); + mojo::ViewDataPtr ViewToViewData(const ServerView* view); + + // Implementation of GetViewTree(). Adds |view| to |views| and recurses if + // CanDescendIntoViewForViewTree() returns true. + void GetViewTreeImpl(const ServerView* view, + std::vector<const ServerView*>* views) const; + + // Notify the client if the drawn state of any of the roots changes. + // |view| is the view that is changing to the drawn state |new_drawn_value|. + void NotifyDrawnStateChanged(const ServerView* view, bool new_drawn_value); + + // Deletes all Views we own. + void DestroyViews(); + + bool PrepareForEmbed(const ViewId& view_id); + + // ViewManagerService: + void CreateView( + mojo::Id transport_view_id, + const mojo::Callback<void(mojo::ErrorCode)>& callback) override; + void DeleteView(mojo::Id transport_view_id, + const mojo::Callback<void(bool)>& callback) override; + void AddView(mojo::Id parent_id, + mojo::Id child_id, + const mojo::Callback<void(bool)>& callback) override; + void RemoveViewFromParent( + mojo::Id view_id, + const mojo::Callback<void(bool)>& callback) override; + void ReorderView(mojo::Id view_id, + mojo::Id relative_view_id, + mojo::OrderDirection direction, + const mojo::Callback<void(bool)>& callback) override; + void GetViewTree(mojo::Id view_id, + const mojo::Callback<void(mojo::Array<mojo::ViewDataPtr>)>& + callback) override; + void SetViewSurfaceId(mojo::Id view_id, + mojo::SurfaceIdPtr surface_id, + const mojo::Callback<void(bool)>& callback) override; + void SetViewBounds(mojo::Id view_id, + mojo::RectPtr bounds, + const mojo::Callback<void(bool)>& callback) override; + void SetViewVisibility(mojo::Id view_id, + bool visible, + const mojo::Callback<void(bool)>& callback) override; + void SetViewProperty(mojo::Id view_id, + const mojo::String& name, + mojo::Array<uint8_t> value, + const mojo::Callback<void(bool)>& callback) override; + void EmbedUrl(const mojo::String& url, + mojo::Id transport_view_id, + mojo::InterfaceRequest<mojo::ServiceProvider> services, + mojo::ServiceProviderPtr exposed_services, + const mojo::Callback<void(bool)>& callback) override; + void Embed(mojo::Id transport_view_id, + mojo::ViewManagerClientPtr client, + const mojo::Callback<void(bool)>& callback) override; + void PerformAction(mojo::Id transport_view_id, + const mojo::String& action, + const mojo::Callback<void(bool)>& callback) override; + + // AccessPolicyDelegate: + bool IsRootForAccessPolicy(const ViewId& id) const override; + bool IsViewKnownForAccessPolicy(const ServerView* view) const override; + bool IsViewRootOfAnotherConnectionForAccessPolicy( + const ServerView* view) const override; + + ConnectionManager* connection_manager_; + + // Id of this connection as assigned by ConnectionManager. + const mojo::ConnectionSpecificId id_; + + // URL this connection was created for. + const std::string url_; + + // ID of the connection that created us. If 0 it indicates either we were + // created by the root, or the connection that created us has been destroyed. + mojo::ConnectionSpecificId creator_id_; + + // The URL of the app that embedded the app this connection was created for. + // NOTE: this is empty if the connection was created by way of directly + // supplying the ViewManagerClient. + const std::string creator_url_; + + mojo::ViewManagerClient* client_; + + scoped_ptr<AccessPolicy> access_policy_; + + // The views created by this connection. This connection owns these objects. + ViewMap view_map_; + + // The set of views that has been communicated to the client. + ViewIdSet known_views_; + + // The root of this connection. This is a scoped_ptr to reinforce the + // connection may have no root. A connection has no root if either the root + // is destroyed or Embed() is invoked on the root. + scoped_ptr<ViewId> root_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceImpl); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_VIEW_MANAGER_SERVICE_IMPL_H_ diff --git a/components/view_manager/view_manager_service_unittest.cc b/components/view_manager/view_manager_service_unittest.cc new file mode 100644 index 0000000..b51e8f5 --- /dev/null +++ b/components/view_manager/view_manager_service_unittest.cc @@ -0,0 +1,471 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "components/view_manager/client_connection.h" +#include "components/view_manager/connection_manager.h" +#include "components/view_manager/connection_manager_delegate.h" +#include "components/view_manager/display_manager.h" +#include "components/view_manager/ids.h" +#include "components/view_manager/server_view.h" +#include "components/view_manager/test_change_tracker.h" +#include "components/view_manager/view_manager_service_impl.h" +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/mojo/src/mojo/public/interfaces/application/service_provider.mojom.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/types.h" +#include "third_party/mojo_services/src/view_manager/public/cpp/util.h" +#include "third_party/mojo_services/src/view_manager/public/interfaces/view_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager.mojom.h" +#include "third_party/mojo_services/src/window_manager/public/interfaces/window_manager_internal.mojom.h" +#include "ui/gfx/geometry/rect.h" + +using mojo::Array; +using mojo::ERROR_CODE_NONE; +using mojo::InterfaceRequest; +using mojo::ServiceProvider; +using mojo::ServiceProviderPtr; +using mojo::String; +using mojo::ViewDataPtr; + +namespace view_manager { +namespace { + +// ----------------------------------------------------------------------------- + +// ViewManagerClient implementation that logs all calls to a TestChangeTracker. +// TODO(sky): refactor so both this and ViewManagerServiceAppTest share code. +class TestViewManagerClient : public mojo::ViewManagerClient { + public: + TestViewManagerClient() {} + ~TestViewManagerClient() override {} + + TestChangeTracker* tracker() { return &tracker_; } + + private: + // ViewManagerClient: + void OnEmbed(uint16_t connection_id, + const String& embedder_url, + ViewDataPtr root, + mojo::ViewManagerServicePtr view_manager_service, + InterfaceRequest<ServiceProvider> services, + ServiceProviderPtr exposed_services, + mojo::ScopedMessagePipeHandle window_manager_pipe) override { + tracker_.OnEmbed(connection_id, embedder_url, root.Pass()); + } + void OnEmbeddedAppDisconnected(uint32_t view) override { + tracker_.OnEmbeddedAppDisconnected(view); + } + void OnViewBoundsChanged(uint32_t view, + mojo::RectPtr old_bounds, + mojo::RectPtr new_bounds) override { + tracker_.OnViewBoundsChanged(view, old_bounds.Pass(), new_bounds.Pass()); + } + void OnViewViewportMetricsChanged( + mojo::ViewportMetricsPtr old_metrics, + mojo::ViewportMetricsPtr new_metrics) override { + tracker_.OnViewViewportMetricsChanged(old_metrics.Pass(), + new_metrics.Pass()); + } + void OnViewHierarchyChanged(uint32_t view, + uint32_t new_parent, + uint32_t old_parent, + Array<ViewDataPtr> views) override { + tracker_.OnViewHierarchyChanged(view, new_parent, old_parent, views.Pass()); + } + void OnViewReordered(uint32_t view_id, + uint32_t relative_view_id, + mojo::OrderDirection direction) override { + tracker_.OnViewReordered(view_id, relative_view_id, direction); + } + void OnViewDeleted(uint32_t view) override { tracker_.OnViewDeleted(view); } + void OnViewVisibilityChanged(uint32_t view, bool visible) override { + tracker_.OnViewVisibilityChanged(view, visible); + } + void OnViewDrawnStateChanged(uint32_t view, bool drawn) override { + tracker_.OnViewDrawnStateChanged(view, drawn); + } + void OnViewSharedPropertyChanged(uint32_t view, + const String& name, + Array<uint8_t> new_data) override { + tracker_.OnViewSharedPropertyChanged(view, name, new_data.Pass()); + } + void OnViewInputEvent(uint32_t view, + mojo::EventPtr event, + const mojo::Callback<void()>& callback) override { + tracker_.OnViewInputEvent(view, event.Pass()); + } + void OnPerformAction(uint32_t view_id, + const String& name, + const mojo::Callback<void(bool)>& callback) override {} + + TestChangeTracker tracker_; + + DISALLOW_COPY_AND_ASSIGN(TestViewManagerClient); +}; + +// ----------------------------------------------------------------------------- + +// ClientConnection implementation that vends TestViewManagerClient. +class TestClientConnection : public ClientConnection { + public: + explicit TestClientConnection(scoped_ptr<ViewManagerServiceImpl> service_impl) + : ClientConnection(service_impl.Pass(), &client_) {} + ~TestClientConnection() override {} + + TestViewManagerClient* client() { return &client_; } + + private: + TestViewManagerClient client_; + + DISALLOW_COPY_AND_ASSIGN(TestClientConnection); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of ConnectionManagerDelegate. +class TestConnectionManagerDelegate : public ConnectionManagerDelegate { + public: + TestConnectionManagerDelegate() : last_connection_(nullptr) {} + ~TestConnectionManagerDelegate() override {} + + TestViewManagerClient* last_client() { + return last_connection_ ? last_connection_->client() : nullptr; + } + + TestClientConnection* last_connection() { return last_connection_; } + + private: + // ConnectionManagerDelegate: + void OnLostConnectionToWindowManager() override {} + + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const std::string& url, + const ViewId& root_id) override { + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager, creator_id, creator_url, url, root_id)); + last_connection_ = new TestClientConnection(service.Pass()); + return last_connection_; + } + ClientConnection* CreateClientConnectionForEmbedAtView( + ConnectionManager* connection_manager, + mojo::InterfaceRequest<mojo::ViewManagerService> service_request, + mojo::ConnectionSpecificId creator_id, + const std::string& creator_url, + const ViewId& root_id, + mojo::ViewManagerClientPtr client) override { + NOTIMPLEMENTED(); + return nullptr; + } + + TestClientConnection* last_connection_; + + DISALLOW_COPY_AND_ASSIGN(TestConnectionManagerDelegate); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of DisplayManager. +class TestDisplayManager : public DisplayManager { + public: + TestDisplayManager() {} + ~TestDisplayManager() override {} + + // DisplayManager: + void Init(ConnectionManager* connection_manager) override {} + void SchedulePaint(const ServerView* view, const gfx::Rect& bounds) override { + } + void SetViewportSize(const gfx::Size& size) override {} + const mojo::ViewportMetrics& GetViewportMetrics() override { + return display_metrices_; + } + + private: + mojo::ViewportMetrics display_metrices_; + + DISALLOW_COPY_AND_ASSIGN(TestDisplayManager); +}; + +// ----------------------------------------------------------------------------- + +// Empty implementation of WindowManagerInternal. +class TestWindowManagerInternal : public mojo::WindowManagerInternal { + public: + TestWindowManagerInternal() {} + ~TestWindowManagerInternal() override {} + + // WindowManagerInternal: + void CreateWindowManagerForViewManagerClient( + uint16_t connection_id, + mojo::ScopedMessagePipeHandle window_manager_pipe) override {} + void SetViewManagerClient(mojo::ScopedMessagePipeHandle) override {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestWindowManagerInternal); +}; + +} // namespace + +// ----------------------------------------------------------------------------- + +class ViewManagerServiceTest : public testing::Test { + public: + ViewManagerServiceTest() : wm_client_(nullptr) {} + ~ViewManagerServiceTest() override {} + + // ViewManagerServiceImpl for the window manager. + ViewManagerServiceImpl* wm_connection() { + return connection_manager_->GetConnection(1); + } + + TestViewManagerClient* last_view_manager_client() { + return delegate_.last_client(); + } + + TestClientConnection* last_client_connection() { + return delegate_.last_connection(); + } + + ConnectionManager* connection_manager() { return connection_manager_.get(); } + + TestViewManagerClient* wm_client() { return wm_client_; } + + protected: + // testing::Test: + void SetUp() override { + connection_manager_.reset(new ConnectionManager( + &delegate_, scoped_ptr<DisplayManager>(new TestDisplayManager), + &wm_internal_)); + scoped_ptr<ViewManagerServiceImpl> service(new ViewManagerServiceImpl( + connection_manager_.get(), kInvalidConnectionId, std::string(), + std::string("mojo:window_manager"), RootViewId())); + scoped_ptr<TestClientConnection> client_connection( + new TestClientConnection(service.Pass())); + wm_client_ = client_connection->client(); + ASSERT_TRUE(wm_client_ != nullptr); + connection_manager_->SetWindowManagerClientConnection( + client_connection.Pass()); + ASSERT_TRUE(wm_connection() != nullptr); + ASSERT_TRUE(wm_connection()->root() != nullptr); + } + + private: + // TestViewManagerClient that is used for the WM connection. + TestViewManagerClient* wm_client_; + + TestWindowManagerInternal wm_internal_; + TestConnectionManagerDelegate delegate_; + scoped_ptr<ConnectionManager> connection_manager_; + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(ViewManagerServiceTest); +}; + +namespace { + +const ServerView* GetFirstCloned(const ServerView* view) { + for (const ServerView* child : view->GetChildren()) { + if (child->id() == ClonedViewId()) + return child; + } + return nullptr; +} + +// Provides common setup for animation tests. Creates the following views: +// 0,1 (the root, provided by view manager) +// 1,1 the second connection is embedded here (view owned by wm_connection()). +// 2,1 bounds=1,2 11x22 +// 2,2 bounds=2,3 6x7 +// 2,3 bounds=3,4 6x7 +// CloneAndAnimate() is invoked for 2,2. +void SetUpAnimate1(ViewManagerServiceTest* test, ViewId* embed_view_id) { + *embed_view_id = ViewId(test->wm_connection()->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, test->wm_connection()->CreateView(*embed_view_id)); + EXPECT_TRUE(test->wm_connection()->SetViewVisibility(*embed_view_id, true)); + EXPECT_TRUE(test->wm_connection()->AddView(*(test->wm_connection()->root()), + *embed_view_id)); + test->wm_connection()->EmbedUrl(std::string(), *embed_view_id, nullptr, + nullptr); + ViewManagerServiceImpl* connection1 = + test->connection_manager()->GetConnectionWithRoot(*embed_view_id); + ASSERT_TRUE(connection1 != nullptr); + ASSERT_NE(connection1, test->wm_connection()); + + const ViewId child1(connection1->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1)); + const ViewId child2(connection1->id(), 2); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2)); + const ViewId child3(connection1->id(), 3); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3)); + + ServerView* v1 = connection1->GetView(child1); + v1->SetVisible(true); + v1->SetBounds(gfx::Rect(1, 2, 11, 22)); + ServerView* v2 = connection1->GetView(child2); + v2->SetVisible(true); + v2->SetBounds(gfx::Rect(2, 3, 6, 7)); + ServerView* v3 = connection1->GetView(child3); + v3->SetVisible(true); + v3->SetBounds(gfx::Rect(3, 4, 6, 7)); + + EXPECT_TRUE(connection1->AddView(*embed_view_id, child1)); + EXPECT_TRUE(connection1->AddView(child1, child2)); + EXPECT_TRUE(connection1->AddView(child2, child3)); + + TestViewManagerClient* connection1_client = test->last_view_manager_client(); + connection1_client->tracker()->changes()->clear(); + test->wm_client()->tracker()->changes()->clear(); + EXPECT_TRUE(test->connection_manager()->CloneAndAnimate(child2)); + EXPECT_TRUE(connection1_client->tracker()->changes()->empty()); + EXPECT_TRUE(test->wm_client()->tracker()->changes()->empty()); + + // We cloned v2. The cloned view ends up as a sibling of it. + const ServerView* cloned_view = GetFirstCloned(connection1->GetView(child1)); + ASSERT_TRUE(cloned_view); + // |cloned_view| should have one and only one cloned child (corresponds to + // |child3|). + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + + // Cloned views should match the bounds of the view they were cloned from. + EXPECT_EQ(v2->bounds(), cloned_view->bounds()); + EXPECT_EQ(v3->bounds(), cloned_view->GetChildren()[0]->bounds()); + + // Cloned views are owned by the ConnectionManager and shouldn't be returned + // from ViewManagerServiceImpl::GetView. + EXPECT_TRUE(connection1->GetView(ClonedViewId()) == nullptr); + EXPECT_TRUE(test->wm_connection()->GetView(ClonedViewId()) == nullptr); +} + +} // namespace + +// Verifies ViewManagerService::GetViewTree() doesn't return cloned views. +TEST_F(ViewManagerServiceTest, ConnectionsCantSeeClonedViews) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + + const ViewId child1(connection1->id(), 1); + const ViewId child2(connection1->id(), 2); + const ViewId child3(connection1->id(), 3); + + // Verify the root doesn't see any cloned views. + std::vector<const ServerView*> views( + wm_connection()->GetViewTree(*wm_connection()->root())); + ASSERT_EQ(5u, views.size()); + ASSERT_TRUE(views[0]->id() == *wm_connection()->root()); + ASSERT_TRUE(views[1]->id() == embed_view_id); + ASSERT_TRUE(views[2]->id() == child1); + ASSERT_TRUE(views[3]->id() == child2); + ASSERT_TRUE(views[4]->id() == child3); + + // Verify connection1 doesn't see any cloned views. + std::vector<const ServerView*> v1_views( + connection1->GetViewTree(embed_view_id)); + ASSERT_EQ(4u, v1_views.size()); + ASSERT_TRUE(v1_views[0]->id() == embed_view_id); + ASSERT_TRUE(v1_views[1]->id() == child1); + ASSERT_TRUE(v1_views[2]->id() == child2); + ASSERT_TRUE(v1_views[3]->id() == child3); +} + +TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnConnectionClose) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + // Destroy connection1, which should force the cloned view to become a child + // of where it was embedded (the embedded view still exists). + connection_manager()->OnConnectionError(last_client_connection()); + + ServerView* embed_view = wm_connection()->GetView(embed_view_id); + ASSERT_TRUE(embed_view != nullptr); + const ServerView* cloned_view = GetFirstCloned(embed_view); + ASSERT_TRUE(cloned_view); + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + + // Because the cloned view changed parents its bounds should have changed. + EXPECT_EQ(gfx::Rect(3, 5, 6, 7), cloned_view->bounds()); + // The bounds of the cloned child should not have changed though. + EXPECT_EQ(gfx::Rect(3, 4, 6, 7), cloned_view->GetChildren()[0]->bounds()); +} + +TEST_F(ViewManagerServiceTest, ClonedViewsPromotedOnHide) { + ViewId embed_view_id; + EXPECT_NO_FATAL_FAILURE(SetUpAnimate1(this, &embed_view_id)); + + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + + // Hide the parent of the cloned view, which should force the cloned view to + // become a sibling of the parent. + const ServerView* view_to_hide = + connection1->GetView(ViewId(connection1->id(), 1)); + ASSERT_TRUE(connection1->SetViewVisibility(view_to_hide->id(), false)); + + const ServerView* cloned_view = GetFirstCloned(view_to_hide->parent()); + ASSERT_TRUE(cloned_view); + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + EXPECT_TRUE(cloned_view->GetChildren()[0]->id() == ClonedViewId()); + EXPECT_EQ(2u, cloned_view->parent()->GetChildren().size()); + EXPECT_TRUE(cloned_view->parent()->GetChildren()[1] == cloned_view); +} + +// Clone and animate on a tree with more depth. Basically that of +// SetUpAnimate1() but cloning 2,1. +TEST_F(ViewManagerServiceTest, CloneAndAnimateLargerDepth) { + const ViewId embed_view_id(wm_connection()->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, wm_connection()->CreateView(embed_view_id)); + EXPECT_TRUE(wm_connection()->SetViewVisibility(embed_view_id, true)); + EXPECT_TRUE( + wm_connection()->AddView(*(wm_connection()->root()), embed_view_id)); + wm_connection()->EmbedUrl(std::string(), embed_view_id, nullptr, nullptr); + ViewManagerServiceImpl* connection1 = + connection_manager()->GetConnectionWithRoot(embed_view_id); + ASSERT_TRUE(connection1 != nullptr); + ASSERT_NE(connection1, wm_connection()); + + const ViewId child1(connection1->id(), 1); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child1)); + const ViewId child2(connection1->id(), 2); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child2)); + const ViewId child3(connection1->id(), 3); + EXPECT_EQ(ERROR_CODE_NONE, connection1->CreateView(child3)); + + ServerView* v1 = connection1->GetView(child1); + v1->SetVisible(true); + connection1->GetView(child2)->SetVisible(true); + connection1->GetView(child3)->SetVisible(true); + + EXPECT_TRUE(connection1->AddView(embed_view_id, child1)); + EXPECT_TRUE(connection1->AddView(child1, child2)); + EXPECT_TRUE(connection1->AddView(child2, child3)); + + TestViewManagerClient* connection1_client = last_view_manager_client(); + connection1_client->tracker()->changes()->clear(); + wm_client()->tracker()->changes()->clear(); + EXPECT_TRUE(connection_manager()->CloneAndAnimate(child1)); + EXPECT_TRUE(connection1_client->tracker()->changes()->empty()); + EXPECT_TRUE(wm_client()->tracker()->changes()->empty()); + + // We cloned v1. The cloned view ends up as a sibling of it. + const ServerView* cloned_view = GetFirstCloned(v1->parent()); + ASSERT_TRUE(cloned_view); + // |cloned_view| should have a child and its child should have a child. + ASSERT_EQ(1u, cloned_view->GetChildren().size()); + const ServerView* cloned_view_child = cloned_view->GetChildren()[0]; + EXPECT_EQ(1u, cloned_view_child->GetChildren().size()); + EXPECT_TRUE(cloned_view_child->id() == ClonedViewId()); +} + +} // namespace view_manager diff --git a/components/view_manager/window_manager_access_policy.cc b/components/view_manager/window_manager_access_policy.cc new file mode 100644 index 0000000..600a4f6 --- /dev/null +++ b/components/view_manager/window_manager_access_policy.cc @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/view_manager/window_manager_access_policy.h" + +#include "components/view_manager/access_policy_delegate.h" +#include "components/view_manager/server_view.h" + +namespace view_manager { + +// TODO(sky): document why this differs from default for each case. Maybe want +// to subclass DefaultAccessPolicy. + +WindowManagerAccessPolicy::WindowManagerAccessPolicy( + mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate) + : connection_id_(connection_id), delegate_(delegate) { +} + +WindowManagerAccessPolicy::~WindowManagerAccessPolicy() { +} + +bool WindowManagerAccessPolicy::CanRemoveViewFromParent( + const ServerView* view) const { + return true; +} + +bool WindowManagerAccessPolicy::CanAddView(const ServerView* parent, + const ServerView* child) const { + return true; +} + +bool WindowManagerAccessPolicy::CanReorderView( + const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const { + return true; +} + +bool WindowManagerAccessPolicy::CanDeleteView(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanGetViewTree(const ServerView* view) const { + return view->id() != ClonedViewId(); +} + +bool WindowManagerAccessPolicy::CanDescendIntoViewForViewTree( + const ServerView* view) const { + return view->id() != ClonedViewId(); +} + +bool WindowManagerAccessPolicy::CanEmbed(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanChangeViewVisibility( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanSetViewSurfaceId( + const ServerView* view) const { + if (delegate_->IsViewRootOfAnotherConnectionForAccessPolicy(view)) + return false; + return view->id().connection_id == connection_id_ || + (delegate_->IsRootForAccessPolicy(view->id())); +} + +bool WindowManagerAccessPolicy::CanSetViewBounds(const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::CanSetViewProperties( + const ServerView* view) const { + return view->id().connection_id == connection_id_; +} + +bool WindowManagerAccessPolicy::ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const { + if (view->id() == ClonedViewId()) + return false; + + // Notify if we've already told the window manager about the view, or if we've + // already told the window manager about the parent. The later handles the + // case of a view that wasn't parented to the root getting added to the root. + return IsViewKnown(view) || (*new_parent && IsViewKnown(*new_parent)); +} + +bool WindowManagerAccessPolicy::IsViewKnown(const ServerView* view) const { + return delegate_->IsViewKnownForAccessPolicy(view); +} + +} // namespace view_manager diff --git a/components/view_manager/window_manager_access_policy.h b/components/view_manager/window_manager_access_policy.h new file mode 100644 index 0000000..94cda10 --- /dev/null +++ b/components/view_manager/window_manager_access_policy.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ +#define COMPONENTS_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ + +#include "base/basictypes.h" +#include "components/view_manager/access_policy.h" + +namespace view_manager { + +class AccessPolicyDelegate; + +class WindowManagerAccessPolicy : public AccessPolicy { + public: + WindowManagerAccessPolicy(mojo::ConnectionSpecificId connection_id, + AccessPolicyDelegate* delegate); + ~WindowManagerAccessPolicy() override; + + // AccessPolicy: + bool CanRemoveViewFromParent(const ServerView* view) const override; + bool CanAddView(const ServerView* parent, + const ServerView* child) const override; + bool CanReorderView(const ServerView* view, + const ServerView* relative_view, + mojo::OrderDirection direction) const override; + bool CanDeleteView(const ServerView* view) const override; + bool CanGetViewTree(const ServerView* view) const override; + bool CanDescendIntoViewForViewTree(const ServerView* view) const override; + bool CanEmbed(const ServerView* view) const override; + bool CanChangeViewVisibility(const ServerView* view) const override; + bool CanSetViewSurfaceId(const ServerView* view) const override; + bool CanSetViewBounds(const ServerView* view) const override; + bool CanSetViewProperties(const ServerView* view) const override; + bool ShouldNotifyOnHierarchyChange( + const ServerView* view, + const ServerView** new_parent, + const ServerView** old_parent) const override; + + private: + bool IsViewKnown(const ServerView* view) const; + + const mojo::ConnectionSpecificId connection_id_; + AccessPolicyDelegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(WindowManagerAccessPolicy); +}; + +} // namespace view_manager + +#endif // COMPONENTS_VIEW_MANAGER_WINDOW_MANAGER_ACCESS_POLICY_H_ |