summaryrefslogtreecommitdiffstats
path: root/components/view_manager
diff options
context:
space:
mode:
authorsky <sky@chromium.org>2015-04-20 20:08:26 -0700
committerCommit bot <commit-bot@chromium.org>2015-04-21 03:08:28 +0000
commitba75ded669862381ab3885197562fe05982df6ad (patch)
tree70f5da5f668e6282aa8013c230633a1bcbb5bb25 /components/view_manager
parentc2a1922419f12248bf72a1ea744e66ced39b4cd5 (diff)
downloadchromium_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')
-rw-r--r--components/view_manager/BUILD.gn182
-rw-r--r--components/view_manager/DEPS9
-rw-r--r--components/view_manager/access_policy.h52
-rw-r--r--components/view_manager/access_policy_delegate.h36
-rw-r--r--components/view_manager/animation_runner.cc157
-rw-r--r--components/view_manager/animation_runner.h114
-rw-r--r--components/view_manager/animation_runner_observer.h23
-rw-r--r--components/view_manager/animation_runner_unittest.cc641
-rw-r--r--components/view_manager/client_connection.cc39
-rw-r--r--components/view_manager/client_connection.h62
-rw-r--r--components/view_manager/connection_manager.cc507
-rw-r--r--components/view_manager/connection_manager.h252
-rw-r--r--components/view_manager/connection_manager_delegate.h51
-rw-r--r--components/view_manager/default_access_policy.cc112
-rw-r--r--components/view_manager/default_access_policy.h53
-rw-r--r--components/view_manager/display_manager.cc187
-rw-r--r--components/view_manager/display_manager.h96
-rw-r--r--components/view_manager/focus_controller.cc54
-rw-r--r--components/view_manager/focus_controller.h53
-rw-r--r--components/view_manager/focus_controller_delegate.h23
-rw-r--r--components/view_manager/focus_controller_unittest.cc106
-rw-r--r--components/view_manager/gesture_manager.cc701
-rw-r--r--components/view_manager/gesture_manager.h188
-rw-r--r--components/view_manager/gesture_manager_delegate.h31
-rw-r--r--components/view_manager/gesture_manager_unittest.cc467
-rw-r--r--components/view_manager/ids.h62
-rw-r--r--components/view_manager/main.cc12
-rw-r--r--components/view_manager/scheduled_animation_group.cc352
-rw-r--r--components/view_manager/scheduled_animation_group.h110
-rw-r--r--components/view_manager/scheduled_animation_group_unittest.cc97
-rw-r--r--components/view_manager/server_view.cc234
-rw-r--r--components/view_manager/server_view.h120
-rw-r--r--components/view_manager/server_view_delegate.h48
-rw-r--r--components/view_manager/server_view_drawn_tracker.cc76
-rw-r--r--components/view_manager/server_view_drawn_tracker.h58
-rw-r--r--components/view_manager/server_view_drawn_tracker_observer.h28
-rw-r--r--components/view_manager/server_view_drawn_tracker_unittest.cc136
-rw-r--r--components/view_manager/server_view_observer.h63
-rw-r--r--components/view_manager/test_change_tracker.cc314
-rw-r--r--components/view_manager/test_change_tracker.h157
-rw-r--r--components/view_manager/test_server_view_delegate.cc30
-rw-r--r--components/view_manager/test_server_view_delegate.h32
-rw-r--r--components/view_manager/view_coordinate_conversions.cc69
-rw-r--r--components/view_manager/view_coordinate_conversions.h35
-rw-r--r--components/view_manager/view_coordinate_conversions_unittest.cc58
-rw-r--r--components/view_manager/view_locator.cc35
-rw-r--r--components/view_manager/view_locator.h24
-rw-r--r--components/view_manager/view_manager_app.cc137
-rw-r--r--components/view_manager/view_manager_app.h86
-rw-r--r--components/view_manager/view_manager_client_apptest.cc613
-rw-r--r--components/view_manager/view_manager_service_apptest.cc1499
-rw-r--r--components/view_manager/view_manager_service_impl.cc654
-rw-r--r--components/view_manager/view_manager_service_impl.h260
-rw-r--r--components/view_manager/view_manager_service_unittest.cc471
-rw-r--r--components/view_manager/window_manager_access_policy.cc97
-rw-r--r--components/view_manager/window_manager_access_policy.h52
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_