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 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
7 #import "base/mac/mac_util.h"
8 #include "base/mac/sdk_forward_declarations.h"
9 #include "base/run_loop.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/devtools/devtools_window_testing.h"
14 #include "chrome/browser/infobars/infobar_service.h"
15 #include "chrome/browser/infobars/simple_alert_infobar_delegate.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/profiles/profile_manager.h"
18 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_commands.h"
21 #include "chrome/browser/ui/browser_list.h"
22 #include "chrome/browser/ui/browser_window.h"
23 #include "chrome/browser/ui/cocoa/browser_window_cocoa.h"
24 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
25 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
26 #import "chrome/browser/ui/cocoa/history_overlay_controller.h"
27 #import "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h"
28 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
29 #import "chrome/browser/ui/cocoa/infobars/infobar_controller.h"
30 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
31 #import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h"
32 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
33 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
34 #include "chrome/browser/ui/extensions/application_launch.h"
35 #include "chrome/browser/ui/find_bar/find_bar.h"
36 #include "chrome/browser/ui/find_bar/find_bar_controller.h"
37 #include "chrome/browser/ui/tabs/tab_strip_model.h"
38 #include "chrome/test/base/in_process_browser_test.h"
39 #include "chrome/test/base/testing_profile.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/test/test_utils.h"
42 #import "testing/gtest_mac.h"
43 #import "ui/base/cocoa/nsview_additions.h"
44 #include "ui/gfx/animation/slide_animation.h"
48 void CreateProfileCallback(const base::Closure& quit_closure,
50 Profile::CreateStatus status) {
52 EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status);
53 EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status);
54 // This will be called multiple times. Wait until the profile is initialized
55 // fully to quit the loop.
56 if (status == Profile::CREATE_STATUS_INITIALIZED)
65 VIEW_ID_DOWNLOAD_SHELF,
66 VIEW_ID_TAB_CONTENT_AREA,
67 VIEW_ID_FULLSCREEN_FLOATING_BAR,
73 @interface InfoBarContainerController(TestingAPI)
74 - (BOOL)isTopInfoBarAnimationRunning;
77 @implementation InfoBarContainerController(TestingAPI)
78 - (BOOL)isTopInfoBarAnimationRunning {
79 InfoBarController* infoBarController = [infobarControllers_ objectAtIndex:0];
80 if (infoBarController) {
81 const gfx::SlideAnimation& infobarAnimation =
82 static_cast<const InfoBarCocoa*>(
83 infoBarController.infobar)->animation();
84 return infobarAnimation.is_animating();
90 class BrowserWindowControllerTest : public InProcessBrowserTest {
92 BrowserWindowControllerTest() : InProcessBrowserTest() {
95 virtual void SetUpOnMainThread() OVERRIDE {
96 [[controller() bookmarkBarController] setStateAnimationsEnabled:NO];
97 [[controller() bookmarkBarController] setInnerContentAnimationsEnabled:NO];
100 BrowserWindowController* controller() const {
101 return [BrowserWindowController browserWindowControllerForWindow:
102 browser()->window()->GetNativeWindow()];
105 static void ShowInfoBar(Browser* browser) {
106 SimpleAlertInfoBarDelegate::Create(
107 InfoBarService::FromWebContents(
108 browser->tab_strip_model()->GetActiveWebContents()),
109 0, base::string16(), false);
112 NSView* GetViewWithID(ViewID view_id) const {
114 case VIEW_ID_FULLSCREEN_FLOATING_BAR:
115 return [controller() floatingBarBackingView];
116 case VIEW_ID_TOOLBAR:
117 return [[controller() toolbarController] view];
118 case VIEW_ID_BOOKMARK_BAR:
119 return [[controller() bookmarkBarController] view];
120 case VIEW_ID_INFO_BAR:
121 return [[controller() infoBarContainerController] view];
122 case VIEW_ID_FIND_BAR:
123 return [[controller() findBarCocoaController] view];
124 case VIEW_ID_DOWNLOAD_SHELF:
125 return [[controller() downloadShelf] view];
126 case VIEW_ID_TAB_CONTENT_AREA:
127 return [controller() tabContentArea];
134 void VerifyZOrder(const std::vector<ViewID>& view_list) const {
135 std::vector<NSView*> visible_views;
136 for (size_t i = 0; i < view_list.size(); ++i) {
137 NSView* view = GetViewWithID(view_list[i]);
138 if ([view superview])
139 visible_views.push_back(view);
142 for (size_t i = 0; i < visible_views.size() - 1; ++i) {
143 NSView* bottom_view = visible_views[i];
144 NSView* top_view = visible_views[i + 1];
146 EXPECT_NSEQ([bottom_view superview], [top_view superview]);
147 EXPECT_TRUE([bottom_view cr_isBelowView:top_view]);
150 // Views not in |view_list| must either be nil or not parented.
151 for (size_t i = 0; i < VIEW_ID_COUNT; ++i) {
152 if (std::find(view_list.begin(), view_list.end(), i) == view_list.end()) {
153 NSView* view = GetViewWithID(static_cast<ViewID>(i));
154 EXPECT_TRUE(!view || ![view superview]);
159 CGFloat GetViewHeight(ViewID viewID) const {
160 CGFloat height = NSHeight([GetViewWithID(viewID) frame]);
161 if (viewID == VIEW_ID_INFO_BAR) {
162 height -= [[controller() infoBarContainerController]
163 overlappingTipHeight];
168 static void CheckTopInfoBarAnimation(
169 InfoBarContainerController* info_bar_container_controller,
170 const base::Closure& quit_task) {
171 if (![info_bar_container_controller isTopInfoBarAnimationRunning])
175 static void CheckBookmarkBarAnimation(
176 BookmarkBarController* bookmark_bar_controller,
177 const base::Closure& quit_task) {
178 if (![bookmark_bar_controller isAnimationRunning])
182 void WaitForTopInfoBarAnimationToFinish() {
183 scoped_refptr<content::MessageLoopRunner> runner =
184 new content::MessageLoopRunner;
186 base::Timer timer(false, true);
189 base::TimeDelta::FromMilliseconds(15),
190 base::Bind(&CheckTopInfoBarAnimation,
191 [controller() infoBarContainerController],
192 runner->QuitClosure()));
196 void WaitForBookmarkBarAnimationToFinish() {
197 scoped_refptr<content::MessageLoopRunner> runner =
198 new content::MessageLoopRunner;
200 base::Timer timer(false, true);
203 base::TimeDelta::FromMilliseconds(15),
204 base::Bind(&CheckBookmarkBarAnimation,
205 [controller() bookmarkBarController],
206 runner->QuitClosure()));
210 NSInteger GetExpectedTopInfoBarTipHeight() {
211 InfoBarContainerController* info_bar_container_controller =
212 [controller() infoBarContainerController];
213 CGFloat overlapping_tip_height =
214 [info_bar_container_controller overlappingTipHeight];
215 LocationBarViewMac* location_bar_view = [controller() locationBarBridge];
216 NSPoint icon_bottom = location_bar_view->GetPageInfoBubblePoint();
218 NSPoint info_bar_top = NSMakePoint(0,
219 NSHeight([info_bar_container_controller view].frame) -
220 overlapping_tip_height);
221 info_bar_top = [[info_bar_container_controller view]
222 convertPoint:info_bar_top toView:nil];
223 return icon_bottom.y - info_bar_top.y;
226 // The traffic lights should always be in front of the content view and the
227 // tab strip view. Since the traffic lights change across OSX versions, this
228 // test verifies that the contentView is in the back, and if the tab strip
229 // view is a sibling, it is directly in front of the content view.
230 void VerifyTrafficLightZOrder() const {
231 NSView* contentView = [[controller() window] contentView];
232 NSView* rootView = [contentView superview];
233 EXPECT_EQ(contentView, [[rootView subviews] objectAtIndex:0]);
235 NSView* tabStripView = [controller() tabStripView];
236 if ([[rootView subviews] containsObject:tabStripView])
237 EXPECT_EQ(tabStripView, [[rootView subviews] objectAtIndex:1]);
241 DISALLOW_COPY_AND_ASSIGN(BrowserWindowControllerTest);
244 // Tests that adding the first profile moves the Lion fullscreen button over
246 // DISABLED_ because it regularly times out: http://crbug.com/159002.
247 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
248 DISABLED_ProfileAvatarFullscreenButton) {
249 if (base::mac::IsOSSnowLeopard())
252 // Initialize the locals.
253 ProfileManager* profile_manager = g_browser_process->profile_manager();
254 ASSERT_TRUE(profile_manager);
256 NSWindow* window = browser()->window()->GetNativeWindow();
259 // With only one profile, the fullscreen button should be visible, but the
260 // avatar button should not.
261 EXPECT_EQ(1u, profile_manager->GetNumberOfProfiles());
263 NSButton* fullscreen_button =
264 [window standardWindowButton:NSWindowFullScreenButton];
265 EXPECT_TRUE(fullscreen_button);
266 EXPECT_FALSE([fullscreen_button isHidden]);
268 AvatarBaseController* avatar_controller =
269 [controller() avatarButtonController];
270 NSView* avatar = [avatar_controller view];
272 EXPECT_TRUE([avatar isHidden]);
274 // Create a profile asynchronously and run the loop until its creation
276 base::RunLoop run_loop;
278 ProfileManager::CreateCallback create_callback =
279 base::Bind(&CreateProfileCallback, run_loop.QuitClosure());
280 profile_manager->CreateProfileAsync(
281 profile_manager->user_data_dir().Append("test"),
283 base::ASCIIToUTF16("avatar_test"),
289 // There should now be two profiles, and the avatar button and fullscreen
290 // button are both visible.
291 EXPECT_EQ(2u, profile_manager->GetNumberOfProfiles());
292 EXPECT_FALSE([avatar isHidden]);
293 EXPECT_FALSE([fullscreen_button isHidden]);
294 EXPECT_EQ([avatar window], [fullscreen_button window]);
296 // Make sure the visual order of the buttons is correct and that they don't
298 NSRect avatar_frame = [avatar frame];
299 NSRect fullscreen_frame = [fullscreen_button frame];
301 EXPECT_LT(NSMinX(fullscreen_frame), NSMinX(avatar_frame));
302 EXPECT_LT(NSMaxX(fullscreen_frame), NSMinX(avatar_frame));
305 // Verify that in non-Instant normal mode that the find bar and download shelf
306 // are above the content area. Everything else should be below it.
307 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ZOrderNormal) {
308 browser()->GetFindBarController(); // add find bar
310 std::vector<ViewID> view_list;
311 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF);
312 view_list.push_back(VIEW_ID_BOOKMARK_BAR);
313 view_list.push_back(VIEW_ID_TOOLBAR);
314 view_list.push_back(VIEW_ID_INFO_BAR);
315 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA);
316 view_list.push_back(VIEW_ID_FIND_BAR);
317 VerifyZOrder(view_list);
319 [controller() showOverlay];
320 [controller() removeOverlay];
321 VerifyZOrder(view_list);
323 [controller() enterImmersiveFullscreen];
324 [controller() exitImmersiveFullscreen];
325 VerifyZOrder(view_list);
328 // Verify that in non-Instant presentation mode that the info bar is below the
329 // content are and everything else is above it.
330 // DISABLED due to flaky failures on trybots. http://crbug.com/178778
331 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
332 DISABLED_ZOrderPresentationMode) {
333 chrome::ToggleFullscreenMode(browser());
334 browser()->GetFindBarController(); // add find bar
336 std::vector<ViewID> view_list;
337 view_list.push_back(VIEW_ID_INFO_BAR);
338 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA);
339 view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR);
340 view_list.push_back(VIEW_ID_BOOKMARK_BAR);
341 view_list.push_back(VIEW_ID_TOOLBAR);
342 view_list.push_back(VIEW_ID_FIND_BAR);
343 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF);
344 VerifyZOrder(view_list);
347 // Verify that if the fullscreen floating bar view is below the tab content area
348 // then calling |updateSubviewZOrder:| will correctly move back above.
349 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
350 DISABLED_FloatingBarBelowContentView) {
351 // TODO(kbr): re-enable: http://crbug.com/222296
352 if (base::mac::IsOSMountainLionOrLater())
355 chrome::ToggleFullscreenMode(browser());
357 NSView* fullscreen_floating_bar =
358 GetViewWithID(VIEW_ID_FULLSCREEN_FLOATING_BAR);
359 [fullscreen_floating_bar removeFromSuperview];
360 [[[controller() window] contentView] addSubview:fullscreen_floating_bar
361 positioned:NSWindowBelow
363 [controller() updateSubviewZOrder];
365 std::vector<ViewID> view_list;
366 view_list.push_back(VIEW_ID_INFO_BAR);
367 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA);
368 view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR);
369 view_list.push_back(VIEW_ID_BOOKMARK_BAR);
370 view_list.push_back(VIEW_ID_TOOLBAR);
371 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF);
372 VerifyZOrder(view_list);
375 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, SheetPosition) {
376 ASSERT_TRUE([controller() isKindOfClass:[BrowserWindowController class]]);
377 EXPECT_TRUE([controller() isTabbedWindow]);
378 EXPECT_TRUE([controller() hasTabStrip]);
379 EXPECT_FALSE([controller() hasTitleBar]);
380 EXPECT_TRUE([controller() hasToolbar]);
381 EXPECT_FALSE([controller() isBookmarkBarVisible]);
383 NSRect defaultAlertFrame = NSMakeRect(0, 0, 300, 200);
384 NSWindow* window = browser()->window()->GetNativeWindow();
385 NSRect alertFrame = [controller() window:window
386 willPositionSheet:nil
387 usingRect:defaultAlertFrame];
388 NSRect toolbarFrame = [[[controller() toolbarController] view] frame];
389 EXPECT_EQ(NSMinY(alertFrame), NSMinY(toolbarFrame));
391 // Open sheet with normal browser window, persistent bookmark bar.
392 chrome::ToggleBookmarkBarWhenVisible(browser()->profile());
393 EXPECT_TRUE([controller() isBookmarkBarVisible]);
394 alertFrame = [controller() window:window
395 willPositionSheet:nil
396 usingRect:defaultAlertFrame];
397 NSRect bookmarkBarFrame = [[[controller() bookmarkBarController] view] frame];
398 EXPECT_EQ(NSMinY(alertFrame), NSMinY(bookmarkBarFrame));
400 // Make sure the profile does not have the bookmark visible so that
401 // we'll create the shortcut window without the bookmark bar.
402 chrome::ToggleBookmarkBarWhenVisible(browser()->profile());
403 // Open application mode window.
404 OpenAppShortcutWindow(browser()->profile(), GURL("about:blank"));
405 Browser* popup_browser = BrowserList::GetInstance(
406 chrome::GetActiveDesktop())->GetLastActive();
407 NSWindow* popupWindow = popup_browser->window()->GetNativeWindow();
408 BrowserWindowController* popupController =
409 [BrowserWindowController browserWindowControllerForWindow:popupWindow];
410 ASSERT_TRUE([popupController isKindOfClass:[BrowserWindowController class]]);
411 EXPECT_FALSE([popupController isTabbedWindow]);
412 EXPECT_FALSE([popupController hasTabStrip]);
413 EXPECT_TRUE([popupController hasTitleBar]);
414 EXPECT_FALSE([popupController isBookmarkBarVisible]);
415 EXPECT_FALSE([popupController hasToolbar]);
417 // Open sheet in an application window.
418 [popupController showWindow:nil];
419 alertFrame = [popupController window:popupWindow
420 willPositionSheet:nil
421 usingRect:defaultAlertFrame];
422 EXPECT_EQ(NSMinY(alertFrame),
423 NSHeight([[popupWindow contentView] frame]) -
424 defaultAlertFrame.size.height);
426 // Close the application window.
427 popup_browser->tab_strip_model()->CloseSelectedTabs();
428 [popupController close];
431 // Verify that the info bar tip is hidden when the toolbar is not visible.
432 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
433 InfoBarTipHiddenForWindowWithoutToolbar) {
434 ShowInfoBar(browser());
436 [[controller() infoBarContainerController] shouldSuppressTopInfoBarTip]);
438 OpenAppShortcutWindow(browser()->profile(), GURL("about:blank"));
439 Browser* popup_browser = BrowserList::GetInstance(
440 chrome::HOST_DESKTOP_TYPE_NATIVE)->GetLastActive();
441 NSWindow* popupWindow = popup_browser->window()->GetNativeWindow();
442 BrowserWindowController* popupController =
443 [BrowserWindowController browserWindowControllerForWindow:popupWindow];
444 EXPECT_FALSE([popupController hasToolbar]);
446 // Show infobar for controller.
447 ShowInfoBar(popup_browser);
449 [[popupController infoBarContainerController]
450 shouldSuppressTopInfoBarTip]);
453 // Tests that status bubble's base frame does move when devTools are docked.
454 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
455 StatusBubblePositioning) {
456 NSPoint origin = [controller() statusBubbleBaseFrame].origin;
458 DevToolsWindow* devtools_window =
459 DevToolsWindowTesting::OpenDevToolsWindowSync(browser(), true);
460 DevToolsWindowTesting::Get(devtools_window)->SetInspectedPageBounds(
461 gfx::Rect(10, 10, 100, 100));
463 NSPoint originWithDevTools = [controller() statusBubbleBaseFrame].origin;
464 EXPECT_FALSE(NSEqualPoints(origin, originWithDevTools));
466 DevToolsWindowTesting::CloseDevToolsWindowSync(devtools_window);
469 // Tests that top infobar tip is streched when bookmark bar becomes SHOWN/HIDDEN
470 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest,
471 InfoBarTipStretchedWhenBookmarkBarStatusChanged) {
472 EXPECT_FALSE([controller() isBookmarkBarVisible]);
473 ShowInfoBar(browser());
474 // The infobar tip is animated during the infobar is being added, wait until
476 WaitForTopInfoBarAnimationToFinish();
478 EXPECT_FALSE([[controller() infoBarContainerController]
479 shouldSuppressTopInfoBarTip]);
481 NSInteger max_tip_height = infobars::InfoBar::kMaximumArrowTargetHeight +
482 infobars::InfoBar::kSeparatorLineHeight;
484 chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR);
485 WaitForBookmarkBarAnimationToFinish();
486 EXPECT_TRUE([controller() isBookmarkBarVisible]);
487 EXPECT_EQ(std::min(GetExpectedTopInfoBarTipHeight(), max_tip_height),
488 [[controller() infoBarContainerController] overlappingTipHeight]);
490 chrome::ExecuteCommand(browser(), IDC_SHOW_BOOKMARK_BAR);
491 WaitForBookmarkBarAnimationToFinish();
492 EXPECT_FALSE([controller() isBookmarkBarVisible]);
493 EXPECT_EQ(std::min(GetExpectedTopInfoBarTipHeight(), max_tip_height),
494 [[controller() infoBarContainerController] overlappingTipHeight]);
497 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, TrafficLightZOrder) {
498 // Verify z order immediately after creation.
499 VerifyTrafficLightZOrder();
501 // Toggle overlay, then verify z order.
502 [controller() showOverlay];
503 [controller() removeOverlay];
504 VerifyTrafficLightZOrder();
506 // Toggle immersive fullscreen, then verify z order.
507 [controller() enterImmersiveFullscreen];
508 [controller() exitImmersiveFullscreen];
509 VerifyTrafficLightZOrder();