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.
5 #include "chrome/browser/ui/panels/base_panel_browser_test.h"
8 #include "base/command_line.h"
9 #include "base/memory/weak_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "chrome/browser/chrome_notification_types.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/ui/browser.h"
16 #include "chrome/browser/ui/browser_window.h"
17 #include "chrome/browser/ui/panels/detached_panel_collection.h"
18 #include "chrome/browser/ui/panels/native_panel.h"
19 #include "chrome/browser/ui/panels/panel_collection.h"
20 #include "chrome/browser/ui/panels/panel_mouse_watcher.h"
21 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
22 #include "chrome/browser/ui/panels/test_panel_active_state_observer.h"
23 #include "chrome/browser/ui/panels/test_panel_mouse_watcher.h"
24 #include "chrome/common/chrome_paths.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/test/base/interactive_test_utils.h"
27 #include "chrome/test/base/ui_test_utils.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/common/url_constants.h"
30 #include "content/public/test/web_contents_tester.h"
31 #include "extensions/browser/extension_prefs.h"
32 #include "extensions/browser/extension_system.h"
33 #include "extensions/browser/install_flag.h"
34 #include "extensions/common/manifest_constants.h"
35 #include "sync/api/string_ordinal.h"
38 #include "ui/base/x/x11_util.h"
41 #if defined(OS_MACOSX)
42 #include "base/mac/scoped_nsautorelease_pool.h"
43 #include "chrome/browser/ui/cocoa/run_loop_testing.h"
46 using content::WebContentsTester
;
47 using extensions::Extension
;
51 const gfx::Rect kTestingPrimaryDisplayArea
= gfx::Rect(0, 0, 800, 600);
52 const gfx::Rect kTestingPrimaryWorkArea
= gfx::Rect(0, 0, 800, 580);
54 struct MockDesktopBar
{
55 bool auto_hiding_enabled
;
56 DisplaySettingsProvider::DesktopBarVisibility visibility
;
60 class MockDisplaySettingsProviderImpl
:
61 public BasePanelBrowserTest::MockDisplaySettingsProvider
{
63 MockDisplaySettingsProviderImpl();
64 ~MockDisplaySettingsProviderImpl() override
{}
66 // Overridden from DisplaySettingsProvider:
67 gfx::Rect
GetPrimaryDisplayArea() const override
;
68 gfx::Rect
GetPrimaryWorkArea() const override
;
69 gfx::Rect
GetDisplayAreaMatching(const gfx::Rect
& bounds
) const override
;
70 gfx::Rect
GetWorkAreaMatching(const gfx::Rect
& bounds
) const override
;
71 bool IsAutoHidingDesktopBarEnabled(DesktopBarAlignment alignment
) override
;
72 int GetDesktopBarThickness(DesktopBarAlignment alignment
) const override
;
73 DesktopBarVisibility
GetDesktopBarVisibility(
74 DesktopBarAlignment alignment
) const override
;
75 bool IsFullScreen() override
;
77 // Overridden from MockDisplaySettingsProvider:
78 void SetPrimaryDisplay(const gfx::Rect
& display_area
,
79 const gfx::Rect
& work_area
) override
;
80 void SetSecondaryDisplay(const gfx::Rect
& display_area
,
81 const gfx::Rect
& work_area
) override
;
82 void EnableAutoHidingDesktopBar(DesktopBarAlignment alignment
,
84 int thickness
) override
;
85 void SetDesktopBarVisibility(DesktopBarAlignment alignment
,
86 DesktopBarVisibility visibility
) override
;
87 void SetDesktopBarThickness(DesktopBarAlignment alignment
,
88 int thickness
) override
;
89 void EnableFullScreenMode(bool enabled
) override
;
92 gfx::Rect primary_display_area_
;
93 gfx::Rect primary_work_area_
;
94 gfx::Rect secondary_display_area_
;
95 gfx::Rect secondary_work_area_
;
96 MockDesktopBar mock_desktop_bars
[3];
97 bool full_screen_enabled_
;
99 DISALLOW_COPY_AND_ASSIGN(MockDisplaySettingsProviderImpl
);
103 MockDisplaySettingsProviderImpl::MockDisplaySettingsProviderImpl()
104 : full_screen_enabled_(false) {
105 memset(mock_desktop_bars
, 0, sizeof(mock_desktop_bars
));
108 gfx::Rect
MockDisplaySettingsProviderImpl::GetPrimaryDisplayArea() const {
109 return primary_display_area_
;
112 gfx::Rect
MockDisplaySettingsProviderImpl::GetPrimaryWorkArea() const {
113 return primary_work_area_
;
116 gfx::Rect
MockDisplaySettingsProviderImpl::GetDisplayAreaMatching(
117 const gfx::Rect
& bounds
) const {
118 if (secondary_display_area_
.IsEmpty())
119 return primary_display_area_
;
121 gfx::Rect primary_intersection
=
122 gfx::IntersectRects(bounds
, primary_display_area_
);
123 int primary_intersection_size
=
124 primary_intersection
.width() * primary_intersection
.height();
126 gfx::Rect secondary_intersection
=
127 gfx::IntersectRects(bounds
, secondary_display_area_
);
128 int secondary_intersection_size
=
129 secondary_intersection
.width() * secondary_intersection
.height();
131 return primary_intersection_size
>= secondary_intersection_size
?
132 primary_display_area_
: secondary_display_area_
;
135 gfx::Rect
MockDisplaySettingsProviderImpl::GetWorkAreaMatching(
136 const gfx::Rect
& bounds
) const {
137 if (secondary_work_area_
.IsEmpty())
138 return primary_work_area_
;
140 gfx::Rect primary_intersection
=
141 gfx::IntersectRects(bounds
, primary_work_area_
);
142 int primary_intersection_size
=
143 primary_intersection
.width() * primary_intersection
.height();
145 gfx::Rect secondary_intersection
=
146 gfx::IntersectRects(bounds
, secondary_work_area_
);
147 int secondary_intersection_size
=
148 secondary_intersection
.width() * secondary_intersection
.height();
150 return primary_intersection_size
>= secondary_intersection_size
?
151 primary_work_area_
: secondary_work_area_
;
154 bool MockDisplaySettingsProviderImpl::IsAutoHidingDesktopBarEnabled(
155 DesktopBarAlignment alignment
) {
156 return mock_desktop_bars
[static_cast<int>(alignment
)].auto_hiding_enabled
;
159 int MockDisplaySettingsProviderImpl::GetDesktopBarThickness(
160 DesktopBarAlignment alignment
) const {
161 return mock_desktop_bars
[static_cast<int>(alignment
)].thickness
;
164 DisplaySettingsProvider::DesktopBarVisibility
165 MockDisplaySettingsProviderImpl::GetDesktopBarVisibility(
166 DesktopBarAlignment alignment
) const {
167 return mock_desktop_bars
[static_cast<int>(alignment
)].visibility
;
170 bool MockDisplaySettingsProviderImpl::IsFullScreen() {
171 return full_screen_enabled_
;
174 void MockDisplaySettingsProviderImpl::EnableAutoHidingDesktopBar(
175 DesktopBarAlignment alignment
, bool enabled
, int thickness
) {
176 MockDesktopBar
* bar
= &(mock_desktop_bars
[static_cast<int>(alignment
)]);
177 bar
->auto_hiding_enabled
= enabled
;
178 bar
->thickness
= thickness
;
181 void MockDisplaySettingsProviderImpl::SetPrimaryDisplay(
182 const gfx::Rect
& display_area
, const gfx::Rect
& work_area
) {
183 DCHECK(display_area
.Contains(work_area
));
184 primary_display_area_
= display_area
;
185 primary_work_area_
= work_area
;
186 OnDisplaySettingsChanged();
189 void MockDisplaySettingsProviderImpl::SetSecondaryDisplay(
190 const gfx::Rect
& display_area
, const gfx::Rect
& work_area
) {
191 DCHECK(display_area
.Contains(work_area
));
192 secondary_display_area_
= display_area
;
193 secondary_work_area_
= work_area
;
194 OnDisplaySettingsChanged();
197 void MockDisplaySettingsProviderImpl::SetDesktopBarVisibility(
198 DesktopBarAlignment alignment
, DesktopBarVisibility visibility
) {
199 MockDesktopBar
* bar
= &(mock_desktop_bars
[static_cast<int>(alignment
)]);
200 if (!bar
->auto_hiding_enabled
)
202 if (visibility
== bar
->visibility
)
204 bar
->visibility
= visibility
;
207 desktop_bar_observers(),
208 OnAutoHidingDesktopBarVisibilityChanged(alignment
, visibility
));
211 void MockDisplaySettingsProviderImpl::SetDesktopBarThickness(
212 DesktopBarAlignment alignment
, int thickness
) {
213 MockDesktopBar
* bar
= &(mock_desktop_bars
[static_cast<int>(alignment
)]);
214 if (!bar
->auto_hiding_enabled
)
216 if (thickness
== bar
->thickness
)
218 bar
->thickness
= thickness
;
221 desktop_bar_observers(),
222 OnAutoHidingDesktopBarThicknessChanged(alignment
, thickness
));
225 void MockDisplaySettingsProviderImpl::EnableFullScreenMode(bool enabled
) {
226 full_screen_enabled_
= enabled
;
227 CheckFullScreenMode(PERFORM_FULLSCREEN_CHECK
);
232 const base::FilePath::CharType
* BasePanelBrowserTest::kTestDir
=
233 FILE_PATH_LITERAL("panels");
235 BasePanelBrowserTest::BasePanelBrowserTest()
236 : InProcessBrowserTest(),
237 mock_display_settings_enabled_(true) {
240 BasePanelBrowserTest::~BasePanelBrowserTest() {
243 void BasePanelBrowserTest::SetUpCommandLine(base::CommandLine
* command_line
) {
244 command_line
->AppendSwitch(switches::kEnablePanels
);
247 void BasePanelBrowserTest::SetUpOnMainThread() {
248 InProcessBrowserTest::SetUpOnMainThread();
250 // Setup the work area and desktop bar so that we have consistent testing
251 // environment for all panel related tests.
252 if (mock_display_settings_enabled_
) {
253 mock_display_settings_provider_
= new MockDisplaySettingsProviderImpl();
254 mock_display_settings_provider_
->SetPrimaryDisplay(
255 kTestingPrimaryDisplayArea
, kTestingPrimaryWorkArea
);
256 PanelManager::SetDisplaySettingsProviderForTesting(
257 mock_display_settings_provider_
);
260 PanelManager
* panel_manager
= PanelManager::GetInstance();
261 panel_manager
->enable_auto_sizing(false);
263 PanelManager::shorten_time_intervals_for_testing();
265 // Simulate the mouse movement so that tests are not affected by actual mouse
267 PanelMouseWatcher
* mouse_watcher
= new TestPanelMouseWatcher();
268 panel_manager
->SetMouseWatcherForTesting(mouse_watcher
);
270 // This is needed so the subsequently created panels can be activated.
271 // On a Mac, it transforms background-only test process into foreground one.
272 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
275 void BasePanelBrowserTest::WaitForPanelActiveState(
276 Panel
* panel
, ActiveState expected_state
) {
277 DCHECK(expected_state
== SHOW_AS_ACTIVE
||
278 expected_state
== SHOW_AS_INACTIVE
);
280 #if defined(OS_MACOSX)
281 scoped_ptr
<NativePanelTesting
> panel_testing(
282 CreateNativePanelTesting(panel
));
283 ASSERT_TRUE(panel_testing
->EnsureApplicationRunOnForeground()) <<
284 "Failed to bring application to foreground. Bail out.";
287 PanelActiveStateObserver
signal(panel
, expected_state
== SHOW_AS_ACTIVE
);
291 void BasePanelBrowserTest::WaitForBoundsAnimationFinished(Panel
* panel
) {
292 scoped_ptr
<NativePanelTesting
> panel_testing(
293 CreateNativePanelTesting(panel
));
294 // Sometimes there are several animations in sequence due to content
295 // auto resizing. Wait for all animations to finish.
296 while (panel_testing
->IsAnimatingBounds()) {
297 content::WindowedNotificationObserver
signal(
298 chrome::NOTIFICATION_PANEL_BOUNDS_ANIMATIONS_FINISHED
,
299 content::Source
<Panel
>(panel
));
300 if (!panel_testing
->IsAnimatingBounds())
306 BasePanelBrowserTest::CreatePanelParams::CreatePanelParams(
307 const std::string
& name
,
308 const gfx::Rect
& bounds
,
309 ActiveState show_flag
)
312 show_flag(show_flag
),
313 wait_for_fully_created(true),
314 expected_active_state(show_flag
),
315 create_mode(PanelManager::CREATE_AS_DOCKED
),
319 Panel
* BasePanelBrowserTest::CreatePanelWithParams(
320 const CreatePanelParams
& params
) {
321 #if defined(OS_MACOSX)
322 // Opening panels on a Mac causes NSWindowController of the Panel window
323 // to be autoreleased. We need a pool drained after it's done so the test
324 // can close correctly. The NSWindowController of the Panel window controls
325 // lifetime of the Panel object so we want to release it as soon as
326 // possible. In real Chrome, this is done by message pump.
327 // On non-Mac platform, this is an empty class.
328 base::mac::ScopedNSAutoreleasePool autorelease_pool
;
331 content::WindowedNotificationObserver
observer(
332 content::NOTIFICATION_LOAD_STOP
,
333 content::NotificationService::AllSources());
335 PanelManager
* manager
= PanelManager::GetInstance();
336 Panel
* panel
= manager
->CreatePanel(
338 params
.profile
? params
.profile
: browser()->profile(),
343 if (!params
.url
.is_empty())
346 if (!manager
->auto_sizing_enabled() ||
347 params
.bounds
.width() || params
.bounds
.height()) {
348 EXPECT_FALSE(panel
->auto_resizable());
350 EXPECT_TRUE(panel
->auto_resizable());
353 if (params
.show_flag
== SHOW_AS_ACTIVE
) {
356 panel
->ShowInactive();
359 if (params
.wait_for_fully_created
) {
360 base::MessageLoopForUI::current()->RunUntilIdle();
362 #if defined(OS_LINUX) && defined(USE_X11)
363 // On bots, we might have a simple window manager which always activates new
364 // windows, and can't always deactivate them. Re-activate the main tabbed
365 // browser to "deactivate" the newly created panel.
366 if (params
.expected_active_state
== SHOW_AS_INACTIVE
&&
367 ui::GuessWindowManager() == ui::WM_ICE_WM
) {
368 // Wait for new panel to become active before deactivating to ensure
369 // the activated notification is consumed before we wait for the panel
370 // to become inactive.
371 WaitForPanelActiveState(panel
, SHOW_AS_ACTIVE
);
372 browser()->window()->Activate();
375 // More waiting, because gaining or losing focus may require inter-process
376 // asynchronous communication, and it is not enough to just run the local
377 // message loop to make sure this activity has completed.
378 WaitForPanelActiveState(panel
, params
.expected_active_state
);
380 // Wait for the bounds animations on creation to finish.
381 WaitForBoundsAnimationFinished(panel
);
387 Panel
* BasePanelBrowserTest::CreatePanelWithBounds(
388 const std::string
& panel_name
, const gfx::Rect
& bounds
) {
389 CreatePanelParams
params(panel_name
, bounds
, SHOW_AS_ACTIVE
);
390 return CreatePanelWithParams(params
);
393 Panel
* BasePanelBrowserTest::CreatePanel(const std::string
& panel_name
) {
394 CreatePanelParams
params(panel_name
, gfx::Rect(), SHOW_AS_ACTIVE
);
395 return CreatePanelWithParams(params
);
398 Panel
* BasePanelBrowserTest::CreateDockedPanel(const std::string
& name
,
399 const gfx::Rect
& bounds
) {
400 Panel
* panel
= CreatePanelWithBounds(name
, bounds
);
401 EXPECT_EQ(PanelCollection::DOCKED
, panel
->collection()->type());
405 Panel
* BasePanelBrowserTest::CreateDetachedPanel(const std::string
& name
,
406 const gfx::Rect
& bounds
) {
407 Panel
* panel
= CreatePanelWithBounds(name
, bounds
);
408 PanelManager
* panel_manager
= panel
->manager();
409 panel_manager
->MovePanelToCollection(panel
,
410 panel_manager
->detached_collection(),
411 PanelCollection::DEFAULT_POSITION
);
412 EXPECT_EQ(PanelCollection::DETACHED
, panel
->collection()->type());
413 // The panel is first created as docked panel, which ignores the specified
414 // origin in |bounds|. We need to reposition the panel after it becomes
416 panel
->SetPanelBounds(bounds
);
417 WaitForBoundsAnimationFinished(panel
);
421 Panel
* BasePanelBrowserTest::CreateStackedPanel(const std::string
& name
,
422 const gfx::Rect
& bounds
,
423 StackedPanelCollection
* stack
) {
424 Panel
* panel
= CreateDetachedPanel(name
, bounds
);
425 panel
->manager()->MovePanelToCollection(
428 static_cast<PanelCollection::PositioningMask
>(
429 PanelCollection::DEFAULT_POSITION
|
430 PanelCollection::COLLAPSE_TO_FIT
));
431 EXPECT_EQ(PanelCollection::STACKED
, panel
->collection()->type());
432 WaitForBoundsAnimationFinished(panel
);
436 Panel
* BasePanelBrowserTest::CreateInactivePanel(const std::string
& name
) {
437 // Create an active panel first, instead of inactive panel. This is because
438 // certain window managers on Linux, like icewm, will always activate the
440 Panel
* panel
= CreatePanel(name
);
442 DeactivatePanel(panel
);
443 WaitForPanelActiveState(panel
, SHOW_AS_INACTIVE
);
448 Panel
* BasePanelBrowserTest::CreateInactiveDockedPanel(
449 const std::string
& name
, const gfx::Rect
& bounds
) {
450 // Create an active panel first, instead of inactive panel. This is because
451 // certain window managers on Linux, like icewm, will always activate the
453 Panel
* panel
= CreateDockedPanel(name
, bounds
);
455 DeactivatePanel(panel
);
456 WaitForPanelActiveState(panel
, SHOW_AS_INACTIVE
);
461 Panel
* BasePanelBrowserTest::CreateInactiveDetachedPanel(
462 const std::string
& name
, const gfx::Rect
& bounds
) {
463 // Create an active panel first, instead of inactive panel. This is because
464 // certain window managers on Linux, like icewm, will always activate the
466 Panel
* panel
= CreateDetachedPanel(name
, bounds
);
468 DeactivatePanel(panel
);
469 WaitForPanelActiveState(panel
, SHOW_AS_INACTIVE
);
474 void BasePanelBrowserTest::ActivatePanel(Panel
* panel
) {
475 // For certain window managers on Linux, the window activation/deactivation
476 // signals might not be sent. To work around this, we explicitly deactivate
477 // all other panels first.
478 #if defined(OS_LINUX)
479 std::vector
<Panel
*> panels
= PanelManager::GetInstance()->panels();
480 for (std::vector
<Panel
*>::const_iterator iter
= panels
.begin();
481 iter
!= panels
.end(); ++iter
) {
482 Panel
* current_panel
= *iter
;
483 if (panel
!= current_panel
)
484 current_panel
->Deactivate();
491 void BasePanelBrowserTest::DeactivatePanel(Panel
* panel
) {
492 #if defined(OS_LINUX)
493 // For certain window managers on Linux, like icewm, panel activation and
494 // deactivation notification might not get tiggered when non-panel window is
495 // activated or deactivated. So we deactivate the panel directly.
498 // Make the panel lose focus by activating the browser window. This is
500 // 1) On Windows, deactivating the panel window might cause the application
501 // to lose the foreground status. When this occurs, trying to activate
502 // the panel window again will not be allowed by the system.
503 // 2) On MacOS, deactivating a window is not supported by Cocoa.
504 browser()->window()->Activate();
509 NativePanelTesting
* BasePanelBrowserTest::CreateNativePanelTesting(
511 return panel
->native_panel()->CreateNativePanelTesting();
514 scoped_refptr
<Extension
> BasePanelBrowserTest::CreateExtension(
515 const base::FilePath::StringType
& path
,
516 extensions::Manifest::Location location
,
517 const base::DictionaryValue
& extra_value
) {
518 extensions::ExtensionPrefs
* extension_prefs
=
519 extensions::ExtensionPrefs::Get(browser()->profile());
520 base::FilePath full_path
= extension_prefs
->install_directory().Append(path
);
522 scoped_ptr
<base::DictionaryValue
> input_value(extra_value
.DeepCopy());
523 input_value
->SetString(extensions::manifest_keys::kVersion
, "1.0.0.0");
524 input_value
->SetString(extensions::manifest_keys::kName
, "Sample Extension");
527 scoped_refptr
<Extension
> extension
= Extension::Create(
528 full_path
, location
, *input_value
, Extension::NO_FLAGS
, &error
);
529 EXPECT_TRUE(extension
.get());
530 EXPECT_STREQ("", error
.c_str());
531 extensions::ExtensionSystem::Get(
532 browser()->profile())->extension_service()->OnExtensionInstalled(
534 syncer::StringOrdinal(),
535 extensions::kInstallFlagInstallImmediately
);
539 void BasePanelBrowserTest::CloseWindowAndWait(Panel
* panel
) {
540 // Closing a panel may involve several async tasks. Need to use
541 // message pump and wait for the notification.
542 PanelManager
* manager
= PanelManager::GetInstance();
543 int panel_count
= manager
->num_panels();
544 content::WindowedNotificationObserver
signal(
545 chrome::NOTIFICATION_PANEL_CLOSED
,
546 content::Source
<Panel
>(panel
));
549 // Now we have one less panel.
550 EXPECT_EQ(panel_count
- 1, manager
->num_panels());
552 #if defined(OS_MACOSX)
553 // Mac window controllers may be autoreleased, and in the non-test
554 // environment, may actually depend on the autorelease pool being recycled
555 // with the run loop in order to perform important work. Replicate this in
556 // the test environment.
557 AutoreleasePool()->Recycle();
559 // Make sure that everything has a chance to run.
560 chrome::testing::NSRunLoopRunAllPending();
564 void BasePanelBrowserTest::MoveMouseAndWaitForExpansionStateChange(
566 const gfx::Point
& position
) {
567 content::WindowedNotificationObserver
signal(
568 chrome::NOTIFICATION_PANEL_CHANGED_EXPANSION_STATE
,
569 content::Source
<Panel
>(panel
));
574 void BasePanelBrowserTest::MoveMouse(const gfx::Point
& position
) {
575 PanelManager::GetInstance()->mouse_watcher()->NotifyMouseMovement(position
);
578 std::string
BasePanelBrowserTest::MakePanelName(int index
) {
579 std::string
panel_name("Panel");
580 return panel_name
+ base::IntToString(index
);
583 bool BasePanelBrowserTest::WmSupportWindowActivation() {