// Copyright (c) 2012 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 "chrome/browser/ui/cocoa/browser_window_controller.h" #import "base/mac/mac_util.h" #include "base/run_loop.h" #include "base/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/infobars/confirm_infobar_delegate.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/search/search.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_tabstrip.cc" #include "chrome/browser/ui/browser_window.h" #import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h" #include "chrome/browser/ui/cocoa/browser_window_cocoa.h" #import "chrome/browser/ui/cocoa/browser_window_controller_private.h" #import "chrome/browser/ui/cocoa/fast_resize_view.h" #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" #import "chrome/browser/ui/cocoa/nsview_additions.h" #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h" #include "chrome/browser/ui/extensions/application_launch.h" #include "chrome/browser/ui/find_bar/find_bar_controller.h" #include "chrome/browser/ui/find_bar/find_bar.h" #include "chrome/browser/ui/search/search_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/testing_profile.h" #include "content/public/browser/web_contents.h" #import "testing/gtest_mac.h" namespace { #if !defined(MAC_OS_X_VERSION_10_7) || \ MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 enum { NSWindowDocumentVersionsButton = 6, NSWindowFullScreenButton }; #endif // MAC_OS_X_VERSION_10_7 void CreateProfileCallback(const base::Closure& quit_closure, Profile* profile, Profile::CreateStatus status) { EXPECT_TRUE(profile); EXPECT_NE(Profile::CREATE_STATUS_FAIL, status); // This will be called multiple times. Wait until the profile is initialized // fully to quit the loop. if (status == Profile::CREATE_STATUS_INITIALIZED) quit_closure.Run(); } enum ViewID { VIEW_ID_TOOLBAR, VIEW_ID_BOOKMARK_BAR, VIEW_ID_INFO_BAR, VIEW_ID_FIND_BAR, VIEW_ID_DOWNLOAD_SHELF, VIEW_ID_TAB_CONTENT_AREA, VIEW_ID_FULLSCREEN_FLOATING_BAR, VIEW_ID_COUNT, }; // A very simple info bar implementation used to show an infobar on the browser // window. class DummyInfoBar : public ConfirmInfoBarDelegate { public: explicit DummyInfoBar(InfoBarService* service) : ConfirmInfoBarDelegate(service) { } virtual ~DummyInfoBar() { } virtual string16 GetMessageText() const OVERRIDE { return string16(); } private: DISALLOW_COPY_AND_ASSIGN(DummyInfoBar); }; } // namespace class BrowserWindowControllerTest : public InProcessBrowserTest { public: BrowserWindowControllerTest() : InProcessBrowserTest() { } virtual void SetUpOnMainThread() OVERRIDE { [[controller() bookmarkBarController] setStateAnimationsEnabled:NO]; [[controller() bookmarkBarController] setInnerContentAnimationsEnabled:NO]; } BrowserWindowController* controller() const { return [BrowserWindowController browserWindowControllerForWindow: browser()->window()->GetNativeWindow()]; } void ShowInstantResults() { chrome::EnableInstantExtendedAPIForTesting(); SearchMode mode(SearchMode::MODE_SEARCH_SUGGESTIONS, SearchMode::ORIGIN_SEARCH); browser()->search_model()->SetMode(mode); browser()->search_model()->SetTopBarsVisible(false); EXPECT_TRUE(browser()->search_model()->mode().is_search_suggestions()); EXPECT_EQ(browser_window_controller::kInstantUIFullPageResults, [controller() currentInstantUIState]); } void ShowInstantNTP() { chrome::EnableInstantExtendedAPIForTesting(); SearchMode mode(SearchMode::MODE_NTP, SearchMode::ORIGIN_NTP); browser()->search_model()->SetMode(mode); browser()->search_model()->SetTopBarsVisible(true); EXPECT_TRUE(browser()->search_model()->mode().is_ntp()); EXPECT_EQ(browser_window_controller::kInstantUINone, [controller() currentInstantUIState]); } void ShowInfoBar() { content::WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); InfoBarService* service = InfoBarService::FromWebContents(web_contents); info_bar_delegate_.reset(new DummyInfoBar(service)); [[controller() infoBarContainerController] addInfoBar:info_bar_delegate_->CreateInfoBar(service) animate:NO]; } void HideInstant() { SearchMode mode; browser()->search_model()->SetMode(mode); browser()->search_model()->SetTopBarsVisible(true); EXPECT_TRUE(browser()->search_model()->mode().is_default()); EXPECT_EQ(browser_window_controller::kInstantUINone, [controller() currentInstantUIState]); } NSView* GetViewWithID(ViewID view_id) const { switch (view_id) { case VIEW_ID_FULLSCREEN_FLOATING_BAR: return [controller() floatingBarBackingView]; case VIEW_ID_TOOLBAR: return [[controller() toolbarController] view]; case VIEW_ID_BOOKMARK_BAR: return [[controller() bookmarkBarController] view]; case VIEW_ID_INFO_BAR: return [[controller() infoBarContainerController] view]; case VIEW_ID_FIND_BAR: return [[controller() findBarCocoaController] view]; case VIEW_ID_DOWNLOAD_SHELF: return [[controller() downloadShelf] view]; case VIEW_ID_TAB_CONTENT_AREA: return [controller() tabContentArea]; default: NOTREACHED(); return nil; } } void VerifyZOrder(const std::vector& view_list) const { for (size_t i = 0; i < view_list.size() - 1; ++i) { NSView* bottom_view = GetViewWithID(view_list[i]); NSView* top_view = GetViewWithID(view_list[i + 1]); EXPECT_NSEQ([bottom_view superview], [top_view superview]); EXPECT_TRUE([bottom_view cr_isBelowView:top_view]); } // Views not in |view_list| must either be nil or not parented. for (size_t i = 0; i < VIEW_ID_COUNT; ++i) { if (std::find(view_list.begin(), view_list.end(), i) == view_list.end()) { NSView* view = GetViewWithID(static_cast(i)); EXPECT_TRUE(!view || ![view superview]); } } } CGFloat GetViewHeight(ViewID viewID) const { CGFloat height = NSHeight([GetViewWithID(viewID) frame]); if (viewID == VIEW_ID_INFO_BAR) { height -= [[controller() infoBarContainerController] overlappingTipHeight]; } return height; } private: scoped_ptr info_bar_delegate_; DISALLOW_COPY_AND_ASSIGN(BrowserWindowControllerTest); }; // Tests that adding the first profile moves the Lion fullscreen button over // correctly. // DISABLED_ because it regularly times out: http://crbug.com/159002. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_ProfileAvatarFullscreenButton) { if (base::mac::IsOSSnowLeopard()) return; // Initialize the locals. ProfileManager* profile_manager = g_browser_process->profile_manager(); ASSERT_TRUE(profile_manager); NSWindow* window = browser()->window()->GetNativeWindow(); ASSERT_TRUE(window); // With only one profile, the fullscreen button should be visible, but the // avatar button should not. EXPECT_EQ(1u, profile_manager->GetNumberOfProfiles()); NSButton* fullscreen_button = [window standardWindowButton:NSWindowFullScreenButton]; EXPECT_TRUE(fullscreen_button); EXPECT_FALSE([fullscreen_button isHidden]); AvatarButtonController* avatar_controller = [controller() avatarButtonController]; NSView* avatar = [avatar_controller view]; EXPECT_TRUE(avatar); EXPECT_TRUE([avatar isHidden]); // Create a profile asynchronously and run the loop until its creation // is complete. base::RunLoop run_loop; ProfileManager::CreateCallback create_callback = base::Bind(&CreateProfileCallback, run_loop.QuitClosure()); profile_manager->CreateProfileAsync( profile_manager->user_data_dir().Append("test"), create_callback, ASCIIToUTF16("avatar_test"), string16(), false); run_loop.Run(); // There should now be two profiles, and the avatar button and fullscreen // button are both visible. EXPECT_EQ(2u, profile_manager->GetNumberOfProfiles()); EXPECT_FALSE([avatar isHidden]); EXPECT_FALSE([fullscreen_button isHidden]); EXPECT_EQ([avatar window], [fullscreen_button window]); // Make sure the visual order of the buttons is correct and that they don't // overlap. NSRect avatar_frame = [avatar frame]; NSRect fullscreen_frame = [fullscreen_button frame]; EXPECT_LT(NSMinX(fullscreen_frame), NSMinX(avatar_frame)); EXPECT_LT(NSMaxX(fullscreen_frame), NSMinX(avatar_frame)); } // Verify that in non-Instant normal mode that the find bar and download shelf // are above the content area. Everything else should be below it. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ZOrderNormal) { browser()->GetFindBarController(); // add find bar std::vector view_list; view_list.push_back(VIEW_ID_BOOKMARK_BAR); view_list.push_back(VIEW_ID_TOOLBAR); view_list.push_back(VIEW_ID_INFO_BAR); view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); view_list.push_back(VIEW_ID_FIND_BAR); view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); VerifyZOrder(view_list); } // Verify that in non-Instant presentation mode that the info bar is below the // content are and everything else is above it. // DISABLED due to flaky failures on trybots. http://crbug.com/178778 IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_ZOrderPresentationMode) { chrome::ToggleFullscreenMode(browser()); browser()->GetFindBarController(); // add find bar std::vector view_list; view_list.push_back(VIEW_ID_INFO_BAR); view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR); view_list.push_back(VIEW_ID_BOOKMARK_BAR); view_list.push_back(VIEW_ID_TOOLBAR); view_list.push_back(VIEW_ID_FIND_BAR); view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); VerifyZOrder(view_list); } // Normal mode with Instant results showing. Should be same z-order as normal // mode except find bar is below content area. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ZOrderNormalInstant) { ShowInstantResults(); browser()->GetFindBarController(); // add find bar std::vector view_list; view_list.push_back(VIEW_ID_BOOKMARK_BAR); view_list.push_back(VIEW_ID_TOOLBAR); view_list.push_back(VIEW_ID_INFO_BAR); view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); view_list.push_back(VIEW_ID_FIND_BAR); view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); VerifyZOrder(view_list); } // Presentation mode with Instant results showing. Should be exact same as // non-Instant presentation mode. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_ZOrderInstantPresentationMode) { // TODO(kbr): re-enable: http://crbug.com/222296 if (base::mac::IsOSMountainLionOrLater()) return; chrome::ToggleFullscreenMode(browser()); ShowInstantResults(); browser()->GetFindBarController(); // add find bar std::vector view_list; view_list.push_back(VIEW_ID_INFO_BAR); view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR); view_list.push_back(VIEW_ID_BOOKMARK_BAR); view_list.push_back(VIEW_ID_TOOLBAR); view_list.push_back(VIEW_ID_FIND_BAR); view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); VerifyZOrder(view_list); } // Verify that in presentation mode, Instant search results are below the // floating toolbar. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_OverlayOffsetInstantPresentationMode) { chrome::ToggleFullscreenMode(browser()); ShowInstantResults(); [controller() setFloatingBarShownFraction:0.0]; EXPECT_EQ( 0, [[controller() overlayableContentsController] overlayContentsOffset]); EXPECT_EQ( 0, [[controller() overlayableContentsController] activeContainerOffset]); [controller() setFloatingBarShownFraction:1.0]; NSView* floating_bar = GetViewWithID(VIEW_ID_FULLSCREEN_FLOATING_BAR); CGFloat floating_bar_height = NSHeight([floating_bar frame]); EXPECT_EQ( floating_bar_height, [[controller() overlayableContentsController] overlayContentsOffset]); EXPECT_EQ( floating_bar_height, [[controller() overlayableContentsController] activeContainerOffset]); } // Verify that if the fullscreen floating bar view is below the tab content area // then calling |updateSubviewZOrder:| will correctly move back above. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_FloatingBarBelowContentView) { // TODO(kbr): re-enable: http://crbug.com/222296 if (base::mac::IsOSMountainLionOrLater()) return; chrome::ToggleFullscreenMode(browser()); NSView* fullscreen_floating_bar = GetViewWithID(VIEW_ID_FULLSCREEN_FLOATING_BAR); [fullscreen_floating_bar removeFromSuperview]; [[[controller() window] contentView] addSubview:fullscreen_floating_bar positioned:NSWindowBelow relativeTo:nil]; [controller() updateSubviewZOrder:[controller() inPresentationMode]]; std::vector view_list; view_list.push_back(VIEW_ID_INFO_BAR); view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR); view_list.push_back(VIEW_ID_BOOKMARK_BAR); view_list.push_back(VIEW_ID_TOOLBAR); view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); VerifyZOrder(view_list); } // Verify that in non-Instant presentation mode the content area is beneath // the bookmark bar and info bar. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ContentOffset) { OverlayableContentsController* overlay = [controller() overlayableContentsController]; // Just toolbar. EXPECT_EQ(1, [overlay activeContainerOffset]); // Plus bookmark bar. browser()->window()->ToggleBookmarkBar(); CGFloat bookmark_bar_offset = GetViewHeight(VIEW_ID_BOOKMARK_BAR) - bookmarks::kBookmarkBarOverlap + 1; EXPECT_EQ(bookmark_bar_offset, [overlay activeContainerOffset]); // Plus info bar. ShowInfoBar(); EXPECT_EQ(bookmark_bar_offset + GetViewHeight(VIEW_ID_INFO_BAR), [overlay activeContainerOffset]); // Minus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(GetViewHeight(VIEW_ID_INFO_BAR) + 1, [overlay activeContainerOffset]); } // Verify that in non-Instant presentation mode the content area is beneath // the info bar. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, DISABLED_ContentOffsetPresentationMode) { // TODO(kbr): re-enable: http://crbug.com/222296 if (base::mac::IsOSMountainLionOrLater()) return; chrome::ToggleFullscreenMode(browser()); OverlayableContentsController* overlay = [controller() overlayableContentsController]; // Just toolbar. EXPECT_EQ(0, [overlay activeContainerOffset]); // Plus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); // Plus info bar. ShowInfoBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); // Minus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); } // Verify that when showing Instant results the content area overlaps the // bookmark bar and info bar. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ContentOffsetInstant) { OverlayableContentsController* overlay = [controller() overlayableContentsController]; ShowInstantResults(); // Just toolbar. EXPECT_EQ(0, [overlay activeContainerOffset]); // Plus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); // Plus info bar. ShowInfoBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); // Minus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(0, [overlay activeContainerOffset]); } // The Instant NTP case is same as normal case except that the overlay is // also shifted down. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ContentOffsetInstantNTP) { ShowInstantNTP(); OverlayableContentsController* overlay = [controller() overlayableContentsController]; // Just toolbar. EXPECT_EQ(1, [overlay activeContainerOffset]); // Plus bookmark bar. browser()->window()->ToggleBookmarkBar(); CGFloat bookmark_bar_offset = GetViewHeight(VIEW_ID_BOOKMARK_BAR) - bookmarks::kBookmarkBarOverlap + 1; EXPECT_EQ(bookmark_bar_offset, [overlay activeContainerOffset]); // Plus info bar. ShowInfoBar(); EXPECT_EQ(bookmark_bar_offset + GetViewHeight(VIEW_ID_INFO_BAR), [overlay activeContainerOffset]); // Minus bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_EQ(GetViewHeight(VIEW_ID_INFO_BAR) + 1, [overlay activeContainerOffset]); } // Verify that the find bar is positioned corerctly when a full page instant // search result is displayed. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, FindBarOffsetInstant) { // Add bookmark bar and find bar. browser()->window()->ToggleBookmarkBar(); browser()->GetFindBarController(); CGFloat line_width = [GetViewWithID(VIEW_ID_FIND_BAR) cr_lineWidth]; NSRect bookmark_bar_frame = [GetViewWithID(VIEW_ID_BOOKMARK_BAR) frame]; NSRect find_bar_frame = [GetViewWithID(VIEW_ID_FIND_BAR) frame]; EXPECT_EQ(NSMinY(bookmark_bar_frame), NSMaxY(find_bar_frame) - line_width); // Show instant and add a find bar to it. ShowInstantResults(); browser()->GetFindBarController()->find_bar()->Show(false);; NSRect toolbar_bar_frame = [GetViewWithID(VIEW_ID_TOOLBAR) frame]; find_bar_frame = [GetViewWithID(VIEW_ID_FIND_BAR) frame]; EXPECT_EQ(NSMinY(toolbar_bar_frame) - bookmarks::kBookmarkBarOverlap + 1 - line_width, NSMaxY(find_bar_frame)); } // Verify that if bookmark bar is underneath Instant search results then // clicking on Instant search results still works. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, InstantSearchResultsHitTest) { browser()->window()->ToggleBookmarkBar(); ShowInstantResults(); NSView* bookmarkView = [[controller() bookmarkBarController] view]; NSView* contentView = [[controller() window] contentView]; NSPoint point = [bookmarkView convertPoint:NSMakePoint(1, 1) toView:[contentView superview]]; EXPECT_FALSE([[contentView hitTest:point] isDescendantOf:bookmarkView]); EXPECT_TRUE([[contentView hitTest:point] isDescendantOf:[controller() tabContentArea]]); } // Verify that |currentInstantUIState| returns the correct value for search // results mode. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, InstantSearchResultsMode) { chrome::EnableInstantExtendedAPIForTesting(); SearchMode mode(SearchMode::MODE_SEARCH_RESULTS, SearchMode::ORIGIN_SEARCH); browser()->search_model()->SetMode(mode); browser()->search_model()->SetTopBarsVisible(false); EXPECT_TRUE(browser()->search_model()->mode().is_search_results()); EXPECT_EQ(browser_window_controller::kInstantUIFullPageResults, [controller() currentInstantUIState]); } IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, SheetPosition) { NSWindow* window = browser()->window()->GetNativeWindow(); BrowserWindowController* mainController = static_cast([window windowController]); ASSERT_TRUE([mainController isKindOfClass:[BrowserWindowController class]]); EXPECT_TRUE([mainController isTabbedWindow]); EXPECT_TRUE([mainController hasTabStrip]); EXPECT_FALSE([mainController hasTitleBar]); EXPECT_TRUE([mainController hasToolbar]); EXPECT_FALSE([mainController isBookmarkBarVisible]); NSRect defaultAlertFrame = NSMakeRect(0, 0, 300, 200); NSRect alertFrame = [mainController window:window willPositionSheet:nil usingRect:defaultAlertFrame]; NSRect toolbarFrame = [[[mainController toolbarController] view] frame]; EXPECT_EQ(NSMinY(alertFrame), NSMinY(toolbarFrame)); // Open sheet with normal browser window, persistent bookmark bar. browser()->window()->ToggleBookmarkBar(); EXPECT_TRUE([mainController isBookmarkBarVisible]); alertFrame = [mainController window:window willPositionSheet:nil usingRect:defaultAlertFrame]; NSRect bookmarkBarFrame = [[[mainController bookmarkBarController] view] frame]; EXPECT_EQ(NSMinY(alertFrame), NSMinY(bookmarkBarFrame)); // Make sure the profile does not have the bookmark visible so that // we'll create the shortcut window without the bookmark bar. browser()->window()->ToggleBookmarkBar(); // Open application mode window. gfx::Rect initial_bounds(0, 0, 400, 400); chrome::OpenAppShortcutWindow( browser()->profile(), GURL("about:blank"), initial_bounds); Browser* popup_browser = BrowserList::GetInstance( chrome::HOST_DESKTOP_TYPE_NATIVE)->GetLastActive(); NSWindow* popupWindow = popup_browser->window()->GetNativeWindow(); BrowserWindowController* controller = static_cast([popupWindow windowController]); ASSERT_TRUE([controller isKindOfClass:[BrowserWindowController class]]); EXPECT_FALSE([controller isTabbedWindow]); EXPECT_FALSE([controller hasTabStrip]); EXPECT_TRUE([controller hasTitleBar]); EXPECT_FALSE([controller isBookmarkBarVisible]); EXPECT_FALSE([controller hasToolbar]); // Open sheet in an application window. [controller showWindow:nil]; alertFrame = [controller window:popupWindow willPositionSheet:nil usingRect:defaultAlertFrame]; EXPECT_EQ(NSMinY(alertFrame), NSHeight([[popupWindow contentView] frame]) - defaultAlertFrame.size.height); // Close the application window. popup_browser->tab_strip_model()->CloseSelectedTabs(); [controller close]; } // Verify that the info bar tip is hidden when the instant overlay is visible. IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, InfoBarTipHiddenForInstant) { ShowInfoBar(); EXPECT_FALSE( [[controller() infoBarContainerController] shouldSuppressTopInfoBarTip]); ShowInstantResults(); EXPECT_TRUE( [[controller() infoBarContainerController] shouldSuppressTopInfoBarTip]); HideInstant(); EXPECT_FALSE( [[controller() infoBarContainerController] shouldSuppressTopInfoBarTip]); }