1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/controls/native/native_view_host_aura.h"
6
7#include "base/basictypes.h"
8#include "base/memory/scoped_ptr.h"
9#include "ui/aura/client/aura_constants.h"
10#include "ui/aura/window.h"
11#include "ui/base/cursor/cursor.h"
12#include "ui/views/controls/native/native_view_host.h"
13#include "ui/views/controls/native/native_view_host_test_base.h"
14#include "ui/views/view.h"
15#include "ui/views/view_constants_aura.h"
16#include "ui/views/widget/widget.h"
17
18namespace views {
19
20// Observer watching for window visibility and bounds change events. This is
21// used to verify that the child and clipping window operations are done in the
22// right order.
23class NativeViewHostWindowObserver : public aura::WindowObserver {
24 public:
25  enum EventType {
26    EVENT_NONE,
27    EVENT_SHOWN,
28    EVENT_HIDDEN,
29    EVENT_BOUNDS_CHANGED,
30  };
31
32  struct EventDetails {
33    EventType type;
34    aura::Window* window;
35    gfx::Rect bounds;
36    bool operator!=(const EventDetails& rhs) {
37      return type != rhs.type || window != rhs.window || bounds != rhs.bounds;
38    }
39  };
40
41  NativeViewHostWindowObserver() {}
42  virtual ~NativeViewHostWindowObserver() {}
43
44  const std::vector<EventDetails>& events() const { return events_; }
45
46  // aura::WindowObserver overrides
47  virtual void OnWindowVisibilityChanged(aura::Window* window,
48                                         bool visible) OVERRIDE {
49    EventDetails event;
50    event.type = visible ? EVENT_SHOWN : EVENT_HIDDEN;
51    event.window = window;
52    event.bounds = window->GetBoundsInRootWindow();
53
54    // Dedupe events as a single Hide() call can result in several
55    // notifications.
56    if (events_.size() == 0u || events_.back() != event)
57      events_.push_back(event);
58  }
59
60  virtual void OnWindowBoundsChanged(aura::Window* window,
61                                     const gfx::Rect& old_bounds,
62                                     const gfx::Rect& new_bounds) OVERRIDE {
63    EventDetails event;
64    event.type = EVENT_BOUNDS_CHANGED;
65    event.window = window;
66    event.bounds = window->GetBoundsInRootWindow();
67    events_.push_back(event);
68  }
69
70 private:
71  std::vector<EventDetails> events_;
72  gfx::Rect bounds_at_visibility_changed_;
73
74  DISALLOW_COPY_AND_ASSIGN(NativeViewHostWindowObserver);
75};
76
77class NativeViewHostAuraTest : public test::NativeViewHostTestBase {
78 public:
79  NativeViewHostAuraTest() {
80  }
81
82  NativeViewHostAura* native_host() {
83    return static_cast<NativeViewHostAura*>(GetNativeWrapper());
84  }
85
86  Widget* child() {
87    return child_.get();
88  }
89
90  aura::Window* clipping_window() { return &(native_host()->clipping_window_); }
91
92  void CreateHost() {
93    CreateTopLevel();
94    CreateTestingHost();
95    child_.reset(CreateChildForHost(toplevel()->GetNativeView(),
96                                    toplevel()->GetRootView(),
97                                    new View,
98                                    host()));
99  }
100
101 private:
102  scoped_ptr<Widget> child_;
103
104  DISALLOW_COPY_AND_ASSIGN(NativeViewHostAuraTest);
105};
106
107// Verifies NativeViewHostAura stops observing native view on destruction.
108TEST_F(NativeViewHostAuraTest, StopObservingNativeViewOnDestruct) {
109  CreateHost();
110  aura::Window* child_win = child()->GetNativeView();
111  NativeViewHostAura* aura_host = native_host();
112
113  EXPECT_TRUE(child_win->HasObserver(aura_host));
114  DestroyHost();
115  EXPECT_FALSE(child_win->HasObserver(aura_host));
116}
117
118// Tests that the kHostViewKey is correctly set and cleared.
119TEST_F(NativeViewHostAuraTest, HostViewPropertyKey) {
120  // Create the NativeViewHost and attach a NativeView.
121  CreateHost();
122  aura::Window* child_win = child()->GetNativeView();
123  EXPECT_EQ(host(), child_win->GetProperty(views::kHostViewKey));
124  EXPECT_EQ(host()->GetWidget()->GetNativeView(),
125            child_win->GetProperty(aura::client::kHostWindowKey));
126  EXPECT_EQ(host(), clipping_window()->GetProperty(views::kHostViewKey));
127
128  host()->Detach();
129  EXPECT_FALSE(child_win->GetProperty(views::kHostViewKey));
130  EXPECT_FALSE(child_win->GetProperty(aura::client::kHostWindowKey));
131  EXPECT_TRUE(clipping_window()->GetProperty(views::kHostViewKey));
132
133  host()->Attach(child_win);
134  EXPECT_EQ(host(), child_win->GetProperty(views::kHostViewKey));
135  EXPECT_EQ(host()->GetWidget()->GetNativeView(),
136            child_win->GetProperty(aura::client::kHostWindowKey));
137  EXPECT_EQ(host(), clipping_window()->GetProperty(views::kHostViewKey));
138
139  DestroyHost();
140  EXPECT_FALSE(child_win->GetProperty(views::kHostViewKey));
141  EXPECT_FALSE(child_win->GetProperty(aura::client::kHostWindowKey));
142}
143
144// Tests that the NativeViewHost reports the cursor set on its native view.
145TEST_F(NativeViewHostAuraTest, CursorForNativeView) {
146  CreateHost();
147
148  toplevel()->SetCursor(ui::kCursorHand);
149  child()->SetCursor(ui::kCursorWait);
150  ui::MouseEvent move_event(ui::ET_MOUSE_MOVED, gfx::Point(0, 0),
151                            gfx::Point(0, 0), 0, 0);
152
153  EXPECT_EQ(ui::kCursorWait, host()->GetCursor(move_event).native_type());
154
155  DestroyHost();
156}
157
158// Test that destroying the top level widget before destroying the attached
159// NativeViewHost works correctly. Specifically the associated NVH should be
160// destroyed and there shouldn't be any errors.
161TEST_F(NativeViewHostAuraTest, DestroyWidget) {
162  ResetHostDestroyedCount();
163  CreateHost();
164  ReleaseHost();
165  EXPECT_EQ(0, host_destroyed_count());
166  DestroyTopLevel();
167  EXPECT_EQ(1, host_destroyed_count());
168}
169
170// Test that the fast resize path places the clipping and content windows were
171// they are supposed to be.
172TEST_F(NativeViewHostAuraTest, FastResizePath) {
173  CreateHost();
174  toplevel()->SetBounds(gfx::Rect(20, 20, 100, 100));
175
176  // Without fast resize, the clipping window should size to the native view
177  // with the native view positioned at the origin of the clipping window and
178  // the clipping window positioned where the native view was requested.
179  host()->set_fast_resize(false);
180  native_host()->ShowWidget(5, 10, 100, 100);
181  EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
182            host()->native_view()->bounds().ToString());
183  EXPECT_EQ(gfx::Rect(5, 10, 100, 100).ToString(),
184            clipping_window()->bounds().ToString());
185
186  // With fast resize, the native view should remain the same size but be
187  // clipped the requested size.
188  host()->set_fast_resize(true);
189  native_host()->ShowWidget(10, 25, 50, 50);
190  EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
191            host()->native_view()->bounds().ToString());
192  EXPECT_EQ(gfx::Rect(10, 25, 50, 50).ToString(),
193            clipping_window()->bounds().ToString());
194
195  // Turning off fast resize should make the native view start resizing again.
196  host()->set_fast_resize(false);
197  native_host()->ShowWidget(10, 25, 50, 50);
198  EXPECT_EQ(gfx::Rect(0, 0, 50, 50).ToString(),
199            host()->native_view()->bounds().ToString());
200  EXPECT_EQ(gfx::Rect(10, 25, 50, 50).ToString(),
201            clipping_window()->bounds().ToString());
202
203  DestroyHost();
204}
205
206// Test installing and uninstalling a clip.
207TEST_F(NativeViewHostAuraTest, InstallClip) {
208  CreateHost();
209  toplevel()->SetBounds(gfx::Rect(20, 20, 100, 100));
210
211  // Without a clip, the clipping window should always be positioned at the
212  // requested coordinates with the native view positioned at the origin of the
213  // clipping window.
214  native_host()->ShowWidget(10, 20, 100, 100);
215  EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
216            host()->native_view()->bounds().ToString());
217  EXPECT_EQ(gfx::Rect(10, 20, 100, 100).ToString(),
218            clipping_window()->bounds().ToString());
219
220  // Clip to the bottom right quarter of the native view.
221  native_host()->InstallClip(60, 70, 50, 50);
222  native_host()->ShowWidget(10, 20, 100, 100);
223  EXPECT_EQ(gfx::Rect(-50, -50, 100, 100).ToString(),
224            host()->native_view()->bounds().ToString());
225  EXPECT_EQ(gfx::Rect(60, 70, 50, 50).ToString(),
226            clipping_window()->bounds().ToString());
227
228  // Clip to the center of the native view.
229  native_host()->InstallClip(35, 45, 50, 50);
230  native_host()->ShowWidget(10, 20, 100, 100);
231  EXPECT_EQ(gfx::Rect(-25, -25, 100, 100).ToString(),
232            host()->native_view()->bounds().ToString());
233  EXPECT_EQ(gfx::Rect(35, 45, 50, 50).ToString(),
234            clipping_window()->bounds().ToString());
235
236  // Uninstalling the clip should make the clipping window match the native view
237  // again.
238  native_host()->UninstallClip();
239  native_host()->ShowWidget(10, 20, 100, 100);
240  EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
241            host()->native_view()->bounds().ToString());
242  EXPECT_EQ(gfx::Rect(10, 20, 100, 100).ToString(),
243            clipping_window()->bounds().ToString());
244
245  DestroyHost();
246}
247
248// Ensure native view is parented to the root window after detaching. This is
249// a regression test for http://crbug.com/389261.
250TEST_F(NativeViewHostAuraTest, ParentAfterDetach) {
251  CreateHost();
252  aura::Window* child_win = child()->GetNativeView();
253  aura::Window* root_window = child_win->GetRootWindow();
254  aura::WindowTreeHost* child_win_tree_host = child_win->GetHost();
255
256  host()->Detach();
257  EXPECT_EQ(root_window, child_win->GetRootWindow());
258  EXPECT_EQ(child_win_tree_host, child_win->GetHost());
259
260  DestroyHost();
261}
262
263// Ensure the clipping window is hidden before setting the native view's bounds.
264// This is a regression test for http://crbug.com/388699.
265TEST_F(NativeViewHostAuraTest, RemoveClippingWindowOrder) {
266  CreateHost();
267  toplevel()->SetBounds(gfx::Rect(20, 20, 100, 100));
268  native_host()->ShowWidget(10, 20, 100, 100);
269
270  NativeViewHostWindowObserver test_observer;
271  clipping_window()->AddObserver(&test_observer);
272  child()->GetNativeView()->AddObserver(&test_observer);
273
274  host()->Detach();
275
276  ASSERT_EQ(3u, test_observer.events().size());
277  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_HIDDEN,
278            test_observer.events()[0].type);
279  EXPECT_EQ(clipping_window(), test_observer.events()[0].window);
280  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_BOUNDS_CHANGED,
281            test_observer.events()[1].type);
282  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[1].window);
283  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_HIDDEN,
284            test_observer.events()[2].type);
285  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[2].window);
286
287  clipping_window()->RemoveObserver(&test_observer);
288  child()->GetNativeView()->RemoveObserver(&test_observer);
289
290  DestroyHost();
291}
292
293// Ensure the native view receives the correct bounds notification when it is
294// attached. This is a regression test for https://crbug.com/399420.
295TEST_F(NativeViewHostAuraTest, Attach) {
296  CreateHost();
297  host()->Detach();
298
299  child()->GetNativeView()->SetBounds(gfx::Rect(0, 0, 0, 0));
300  toplevel()->SetBounds(gfx::Rect(0, 0, 100, 100));
301  host()->SetBounds(10, 10, 80, 80);
302
303  NativeViewHostWindowObserver test_observer;
304  child()->GetNativeView()->AddObserver(&test_observer);
305
306  host()->Attach(child()->GetNativeView());
307
308  ASSERT_EQ(3u, test_observer.events().size());
309  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_BOUNDS_CHANGED,
310            test_observer.events()[0].type);
311  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[0].window);
312  EXPECT_EQ(gfx::Rect(10, 10, 80, 80).ToString(),
313            test_observer.events()[0].bounds.ToString());
314  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_SHOWN,
315            test_observer.events()[1].type);
316  EXPECT_EQ(child()->GetNativeView(), test_observer.events()[1].window);
317  EXPECT_EQ(gfx::Rect(10, 10, 80, 80).ToString(),
318            test_observer.events()[1].bounds.ToString());
319  EXPECT_EQ(NativeViewHostWindowObserver::EVENT_SHOWN,
320            test_observer.events()[2].type);
321  EXPECT_EQ(clipping_window(), test_observer.events()[2].window);
322  EXPECT_EQ(gfx::Rect(10, 10, 80, 80).ToString(),
323            test_observer.events()[2].bounds.ToString());
324
325  child()->GetNativeView()->RemoveObserver(&test_observer);
326  DestroyHost();
327}
328
329// Ensure the clipping window is hidden with the native view. This is a
330// regression test for https://crbug.com/408877.
331TEST_F(NativeViewHostAuraTest, SimpleShowAndHide) {
332  CreateHost();
333
334  toplevel()->SetBounds(gfx::Rect(20, 20, 100, 100));
335  toplevel()->Show();
336
337  host()->SetBounds(10, 10, 80, 80);
338  EXPECT_TRUE(clipping_window()->IsVisible());
339  EXPECT_TRUE(child()->IsVisible());
340
341  host()->SetVisible(false);
342  EXPECT_FALSE(clipping_window()->IsVisible());
343  EXPECT_FALSE(child()->IsVisible());
344
345  DestroyHost();
346  DestroyTopLevel();
347}
348
349}  // namespace views
350