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#import "chrome/browser/ui/cocoa/browser_window_controller.h" 6 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/browser/browser_process.h" 12#include "chrome/browser/devtools/devtools_window.h" 13#include "chrome/browser/infobars/infobar_service.h" 14#include "chrome/browser/infobars/simple_alert_infobar_delegate.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/profiles/profile_manager.h" 17#include "chrome/browser/ui/bookmarks/bookmark_utils.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_commands.h" 20#include "chrome/browser/ui/browser_list.h" 21#include "chrome/browser/ui/browser_window.h" 22#include "chrome/browser/ui/cocoa/browser_window_cocoa.h" 23#import "chrome/browser/ui/cocoa/browser_window_controller_private.h" 24#import "chrome/browser/ui/cocoa/fast_resize_view.h" 25#import "chrome/browser/ui/cocoa/history_overlay_controller.h" 26#import "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h" 27#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 28#import "chrome/browser/ui/cocoa/nsview_additions.h" 29#import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h" 30#import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h" 31#include "chrome/browser/ui/extensions/application_launch.h" 32#include "chrome/browser/ui/find_bar/find_bar_controller.h" 33#include "chrome/browser/ui/find_bar/find_bar.h" 34#include "chrome/browser/ui/tabs/tab_strip_model.h" 35#include "chrome/test/base/in_process_browser_test.h" 36#include "chrome/test/base/testing_profile.h" 37#include "content/public/browser/web_contents.h" 38#import "testing/gtest_mac.h" 39 40namespace { 41 42void CreateProfileCallback(const base::Closure& quit_closure, 43 Profile* profile, 44 Profile::CreateStatus status) { 45 EXPECT_TRUE(profile); 46 EXPECT_NE(Profile::CREATE_STATUS_LOCAL_FAIL, status); 47 EXPECT_NE(Profile::CREATE_STATUS_REMOTE_FAIL, status); 48 // This will be called multiple times. Wait until the profile is initialized 49 // fully to quit the loop. 50 if (status == Profile::CREATE_STATUS_INITIALIZED) 51 quit_closure.Run(); 52} 53 54enum ViewID { 55 VIEW_ID_TOOLBAR, 56 VIEW_ID_BOOKMARK_BAR, 57 VIEW_ID_INFO_BAR, 58 VIEW_ID_FIND_BAR, 59 VIEW_ID_DOWNLOAD_SHELF, 60 VIEW_ID_TAB_CONTENT_AREA, 61 VIEW_ID_FULLSCREEN_FLOATING_BAR, 62 VIEW_ID_COUNT, 63}; 64 65} // namespace 66 67class BrowserWindowControllerTest : public InProcessBrowserTest { 68 public: 69 BrowserWindowControllerTest() : InProcessBrowserTest() { 70 } 71 72 virtual void SetUpOnMainThread() OVERRIDE { 73 [[controller() bookmarkBarController] setStateAnimationsEnabled:NO]; 74 [[controller() bookmarkBarController] setInnerContentAnimationsEnabled:NO]; 75 } 76 77 BrowserWindowController* controller() const { 78 return [BrowserWindowController browserWindowControllerForWindow: 79 browser()->window()->GetNativeWindow()]; 80 } 81 82 static void ShowInfoBar(Browser* browser) { 83 SimpleAlertInfoBarDelegate::Create( 84 InfoBarService::FromWebContents( 85 browser->tab_strip_model()->GetActiveWebContents()), 86 0, base::string16(), false); 87 } 88 89 NSView* GetViewWithID(ViewID view_id) const { 90 switch (view_id) { 91 case VIEW_ID_FULLSCREEN_FLOATING_BAR: 92 return [controller() floatingBarBackingView]; 93 case VIEW_ID_TOOLBAR: 94 return [[controller() toolbarController] view]; 95 case VIEW_ID_BOOKMARK_BAR: 96 return [[controller() bookmarkBarController] view]; 97 case VIEW_ID_INFO_BAR: 98 return [[controller() infoBarContainerController] view]; 99 case VIEW_ID_FIND_BAR: 100 return [[controller() findBarCocoaController] view]; 101 case VIEW_ID_DOWNLOAD_SHELF: 102 return [[controller() downloadShelf] view]; 103 case VIEW_ID_TAB_CONTENT_AREA: 104 return [controller() tabContentArea]; 105 default: 106 NOTREACHED(); 107 return nil; 108 } 109 } 110 111 void VerifyZOrder(const std::vector<ViewID>& view_list) const { 112 for (size_t i = 0; i < view_list.size() - 1; ++i) { 113 NSView* bottom_view = GetViewWithID(view_list[i]); 114 NSView* top_view = GetViewWithID(view_list[i + 1]); 115 EXPECT_NSEQ([bottom_view superview], [top_view superview]); 116 EXPECT_TRUE([bottom_view cr_isBelowView:top_view]); 117 } 118 119 // Views not in |view_list| must either be nil or not parented. 120 for (size_t i = 0; i < VIEW_ID_COUNT; ++i) { 121 if (std::find(view_list.begin(), view_list.end(), i) == view_list.end()) { 122 NSView* view = GetViewWithID(static_cast<ViewID>(i)); 123 EXPECT_TRUE(!view || ![view superview]); 124 } 125 } 126 } 127 128 CGFloat GetViewHeight(ViewID viewID) const { 129 CGFloat height = NSHeight([GetViewWithID(viewID) frame]); 130 if (viewID == VIEW_ID_INFO_BAR) { 131 height -= [[controller() infoBarContainerController] 132 overlappingTipHeight]; 133 } 134 return height; 135 } 136 137 void SetDevToolsWindowContentsBounds( 138 DevToolsWindow* window, const gfx::Rect& bounds) { 139 window->SetInspectedPageBounds(bounds); 140 } 141 142 private: 143 DISALLOW_COPY_AND_ASSIGN(BrowserWindowControllerTest); 144}; 145 146// Tests that adding the first profile moves the Lion fullscreen button over 147// correctly. 148// DISABLED_ because it regularly times out: http://crbug.com/159002. 149IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 150 DISABLED_ProfileAvatarFullscreenButton) { 151 if (base::mac::IsOSSnowLeopard()) 152 return; 153 154 // Initialize the locals. 155 ProfileManager* profile_manager = g_browser_process->profile_manager(); 156 ASSERT_TRUE(profile_manager); 157 158 NSWindow* window = browser()->window()->GetNativeWindow(); 159 ASSERT_TRUE(window); 160 161 // With only one profile, the fullscreen button should be visible, but the 162 // avatar button should not. 163 EXPECT_EQ(1u, profile_manager->GetNumberOfProfiles()); 164 165 NSButton* fullscreen_button = 166 [window standardWindowButton:NSWindowFullScreenButton]; 167 EXPECT_TRUE(fullscreen_button); 168 EXPECT_FALSE([fullscreen_button isHidden]); 169 170 AvatarBaseController* avatar_controller = 171 [controller() avatarButtonController]; 172 NSView* avatar = [avatar_controller view]; 173 EXPECT_TRUE(avatar); 174 EXPECT_TRUE([avatar isHidden]); 175 176 // Create a profile asynchronously and run the loop until its creation 177 // is complete. 178 base::RunLoop run_loop; 179 180 ProfileManager::CreateCallback create_callback = 181 base::Bind(&CreateProfileCallback, run_loop.QuitClosure()); 182 profile_manager->CreateProfileAsync( 183 profile_manager->user_data_dir().Append("test"), 184 create_callback, 185 base::ASCIIToUTF16("avatar_test"), 186 base::string16(), 187 std::string()); 188 189 run_loop.Run(); 190 191 // There should now be two profiles, and the avatar button and fullscreen 192 // button are both visible. 193 EXPECT_EQ(2u, profile_manager->GetNumberOfProfiles()); 194 EXPECT_FALSE([avatar isHidden]); 195 EXPECT_FALSE([fullscreen_button isHidden]); 196 EXPECT_EQ([avatar window], [fullscreen_button window]); 197 198 // Make sure the visual order of the buttons is correct and that they don't 199 // overlap. 200 NSRect avatar_frame = [avatar frame]; 201 NSRect fullscreen_frame = [fullscreen_button frame]; 202 203 EXPECT_LT(NSMinX(fullscreen_frame), NSMinX(avatar_frame)); 204 EXPECT_LT(NSMaxX(fullscreen_frame), NSMinX(avatar_frame)); 205} 206 207// Verify that in non-Instant normal mode that the find bar and download shelf 208// are above the content area. Everything else should be below it. 209IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, ZOrderNormal) { 210 browser()->GetFindBarController(); // add find bar 211 212 std::vector<ViewID> view_list; 213 view_list.push_back(VIEW_ID_BOOKMARK_BAR); 214 view_list.push_back(VIEW_ID_TOOLBAR); 215 view_list.push_back(VIEW_ID_INFO_BAR); 216 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); 217 view_list.push_back(VIEW_ID_FIND_BAR); 218 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); 219 VerifyZOrder(view_list); 220} 221 222// Verify that in non-Instant presentation mode that the info bar is below the 223// content are and everything else is above it. 224// DISABLED due to flaky failures on trybots. http://crbug.com/178778 225IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 226 DISABLED_ZOrderPresentationMode) { 227 chrome::ToggleFullscreenMode(browser()); 228 browser()->GetFindBarController(); // add find bar 229 230 std::vector<ViewID> view_list; 231 view_list.push_back(VIEW_ID_INFO_BAR); 232 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); 233 view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR); 234 view_list.push_back(VIEW_ID_BOOKMARK_BAR); 235 view_list.push_back(VIEW_ID_TOOLBAR); 236 view_list.push_back(VIEW_ID_FIND_BAR); 237 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); 238 VerifyZOrder(view_list); 239} 240 241// Verify that if the fullscreen floating bar view is below the tab content area 242// then calling |updateSubviewZOrder:| will correctly move back above. 243IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 244 DISABLED_FloatingBarBelowContentView) { 245 // TODO(kbr): re-enable: http://crbug.com/222296 246 if (base::mac::IsOSMountainLionOrLater()) 247 return; 248 249 chrome::ToggleFullscreenMode(browser()); 250 251 NSView* fullscreen_floating_bar = 252 GetViewWithID(VIEW_ID_FULLSCREEN_FLOATING_BAR); 253 [fullscreen_floating_bar removeFromSuperview]; 254 [[[controller() window] contentView] addSubview:fullscreen_floating_bar 255 positioned:NSWindowBelow 256 relativeTo:nil]; 257 [controller() updateSubviewZOrder:[controller() inPresentationMode]]; 258 259 std::vector<ViewID> view_list; 260 view_list.push_back(VIEW_ID_INFO_BAR); 261 view_list.push_back(VIEW_ID_TAB_CONTENT_AREA); 262 view_list.push_back(VIEW_ID_FULLSCREEN_FLOATING_BAR); 263 view_list.push_back(VIEW_ID_BOOKMARK_BAR); 264 view_list.push_back(VIEW_ID_TOOLBAR); 265 view_list.push_back(VIEW_ID_DOWNLOAD_SHELF); 266 VerifyZOrder(view_list); 267} 268 269IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, SheetPosition) { 270 ASSERT_TRUE([controller() isKindOfClass:[BrowserWindowController class]]); 271 EXPECT_TRUE([controller() isTabbedWindow]); 272 EXPECT_TRUE([controller() hasTabStrip]); 273 EXPECT_FALSE([controller() hasTitleBar]); 274 EXPECT_TRUE([controller() hasToolbar]); 275 EXPECT_FALSE([controller() isBookmarkBarVisible]); 276 277 NSRect defaultAlertFrame = NSMakeRect(0, 0, 300, 200); 278 NSWindow* window = browser()->window()->GetNativeWindow(); 279 NSRect alertFrame = [controller() window:window 280 willPositionSheet:nil 281 usingRect:defaultAlertFrame]; 282 NSRect toolbarFrame = [[[controller() toolbarController] view] frame]; 283 EXPECT_EQ(NSMinY(alertFrame), NSMinY(toolbarFrame)); 284 285 // Open sheet with normal browser window, persistent bookmark bar. 286 chrome::ToggleBookmarkBarWhenVisible(browser()->profile()); 287 EXPECT_TRUE([controller() isBookmarkBarVisible]); 288 alertFrame = [controller() window:window 289 willPositionSheet:nil 290 usingRect:defaultAlertFrame]; 291 NSRect bookmarkBarFrame = [[[controller() bookmarkBarController] view] frame]; 292 EXPECT_EQ(NSMinY(alertFrame), NSMinY(bookmarkBarFrame)); 293 294 // Make sure the profile does not have the bookmark visible so that 295 // we'll create the shortcut window without the bookmark bar. 296 chrome::ToggleBookmarkBarWhenVisible(browser()->profile()); 297 // Open application mode window. 298 OpenAppShortcutWindow(browser()->profile(), GURL("about:blank")); 299 Browser* popup_browser = BrowserList::GetInstance( 300 chrome::GetActiveDesktop())->GetLastActive(); 301 NSWindow* popupWindow = popup_browser->window()->GetNativeWindow(); 302 BrowserWindowController* popupController = 303 [BrowserWindowController browserWindowControllerForWindow:popupWindow]; 304 ASSERT_TRUE([popupController isKindOfClass:[BrowserWindowController class]]); 305 EXPECT_FALSE([popupController isTabbedWindow]); 306 EXPECT_FALSE([popupController hasTabStrip]); 307 EXPECT_TRUE([popupController hasTitleBar]); 308 EXPECT_FALSE([popupController isBookmarkBarVisible]); 309 EXPECT_FALSE([popupController hasToolbar]); 310 311 // Open sheet in an application window. 312 [popupController showWindow:nil]; 313 alertFrame = [popupController window:popupWindow 314 willPositionSheet:nil 315 usingRect:defaultAlertFrame]; 316 EXPECT_EQ(NSMinY(alertFrame), 317 NSHeight([[popupWindow contentView] frame]) - 318 defaultAlertFrame.size.height); 319 320 // Close the application window. 321 popup_browser->tab_strip_model()->CloseSelectedTabs(); 322 [popupController close]; 323} 324 325// Verify that the info bar tip is hidden when the toolbar is not visible. 326IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 327 InfoBarTipHiddenForWindowWithoutToolbar) { 328 ShowInfoBar(browser()); 329 EXPECT_FALSE( 330 [[controller() infoBarContainerController] shouldSuppressTopInfoBarTip]); 331 332 OpenAppShortcutWindow(browser()->profile(), GURL("about:blank")); 333 Browser* popup_browser = BrowserList::GetInstance( 334 chrome::HOST_DESKTOP_TYPE_NATIVE)->GetLastActive(); 335 NSWindow* popupWindow = popup_browser->window()->GetNativeWindow(); 336 BrowserWindowController* popupController = 337 [BrowserWindowController browserWindowControllerForWindow:popupWindow]; 338 EXPECT_FALSE([popupController hasToolbar]); 339 340 // Show infobar for controller. 341 ShowInfoBar(popup_browser); 342 EXPECT_TRUE( 343 [[popupController infoBarContainerController] 344 shouldSuppressTopInfoBarTip]); 345} 346 347// Verify that AllowOverlappingViews is set while the history overlay is 348// visible. 349IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 350 AllowOverlappingViewsHistoryOverlay) { 351 content::WebContents* web_contents = 352 browser()->tab_strip_model()->GetActiveWebContents(); 353 EXPECT_TRUE(web_contents->GetAllowOverlappingViews()); 354 355 base::scoped_nsobject<HistoryOverlayController> overlay( 356 [[HistoryOverlayController alloc] initForMode:kHistoryOverlayModeBack]); 357 [overlay showPanelForView:web_contents->GetNativeView()]; 358 EXPECT_TRUE(web_contents->GetAllowOverlappingViews()); 359 360 overlay.reset(); 361 EXPECT_TRUE(web_contents->GetAllowOverlappingViews()); 362} 363 364// Tests that status bubble's base frame does move when devTools are docked. 365IN_PROC_BROWSER_TEST_F(BrowserWindowControllerTest, 366 StatusBubblePositioning) { 367 NSPoint origin = [controller() statusBubbleBaseFrame].origin; 368 369 DevToolsWindow* devtools_window = DevToolsWindow::OpenDevToolsWindowForTest( 370 browser(), true); 371 SetDevToolsWindowContentsBounds(devtools_window, gfx::Rect(10, 10, 100, 100)); 372 373 NSPoint originWithDevTools = [controller() statusBubbleBaseFrame].origin; 374 EXPECT_FALSE(NSEqualPoints(origin, originWithDevTools)); 375} 376