1// Copyright (c) 2012 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 "ash/wm/frame_painter.h"
6
7#include "ash/ash_constants.h"
8#include "ash/shell.h"
9#include "ash/shell_window_ids.h"
10#include "ash/test/ash_test_base.h"
11#include "ash/wm/property_util.h"
12#include "ash/wm/window_properties.h"
13#include "ash/wm/window_util.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "grit/ash_resources.h"
17#include "testing/gtest/include/gtest/gtest.h"
18#include "ui/aura/client/aura_constants.h"
19#include "ui/aura/root_window.h"
20#include "ui/aura/window_observer.h"
21#include "ui/base/hit_test.h"
22#include "ui/base/theme_provider.h"
23#include "ui/gfx/font.h"
24#include "ui/gfx/screen.h"
25#include "ui/views/controls/button/button.h"
26#include "ui/views/controls/button/image_button.h"
27#include "ui/views/widget/widget.h"
28#include "ui/views/widget/widget_delegate.h"
29#include "ui/views/widget/widget_observer.h"
30#include "ui/views/window/non_client_view.h"
31
32using ash::FramePainter;
33using ui::ThemeProvider;
34using views::Button;
35using views::ImageButton;
36using views::NonClientFrameView;
37using views::ToggleImageButton;
38using views::Widget;
39
40namespace {
41
42bool ImagesMatch(ImageButton* button,
43                 int normal_image_id,
44                 int hovered_image_id,
45                 int pressed_image_id) {
46  ThemeProvider* theme = button->GetWidget()->GetThemeProvider();
47  gfx::ImageSkia* normal = theme->GetImageSkiaNamed(normal_image_id);
48  gfx::ImageSkia* hovered = theme->GetImageSkiaNamed(hovered_image_id);
49  gfx::ImageSkia* pressed = theme->GetImageSkiaNamed(pressed_image_id);
50  return button->GetImage(Button::STATE_NORMAL).BackedBySameObjectAs(*normal) &&
51      button->GetImage(Button::STATE_HOVERED).BackedBySameObjectAs(*hovered) &&
52      button->GetImage(Button::STATE_PRESSED).BackedBySameObjectAs(*pressed);
53}
54
55class ResizableWidgetDelegate : public views::WidgetDelegate {
56 public:
57  ResizableWidgetDelegate(views::Widget* widget) {
58    widget_ = widget;
59  }
60
61  virtual bool CanResize() const OVERRIDE { return true; }
62  // Implementations of the widget class.
63  virtual views::Widget* GetWidget() OVERRIDE { return widget_; }
64  virtual const views::Widget* GetWidget() const OVERRIDE { return widget_; }
65  virtual void DeleteDelegate() OVERRIDE {
66    delete this;
67  }
68
69 private:
70  views::Widget* widget_;
71
72  DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate);
73};
74
75class WindowRepaintChecker : public aura::WindowObserver {
76 public:
77  explicit WindowRepaintChecker(aura::Window* window)
78      : is_paint_scheduled_(false) {
79    window->AddObserver(this);
80  }
81  virtual ~WindowRepaintChecker() {
82  }
83
84  bool IsPaintScheduledAndReset() {
85    bool result = is_paint_scheduled_;
86    is_paint_scheduled_ = false;
87    return result;
88  }
89
90 private:
91  // aura::WindowObserver overrides:
92  virtual void OnWindowPaintScheduled(aura::Window* window,
93                                      const gfx::Rect& region) OVERRIDE {
94    is_paint_scheduled_ = true;
95  }
96  virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {
97    window->RemoveObserver(this);
98  }
99
100  bool is_paint_scheduled_;
101
102  DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker);
103};
104
105// Modifies the values of kInactiveWindowOpacity, kActiveWindowOpacity, and
106// kSoloWindowOpacity for the lifetime of the class. This is useful so that
107// the constants each have different values.
108class ScopedOpacityConstantModifier {
109 public:
110  ScopedOpacityConstantModifier()
111      : initial_active_window_opacity_(
112            ash::FramePainter::kActiveWindowOpacity),
113        initial_inactive_window_opacity_(
114            ash::FramePainter::kInactiveWindowOpacity),
115        initial_solo_window_opacity_(ash::FramePainter::kSoloWindowOpacity) {
116    ash::FramePainter::kActiveWindowOpacity = 100;
117    ash::FramePainter::kInactiveWindowOpacity = 120;
118    ash::FramePainter::kSoloWindowOpacity = 140;
119  }
120  ~ScopedOpacityConstantModifier() {
121    ash::FramePainter::kActiveWindowOpacity = initial_active_window_opacity_;
122    ash::FramePainter::kInactiveWindowOpacity =
123        initial_inactive_window_opacity_;
124    ash::FramePainter::kSoloWindowOpacity = initial_solo_window_opacity_;
125  }
126
127 private:
128  int initial_active_window_opacity_;
129  int initial_inactive_window_opacity_;
130  int initial_solo_window_opacity_;
131
132  DISALLOW_COPY_AND_ASSIGN(ScopedOpacityConstantModifier);
133};
134
135// Creates a new FramePainter with empty buttons. Caller owns the memory.
136FramePainter* CreateTestPainter(Widget* widget) {
137  FramePainter* painter = new FramePainter();
138  ImageButton* size_button = new ImageButton(NULL);
139  ImageButton* close_button = new ImageButton(NULL);
140  // Add the buttons to the widget's non-client frame view so they will be
141  // deleted when the widget is destroyed.
142  NonClientFrameView* frame_view = widget->non_client_view()->frame_view();
143  frame_view->AddChildView(size_button);
144  frame_view->AddChildView(close_button);
145  painter->Init(widget,
146                NULL,
147                size_button,
148                close_button,
149                FramePainter::SIZE_BUTTON_MAXIMIZES);
150  return painter;
151}
152
153// Self-owned manager of the frame painter which deletes the painter and itself
154// when its widget is closed.
155class FramePainterOwner : views::WidgetObserver {
156 public:
157  explicit FramePainterOwner(Widget* widget)
158      : frame_painter_(CreateTestPainter(widget)) {
159    widget->AddObserver(this);
160  }
161
162  virtual ~FramePainterOwner() {}
163
164  FramePainter* frame_painter() { return frame_painter_.get(); }
165
166 private:
167  virtual void OnWidgetDestroying(Widget* widget) OVERRIDE {
168    widget->RemoveObserver(this);
169    // Do not delete directly here, since the task of FramePainter causing
170    // the crash of crbug.com/273310 may run after this class handles this
171    // event.
172    base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
173  }
174
175  scoped_ptr<FramePainter> frame_painter_;
176
177  DISALLOW_COPY_AND_ASSIGN(FramePainterOwner);
178};
179
180}  // namespace
181
182namespace ash {
183
184class FramePainterTest : public ash::test::AshTestBase {
185 public:
186  // Creates a test widget that owns its native widget.
187  Widget* CreateTestWidget() {
188    Widget* widget = new Widget;
189    Widget::InitParams params;
190    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
191    params.context = CurrentContext();
192    widget->Init(params);
193    return widget;
194  }
195
196  Widget* CreateAlwaysOnTopWidget() {
197    Widget* widget = new Widget;
198    Widget::InitParams params;
199    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
200    params.context = CurrentContext();
201    params.keep_on_top = true;
202    widget->Init(params);
203    return widget;
204  }
205
206  Widget* CreatePanelWidget() {
207    Widget* widget = new Widget;
208    Widget::InitParams params;
209    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
210    params.context = CurrentContext();
211    params.type = Widget::InitParams::TYPE_PANEL;
212    widget->Init(params);
213    return widget;
214  }
215
216  Widget* CreateResizableWidget() {
217    Widget* widget = new Widget;
218    Widget::InitParams params;
219    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
220    params.context = CurrentContext();
221    params.keep_on_top = true;
222    params.delegate = new ResizableWidgetDelegate(widget);
223    params.type = Widget::InitParams::TYPE_WINDOW;
224    widget->Init(params);
225    return widget;
226  }
227};
228
229TEST_F(FramePainterTest, CreateAndDeleteSingleWindow) {
230  // Ensure that creating/deleting a window works well and doesn't cause
231  // crashes.  See crbug.com/155634
232  aura::RootWindow* root = Shell::GetActiveRootWindow();
233
234  scoped_ptr<Widget> widget(CreateTestWidget());
235  scoped_ptr<FramePainter> painter(CreateTestPainter(widget.get()));
236  widget->Show();
237
238  // We only have one window, so it should use a solo header.
239  EXPECT_TRUE(painter->UseSoloWindowHeader());
240  EXPECT_TRUE(root->GetProperty(internal::kSoloWindowHeaderKey));
241
242  // Close the window.
243  widget.reset();
244  EXPECT_FALSE(root->GetProperty(internal::kSoloWindowHeaderKey));
245
246  // Recreate another window again.
247  widget.reset(CreateTestWidget());
248  painter.reset(CreateTestPainter(widget.get()));
249  widget->Show();
250  EXPECT_TRUE(painter->UseSoloWindowHeader());
251  EXPECT_TRUE(root->GetProperty(internal::kSoloWindowHeaderKey));
252}
253
254TEST_F(FramePainterTest, LayoutHeader) {
255  scoped_ptr<Widget> widget(CreateTestWidget());
256  ImageButton size_button(NULL);
257  ImageButton close_button(NULL);
258  NonClientFrameView* frame_view = widget->non_client_view()->frame_view();
259  frame_view->AddChildView(&size_button);
260  frame_view->AddChildView(&close_button);
261  scoped_ptr<FramePainter> painter(new FramePainter);
262  painter->Init(widget.get(),
263                NULL,
264                &size_button,
265                &close_button,
266                FramePainter::SIZE_BUTTON_MAXIMIZES);
267  widget->Show();
268
269  // Basic layout.
270  painter->LayoutHeader(frame_view, false);
271  EXPECT_TRUE(ImagesMatch(&close_button,
272                          IDR_AURA_WINDOW_CLOSE,
273                          IDR_AURA_WINDOW_CLOSE_H,
274                          IDR_AURA_WINDOW_CLOSE_P));
275  EXPECT_TRUE(ImagesMatch(&size_button,
276                          IDR_AURA_WINDOW_MAXIMIZE,
277                          IDR_AURA_WINDOW_MAXIMIZE_H,
278                          IDR_AURA_WINDOW_MAXIMIZE_P));
279
280  // Shorter layout.
281  painter->LayoutHeader(frame_view, true);
282  EXPECT_TRUE(ImagesMatch(&close_button,
283                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
284                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
285                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P));
286  EXPECT_TRUE(ImagesMatch(&size_button,
287                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
288                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
289                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P));
290
291  // Maximized shorter layout.
292  widget->Maximize();
293  painter->LayoutHeader(frame_view, true);
294  EXPECT_TRUE(ImagesMatch(&close_button,
295                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
296                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
297                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P));
298  EXPECT_TRUE(ImagesMatch(&size_button,
299                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
300                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
301                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P));
302
303  // Fullscreen can show the buttons during an immersive reveal, so it should
304  // use the same images as maximized.
305  widget->SetFullscreen(true);
306  painter->LayoutHeader(frame_view, true);
307  EXPECT_TRUE(ImagesMatch(&close_button,
308                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
309                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
310                          IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P));
311  EXPECT_TRUE(ImagesMatch(&size_button,
312                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
313                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
314                          IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P));
315}
316
317TEST_F(FramePainterTest, UseSoloWindowHeader) {
318  // Create a widget and a painter for it.
319  scoped_ptr<Widget> w1(CreateTestWidget());
320  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
321  w1->Show();
322
323  // We only have one window, so it should use a solo header.
324  EXPECT_TRUE(p1->UseSoloWindowHeader());
325
326  // Create a second widget and painter.
327  scoped_ptr<Widget> w2(CreateTestWidget());
328  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
329  w2->Show();
330
331  // Now there are two windows, so we should not use solo headers. This only
332  // needs to test |p1| because "solo window headers" are a per-root-window
333  // property.
334  EXPECT_FALSE(p1->UseSoloWindowHeader());
335
336  // Hide one window.  Solo should be enabled.
337  w2->Hide();
338  EXPECT_TRUE(p1->UseSoloWindowHeader());
339
340  // Show that window.  Solo should be disabled.
341  w2->Show();
342  EXPECT_FALSE(p1->UseSoloWindowHeader());
343
344  // Minimize the second window.  Solo should be enabled.
345  w2->Minimize();
346  EXPECT_TRUE(p1->UseSoloWindowHeader());
347
348  // Close the minimized window.
349  w2.reset();
350  EXPECT_TRUE(p1->UseSoloWindowHeader());
351
352  // Open an always-on-top widget (which lives in a different container).
353  scoped_ptr<Widget> w3(CreateAlwaysOnTopWidget());
354  scoped_ptr<FramePainter> p3(CreateTestPainter(w3.get()));
355  w3->Show();
356  EXPECT_FALSE(p3->UseSoloWindowHeader());
357
358  // Close the always-on-top widget.
359  w3.reset();
360  EXPECT_TRUE(p1->UseSoloWindowHeader());
361}
362
363// An open V2 app window should cause browser windows not to use the
364// solo window header.
365TEST_F(FramePainterTest, UseSoloWindowHeaderWithApp) {
366  // Create a widget and a painter for it.
367  scoped_ptr<Widget> w1(CreateTestWidget());
368  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
369  w1->Show();
370
371  // We only have one window, so it should use a solo header.
372  EXPECT_TRUE(p1->UseSoloWindowHeader());
373
374  // Simulate a V2 app window, which is part of the active workspace but does
375  // not have a frame painter.
376  scoped_ptr<Widget> w2(CreateTestWidget());
377  w2->Show();
378
379  // Now there are two windows, so we should not use solo headers.
380  EXPECT_FALSE(p1->UseSoloWindowHeader());
381
382  // Minimize the app window. The first window should go solo again.
383  w2->Minimize();
384  EXPECT_TRUE(p1->UseSoloWindowHeader());
385
386  // Restoring the app window turns off solo headers.
387  w2->Restore();
388  EXPECT_FALSE(p1->UseSoloWindowHeader());
389}
390
391// Panels should not "count" for computing solo window headers, and the panel
392// itself should always have an opaque header.
393TEST_F(FramePainterTest, UseSoloWindowHeaderWithPanel) {
394  // Create a widget and a painter for it.
395  scoped_ptr<Widget> w1(CreateTestWidget());
396  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
397  w1->Show();
398
399  // We only have one window, so it should use a solo header.
400  EXPECT_TRUE(p1->UseSoloWindowHeader());
401
402  // Create a panel and a painter for it.
403  scoped_ptr<Widget> w2(CreatePanelWidget());
404  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
405  w2->Show();
406
407  // Despite two windows, the first window should still be considered "solo"
408  // because panels aren't included in the computation.
409  EXPECT_TRUE(p1->UseSoloWindowHeader());
410
411  // The panel itself is not considered solo.
412  EXPECT_FALSE(p2->UseSoloWindowHeader());
413
414  // Even after closing the first window, the panel is still not considered
415  // solo.
416  w1.reset();
417  EXPECT_FALSE(p2->UseSoloWindowHeader());
418}
419
420// Modal dialogs should not use solo headers.
421TEST_F(FramePainterTest, UseSoloWindowHeaderModal) {
422  // Create a widget and a painter for it.
423  scoped_ptr<Widget> w1(CreateTestWidget());
424  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
425  w1->Show();
426
427  // We only have one window, so it should use a solo header.
428  EXPECT_TRUE(p1->UseSoloWindowHeader());
429
430  // Create a fake modal window.
431  scoped_ptr<Widget> w2(CreateTestWidget());
432  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
433  w2->GetNativeWindow()->SetProperty(aura::client::kModalKey,
434                                     ui::MODAL_TYPE_WINDOW);
435  w2->Show();
436
437  // Despite two windows, the first window should still be considered "solo"
438  // because modal windows aren't included in the computation.
439  EXPECT_TRUE(p1->UseSoloWindowHeader());
440
441  // The modal window itself is not considered solo.
442  EXPECT_FALSE(p2->UseSoloWindowHeader());
443}
444
445// Constrained windows should not use solo headers.
446TEST_F(FramePainterTest, UseSoloWindowHeaderConstrained) {
447  // Create a widget and a painter for it.
448  scoped_ptr<Widget> w1(CreateTestWidget());
449  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
450  w1->Show();
451
452  // We only have one window, so it should use a solo header.
453  EXPECT_TRUE(p1->UseSoloWindowHeader());
454
455  // Create a fake constrained window.
456  scoped_ptr<Widget> w2(CreateTestWidget());
457  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
458  w2->GetNativeWindow()->SetProperty(ash::kConstrainedWindowKey, true);
459  w2->Show();
460
461  // Despite two windows, the first window should still be considered "solo"
462  // because constrained windows aren't included in the computation.
463  EXPECT_TRUE(p1->UseSoloWindowHeader());
464
465  // The constrained window itself is not considered solo.
466  EXPECT_FALSE(p2->UseSoloWindowHeader());
467}
468
469// Non-drawing windows should not affect the solo computation.
470TEST_F(FramePainterTest, UseSoloWindowHeaderNotDrawn) {
471  // Create a widget and a painter for it.
472  scoped_ptr<Widget> widget(CreateTestWidget());
473  scoped_ptr<FramePainter> painter(CreateTestPainter(widget.get()));
474  widget->Show();
475
476  // We only have one window, so it should use a solo header.
477  EXPECT_TRUE(painter->UseSoloWindowHeader());
478
479  // Create non-drawing window similar to DragDropTracker.
480  scoped_ptr<aura::Window> window(new aura::Window(NULL));
481  window->SetType(aura::client::WINDOW_TYPE_NORMAL);
482  window->Init(ui::LAYER_NOT_DRAWN);
483  window->SetDefaultParentByRootWindow(
484      widget->GetNativeWindow()->GetRootWindow(), gfx::Rect());
485  window->Show();
486
487  // Despite two windows, the first window should still be considered "solo"
488  // because non-drawing windows aren't included in the computation.
489  EXPECT_TRUE(painter->UseSoloWindowHeader());
490}
491
492#if defined(OS_WIN)
493// Multiple displays are not supported on Windows Ash. http://crbug.com/165962
494#define MAYBE_UseSoloWindowHeaderMultiDisplay \
495        DISABLED_UseSoloWindowHeaderMultiDisplay
496#else
497#define MAYBE_UseSoloWindowHeaderMultiDisplay \
498        UseSoloWindowHeaderMultiDisplay
499#endif
500
501TEST_F(FramePainterTest, MAYBE_UseSoloWindowHeaderMultiDisplay) {
502  if (!SupportsMultipleDisplays())
503    return;
504
505  UpdateDisplay("1000x600,600x400");
506
507  // Create two widgets and painters for them.
508  scoped_ptr<Widget> w1(CreateTestWidget());
509  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
510  w1->SetBounds(gfx::Rect(0, 0, 100, 100));
511  w1->Show();
512  WindowRepaintChecker checker1(w1->GetNativeWindow());
513  scoped_ptr<Widget> w2(CreateTestWidget());
514  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
515  w2->SetBounds(gfx::Rect(0, 0, 100, 100));
516  w2->Show();
517  WindowRepaintChecker checker2(w2->GetNativeWindow());
518
519  // Now there are two windows in the same display, so we should not use solo
520  // headers.
521  EXPECT_FALSE(p1->UseSoloWindowHeader());
522  EXPECT_FALSE(p2->UseSoloWindowHeader());
523  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
524
525  // Moves the second window to the secondary display.  Both w1/w2 should be
526  // solo.
527  w2->SetBounds(gfx::Rect(1200, 0, 100, 100));
528  EXPECT_TRUE(p1->UseSoloWindowHeader());
529  EXPECT_TRUE(p2->UseSoloWindowHeader());
530  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
531  EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
532
533  // Open two more windows in the primary display.
534  scoped_ptr<Widget> w3(CreateTestWidget());
535  scoped_ptr<FramePainter> p3(CreateTestPainter(w3.get()));
536  w3->SetBounds(gfx::Rect(0, 0, 100, 100));
537  w3->Show();
538  scoped_ptr<Widget> w4(CreateTestWidget());
539  scoped_ptr<FramePainter> p4(CreateTestPainter(w4.get()));
540  w4->SetBounds(gfx::Rect(0, 0, 100, 100));
541  w4->Show();
542
543  // Because the primary display has two windows w1 and w3, they shouldn't be
544  // solo.  w2 should be solo.
545  EXPECT_FALSE(p1->UseSoloWindowHeader());
546  EXPECT_TRUE(p2->UseSoloWindowHeader());
547  EXPECT_FALSE(p3->UseSoloWindowHeader());
548  EXPECT_FALSE(p4->UseSoloWindowHeader());
549  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
550
551  // Moves the w4 to the secondary display.  Now the w2 shouldn't be solo
552  // anymore.
553  w4->SetBounds(gfx::Rect(1200, 0, 100, 100));
554  EXPECT_FALSE(p1->UseSoloWindowHeader());
555  EXPECT_FALSE(p2->UseSoloWindowHeader());
556  EXPECT_FALSE(p3->UseSoloWindowHeader());
557  EXPECT_FALSE(p4->UseSoloWindowHeader());
558  EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
559
560  // Moves the w3 to the secondary display too.  Now w1 should be solo again.
561  w3->SetBounds(gfx::Rect(1200, 0, 100, 100));
562  EXPECT_TRUE(p1->UseSoloWindowHeader());
563  EXPECT_FALSE(p2->UseSoloWindowHeader());
564  EXPECT_FALSE(p3->UseSoloWindowHeader());
565  EXPECT_FALSE(p4->UseSoloWindowHeader());
566  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
567
568  // Change the w3 state to maximize.  Doesn't affect to w1.
569  wm::MaximizeWindow(w3->GetNativeWindow());
570  EXPECT_TRUE(p1->UseSoloWindowHeader());
571  EXPECT_FALSE(p2->UseSoloWindowHeader());
572  EXPECT_FALSE(p3->UseSoloWindowHeader());
573  EXPECT_FALSE(p4->UseSoloWindowHeader());
574
575  // Close the w3 and w4.
576  w3.reset();
577  w4.reset();
578  EXPECT_TRUE(p1->UseSoloWindowHeader());
579  EXPECT_TRUE(p2->UseSoloWindowHeader());
580  EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
581
582  // Move w2 back to the primary display.
583  w2->SetBounds(gfx::Rect(0, 0, 100, 100));
584  EXPECT_FALSE(p1->UseSoloWindowHeader());
585  EXPECT_FALSE(p2->UseSoloWindowHeader());
586  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
587  EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
588
589  // Close w2.
590  w2.reset();
591  EXPECT_TRUE(p1->UseSoloWindowHeader());
592  EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
593}
594
595TEST_F(FramePainterTest, GetHeaderOpacity) {
596  // Create a widget and a painter for it.
597  scoped_ptr<Widget> w1(CreateTestWidget());
598  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
599  w1->Show();
600
601  // Modify the values of the opacity constants so that they each have a
602  // different value.
603  ScopedOpacityConstantModifier opacity_constant_modifier;
604
605  // Solo active window has solo window opacity.
606  EXPECT_EQ(FramePainter::kSoloWindowOpacity,
607            p1->GetHeaderOpacity(FramePainter::ACTIVE,
608                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
609                                 0));
610
611  // Create a second widget and painter.
612  scoped_ptr<Widget> w2(CreateTestWidget());
613  scoped_ptr<FramePainter> p2(CreateTestPainter(w2.get()));
614  w2->Show();
615
616  // Active window has active window opacity.
617  EXPECT_EQ(FramePainter::kActiveWindowOpacity,
618            p2->GetHeaderOpacity(FramePainter::ACTIVE,
619                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
620                                 0));
621
622  // Inactive window has inactive window opacity.
623  EXPECT_EQ(FramePainter::kInactiveWindowOpacity,
624            p2->GetHeaderOpacity(FramePainter::INACTIVE,
625                                 IDR_AURA_WINDOW_HEADER_BASE_INACTIVE,
626                                 0));
627
628  // Regular maximized windows are fully opaque.
629  ash::wm::MaximizeWindow(w1->GetNativeWindow());
630  EXPECT_EQ(255,
631            p1->GetHeaderOpacity(FramePainter::ACTIVE,
632                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
633                                 0));
634}
635
636// Test that the minimal header style is used in the proper situations.
637TEST_F(FramePainterTest, MinimalHeaderStyle) {
638  // Create a widget and a painter for it.
639  scoped_ptr<Widget> w(CreateTestWidget());
640  scoped_ptr<FramePainter> p(CreateTestPainter(w.get()));
641  w->Show();
642
643  // Regular non-maximized windows should not use the minimal header style.
644  EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
645
646  // Regular maximized windows should use the minimal header style.
647  w->Maximize();
648  EXPECT_TRUE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
649
650  // Test cases where the maximized window should not use the minimal header
651  // style.
652  EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_YES));
653
654  SetTrackedByWorkspace(w->GetNativeWindow(), false);
655  EXPECT_FALSE(p->ShouldUseMinimalHeaderStyle(FramePainter::THEMED_NO));
656  SetTrackedByWorkspace(w->GetNativeWindow(), true);
657}
658
659// Ensure the title text is vertically aligned with the window icon.
660TEST_F(FramePainterTest, TitleIconAlignment) {
661  scoped_ptr<Widget> w(CreateTestWidget());
662  FramePainter p;
663  ImageButton size(NULL);
664  ImageButton close(NULL);
665  views::View window_icon;
666  window_icon.SetBounds(0, 0, 16, 16);
667  p.Init(w.get(),
668         &window_icon,
669         &size,
670         &close,
671         FramePainter::SIZE_BUTTON_MAXIMIZES);
672  w->SetBounds(gfx::Rect(0, 0, 500, 500));
673  w->Show();
674
675  // Title and icon are aligned when shorter_header is false.
676  p.LayoutHeader(w->non_client_view()->frame_view(), false);
677  gfx::Font default_font;
678  gfx::Rect large_header_title_bounds = p.GetTitleBounds(default_font);
679  EXPECT_EQ(window_icon.bounds().CenterPoint().y(),
680            large_header_title_bounds.CenterPoint().y());
681
682  // Title and icon are aligned when shorter_header is true.
683  p.LayoutHeader(w->non_client_view()->frame_view(), true);
684  gfx::Rect short_header_title_bounds = p.GetTitleBounds(default_font);
685  EXPECT_EQ(window_icon.bounds().CenterPoint().y(),
686            short_header_title_bounds.CenterPoint().y());
687}
688
689TEST_F(FramePainterTest, ChildWindowVisibility) {
690  scoped_ptr<Widget> w1(CreateTestWidget());
691  scoped_ptr<FramePainter> p1(CreateTestPainter(w1.get()));
692  w1->Show();
693
694  // Solo active window has solo window opacity.
695  EXPECT_EQ(FramePainter::kSoloWindowOpacity,
696            p1->GetHeaderOpacity(FramePainter::ACTIVE,
697                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
698                                 0));
699
700  // Create a child window which doesn't affect the solo header.
701  scoped_ptr<Widget> w2(new Widget);
702  Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
703  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
704  params.parent = w1->GetNativeView();
705  w2->Init(params);
706  w2->Show();
707
708  // Still has solo header if child window is added.
709  EXPECT_EQ(FramePainter::kSoloWindowOpacity,
710            p1->GetHeaderOpacity(FramePainter::ACTIVE,
711                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
712                                 0));
713
714  // Change the visibility of w2 and verifies w1 still has solo header.
715  w2->Hide();
716  EXPECT_EQ(FramePainter::kSoloWindowOpacity,
717            p1->GetHeaderOpacity(FramePainter::ACTIVE,
718                                 IDR_AURA_WINDOW_HEADER_BASE_ACTIVE,
719                                 0));
720}
721
722TEST_F(FramePainterTest, NoCrashShutdownWithAlwaysOnTopWindow) {
723  // Create normal window and an always-on-top window, and leave it as is
724  // and finish the test, then verify it doesn't cause a crash. See
725  // crbug.com/273310.  Note that those widgets will be deleted at
726  // RootWindowController::CloseChildWindows(), so this code is memory-safe.
727  Widget* w1 = new Widget;
728  Widget::InitParams params1;
729  params1.context = CurrentContext();
730  w1->Init(params1);
731  FramePainterOwner* o1 = new FramePainterOwner(w1);
732  FramePainter* p1 = o1->frame_painter();
733  w1->SetBounds(gfx::Rect(0, 0, 100, 100));
734  w1->Show();
735  EXPECT_TRUE(p1->UseSoloWindowHeader());
736
737  Widget* w2 = new Widget;
738  Widget::InitParams params2;
739  params2.context = CurrentContext();
740  params2.keep_on_top = true;
741  w2->Init(params2);
742  FramePainterOwner* o2 = new FramePainterOwner(w2);
743  FramePainter* p2 = o2->frame_painter();
744  w2->Show();
745  EXPECT_FALSE(p1->UseSoloWindowHeader());
746  EXPECT_FALSE(p2->UseSoloWindowHeader());
747
748  // Exit with no resource release. They'll be released at shutdown.
749}
750
751}  // namespace ash
752