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 #include "base/mac/mac_util.h"
8 #import "base/mac/scoped_nsobject.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/signin/fake_signin_manager.h"
15 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
16 #include "chrome/browser/signin/signin_manager_factory.h"
17 #include "chrome/browser/sync/profile_sync_service.h"
18 #include "chrome/browser/sync/profile_sync_service_factory.h"
19 #include "chrome/browser/sync/profile_sync_service_mock.h"
20 #include "chrome/browser/ui/browser_list.h"
21 #include "chrome/browser/ui/browser_window.h"
22 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
23 #include "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
24 #include "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
25 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
26 #include "chrome/browser/ui/host_desktop.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/test/base/testing_profile.h"
29 #include "components/signin/core/browser/fake_auth_status_provider.h"
30 #include "components/signin/core/browser/profile_oauth2_token_service.h"
31 #include "components/signin/core/browser/signin_error_controller.h"
32 #include "components/signin/core/browser/signin_manager.h"
33 #include "content/public/browser/notification_service.h"
34 #include "content/public/test/test_utils.h"
35 #include "grit/chromium_strings.h"
36 #include "grit/generated_resources.h"
37 #include "testing/gmock/include/gmock/gmock.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/l10n/l10n_util_mac.h"
41 using ::testing::Return;
43 @interface BrowserWindowController (JustForTesting)
44 // Already defined in BWC.
45 - (void)saveWindowPositionIfNeeded;
46 - (void)layoutSubviews;
49 @interface BrowserWindowController (ExposedForTesting)
50 // Implementations are below.
51 - (NSView*)infoBarContainerView;
52 - (NSView*)toolbarView;
53 - (NSView*)bookmarkView;
54 - (BOOL)bookmarkBarVisible;
57 @implementation BrowserWindowController (ExposedForTesting)
58 - (NSView*)infoBarContainerView {
59 return [infoBarContainerController_ view];
62 - (NSView*)toolbarView {
63 return [toolbarController_ view];
66 - (NSView*)bookmarkView {
67 return [bookmarkBarController_ view];
70 - (NSView*)findBarView {
71 return [findBarCocoaController_ view];
74 - (BOOL)bookmarkBarVisible {
75 return [bookmarkBarController_ isVisible];
79 class BrowserWindowControllerTest : public CocoaProfileTest {
81 virtual void SetUp() {
82 CocoaProfileTest::SetUp();
83 ASSERT_TRUE(browser());
85 controller_ = [[BrowserWindowController alloc] initWithBrowser:browser()
89 virtual void TearDown() {
91 CocoaProfileTest::TearDown();
95 BrowserWindowController* controller_;
98 TEST_F(BrowserWindowControllerTest, TestSaveWindowPosition) {
99 PrefService* prefs = profile()->GetPrefs();
100 ASSERT_TRUE(prefs != NULL);
102 // Check to make sure there is no existing pref for window placement.
103 const base::DictionaryValue* browser_window_placement =
104 prefs->GetDictionary(prefs::kBrowserWindowPlacement);
105 ASSERT_TRUE(browser_window_placement);
106 EXPECT_TRUE(browser_window_placement->empty());
108 // Ask the window to save its position, then check that a preference
110 BrowserList::SetLastActive(browser());
111 [controller_ saveWindowPositionIfNeeded];
112 browser_window_placement =
113 prefs->GetDictionary(prefs::kBrowserWindowPlacement);
114 ASSERT_TRUE(browser_window_placement);
115 EXPECT_FALSE(browser_window_placement->empty());
118 TEST_F(BrowserWindowControllerTest, TestFullScreenWindow) {
119 // Confirm that |-createFullscreenWindow| doesn't return nil.
120 // See BrowserWindowFullScreenControllerTest for more fullscreen tests.
121 EXPECT_TRUE([controller_ createFullscreenWindow]);
124 TEST_F(BrowserWindowControllerTest, TestNormal) {
125 // Force the bookmark bar to be shown.
126 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
127 [controller_ browserWindow]->BookmarkBarStateChanged(
128 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
130 // Make sure a normal BrowserWindowController is, uh, normal.
131 EXPECT_TRUE([controller_ isTabbedWindow]);
132 EXPECT_TRUE([controller_ hasTabStrip]);
133 EXPECT_FALSE([controller_ hasTitleBar]);
134 EXPECT_TRUE([controller_ isBookmarkBarVisible]);
136 // And make sure a controller for a pop-up window is not normal.
137 // popup_browser will be owned by its window.
138 Browser* popup_browser(new Browser(
139 Browser::CreateParams(Browser::TYPE_POPUP, profile(),
140 chrome::GetActiveDesktop())));
141 NSWindow *cocoaWindow = popup_browser->window()->GetNativeWindow();
142 BrowserWindowController* controller =
143 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
144 ASSERT_TRUE([controller isKindOfClass:[BrowserWindowController class]]);
145 EXPECT_FALSE([controller isTabbedWindow]);
146 EXPECT_FALSE([controller hasTabStrip]);
147 EXPECT_TRUE([controller hasTitleBar]);
148 EXPECT_FALSE([controller isBookmarkBarVisible]);
152 TEST_F(BrowserWindowControllerTest, TestSetBounds) {
153 // Create a normal browser with bounds smaller than the minimum.
154 Browser::CreateParams params(Browser::TYPE_TABBED, profile(),
155 chrome::GetActiveDesktop());
156 params.initial_bounds = gfx::Rect(0, 0, 50, 50);
157 Browser* browser = new Browser(params);
158 NSWindow *cocoaWindow = browser->window()->GetNativeWindow();
159 BrowserWindowController* controller =
160 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
162 ASSERT_TRUE([controller isTabbedWindow]);
163 BrowserWindow* browser_window = [controller browserWindow];
164 EXPECT_EQ(browser_window, browser->window());
165 gfx::Rect bounds = browser_window->GetBounds();
166 EXPECT_EQ(400, bounds.width());
167 EXPECT_EQ(272, bounds.height());
169 // Try to set the bounds smaller than the minimum.
170 browser_window->SetBounds(gfx::Rect(0, 0, 50, 50));
171 bounds = browser_window->GetBounds();
172 EXPECT_EQ(400, bounds.width());
173 EXPECT_EQ(272, bounds.height());
178 TEST_F(BrowserWindowControllerTest, TestSetBoundsPopup) {
179 // Create a popup with bounds smaller than the minimum.
180 Browser::CreateParams params(Browser::TYPE_POPUP, profile(),
181 chrome::GetActiveDesktop());
182 params.initial_bounds = gfx::Rect(0, 0, 50, 50);
183 Browser* browser = new Browser(params);
184 NSWindow *cocoaWindow = browser->window()->GetNativeWindow();
185 BrowserWindowController* controller =
186 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
188 ASSERT_FALSE([controller isTabbedWindow]);
189 BrowserWindow* browser_window = [controller browserWindow];
190 EXPECT_EQ(browser_window, browser->window());
191 gfx::Rect bounds = browser_window->GetBounds();
192 EXPECT_EQ(100, bounds.width());
193 EXPECT_EQ(122, bounds.height());
195 // Try to set the bounds smaller than the minimum.
196 browser_window->SetBounds(gfx::Rect(0, 0, 50, 50));
197 bounds = browser_window->GetBounds();
198 EXPECT_EQ(100, bounds.width());
199 EXPECT_EQ(122, bounds.height());
204 TEST_F(BrowserWindowControllerTest, TestTheme) {
205 [controller_ userChangedTheme];
208 TEST_F(BrowserWindowControllerTest, BookmarkBarControllerIndirection) {
209 EXPECT_FALSE([controller_ isBookmarkBarVisible]);
211 // Explicitly show the bar. Can't use chrome::ToggleBookmarkBarWhenVisible()
212 // because of the notification issues.
213 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
215 [controller_ browserWindow]->BookmarkBarStateChanged(
216 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
217 EXPECT_TRUE([controller_ isBookmarkBarVisible]);
221 // TODO(jrg): This crashes trying to create the BookmarkBarController, adding
222 // an observer to the BookmarkModel.
223 TEST_F(BrowserWindowControllerTest, TestIncognitoWidthSpace) {
224 scoped_ptr<TestingProfile> incognito_profile(new TestingProfile());
225 incognito_profile->set_off_the_record(true);
226 scoped_ptr<Browser> browser(
227 new Browser(Browser::CreateParams(incognito_profile.get(),
228 chrome::GetActiveDesktop()));
229 controller_.reset([[BrowserWindowController alloc]
230 initWithBrowser:browser.get()
233 NSRect tabFrame = [[controller_ tabStripView] frame];
234 [controller_ installIncognitoBadge];
235 NSRect newTabFrame = [[controller_ tabStripView] frame];
236 EXPECT_GT(tabFrame.size.width, newTabFrame.size.width);
238 controller_.release();
244 // Verifies that the toolbar, infobar, tab content area, and download shelf
245 // completely fill the area under the tabstrip.
246 void CheckViewPositions(BrowserWindowController* controller) {
247 NSRect contentView = [[[controller window] contentView] bounds];
248 NSRect tabstrip = [[controller tabStripView] frame];
249 NSRect toolbar = [[controller toolbarView] frame];
250 NSRect infobar = [[controller infoBarContainerView] frame];
251 NSRect contentArea = [[controller tabContentArea] frame];
252 NSRect download = [[[controller downloadShelf] view] frame];
254 EXPECT_EQ(NSMinY(contentView), NSMinY(download));
255 EXPECT_EQ(NSMaxY(download), NSMinY(contentArea));
256 EXPECT_EQ(NSMaxY(contentArea), NSMinY(infobar));
258 // Bookmark bar frame is random memory when hidden.
259 if ([controller bookmarkBarVisible]) {
260 NSRect bookmark = [[controller bookmarkView] frame];
261 EXPECT_EQ(NSMaxY(infobar), NSMinY(bookmark));
262 EXPECT_EQ(NSMaxY(bookmark), NSMinY(toolbar));
263 EXPECT_FALSE([[controller bookmarkView] isHidden]);
265 EXPECT_EQ(NSMaxY(infobar), NSMinY(toolbar));
266 EXPECT_TRUE([[controller bookmarkView] isHidden]);
269 // Toolbar should start immediately under the tabstrip, but the tabstrip is
270 // not necessarily fixed with respect to the content view.
271 EXPECT_EQ(NSMinY(tabstrip), NSMaxY(toolbar));
276 TEST_F(BrowserWindowControllerTest, TestAdjustWindowHeight) {
277 NSWindow* window = [controller_ window];
278 NSRect workarea = [[window screen] visibleFrame];
280 // Place the window well above the bottom of the screen and try to adjust its
281 // height. It should change appropriately (and only downwards). Then get it to
282 // shrink by the same amount; it should return to its original state.
283 NSRect initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 100,
285 [window setFrame:initialFrame display:YES];
286 [controller_ resetWindowGrowthState];
287 [controller_ adjustWindowHeightBy:40];
288 NSRect finalFrame = [window frame];
289 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
290 EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
291 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
292 [controller_ adjustWindowHeightBy:-40];
293 finalFrame = [window frame];
294 EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
295 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
297 // Place the window at the bottom of the screen and try again. Its height
298 // should still change, but it should not grow down below the work area; it
299 // should instead move upwards. Then shrink it and make sure it goes back to
301 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
302 [window setFrame:initialFrame display:YES];
303 [controller_ resetWindowGrowthState];
304 [controller_ adjustWindowHeightBy:40];
305 finalFrame = [window frame];
306 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
307 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
308 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
309 [controller_ adjustWindowHeightBy:-40];
310 finalFrame = [window frame];
311 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
312 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
314 // Put the window slightly offscreen and try again. The height should not
316 initialFrame = NSMakeRect(workarea.origin.x - 10, 0, 200, 200);
317 [window setFrame:initialFrame display:YES];
318 [controller_ resetWindowGrowthState];
319 [controller_ adjustWindowHeightBy:40];
320 EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
321 [controller_ adjustWindowHeightBy:-40];
322 EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
324 // Make the window the same size as the workarea. Resizing both larger and
325 // smaller should have no effect.
326 [window setFrame:workarea display:YES];
327 [controller_ resetWindowGrowthState];
328 [controller_ adjustWindowHeightBy:40];
329 EXPECT_TRUE(NSEqualRects([window frame], workarea));
330 [controller_ adjustWindowHeightBy:-40];
331 EXPECT_TRUE(NSEqualRects([window frame], workarea));
333 // Make the window smaller than the workarea and place it near the bottom of
334 // the workarea. The window should grow down until it hits the bottom and
335 // then continue to grow up. Then shrink it, and it should return to where it
337 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 5,
339 [window setFrame:initialFrame display:YES];
340 [controller_ resetWindowGrowthState];
341 [controller_ adjustWindowHeightBy:40];
342 finalFrame = [window frame];
343 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
344 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
345 [controller_ adjustWindowHeightBy:-40];
346 finalFrame = [window frame];
347 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
348 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
350 // Inset the window slightly from the workarea. It should not grow to be
351 // larger than the workarea. Shrink it; it should return to where it started.
352 initialFrame = NSInsetRect(workarea, 0, 5);
353 [window setFrame:initialFrame display:YES];
354 [controller_ resetWindowGrowthState];
355 [controller_ adjustWindowHeightBy:40];
356 finalFrame = [window frame];
357 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
358 EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
359 [controller_ adjustWindowHeightBy:-40];
360 finalFrame = [window frame];
361 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
362 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
364 // Place the window at the bottom of the screen and grow; it should grow
365 // upwards. Move the window off the bottom, then shrink. It should then shrink
367 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
368 [window setFrame:initialFrame display:YES];
369 [controller_ resetWindowGrowthState];
370 [controller_ adjustWindowHeightBy:40];
371 finalFrame = [window frame];
372 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
373 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
374 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
375 NSPoint oldOrigin = initialFrame.origin;
376 NSPoint newOrigin = NSMakePoint(oldOrigin.x, oldOrigin.y + 10);
377 [window setFrameOrigin:newOrigin];
378 initialFrame = [window frame];
379 EXPECT_FLOAT_EQ(NSMinY(initialFrame), oldOrigin.y + 10);
380 [controller_ adjustWindowHeightBy:-40];
381 finalFrame = [window frame];
382 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame) + 40);
383 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) - 40);
385 // Do the "inset" test above, but using multiple calls to
386 // |-adjustWindowHeightBy|; the result should be the same.
387 initialFrame = NSInsetRect(workarea, 0, 5);
388 [window setFrame:initialFrame display:YES];
389 [controller_ resetWindowGrowthState];
390 for (int i = 0; i < 8; i++)
391 [controller_ adjustWindowHeightBy:5];
392 finalFrame = [window frame];
393 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
394 EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
395 for (int i = 0; i < 8; i++)
396 [controller_ adjustWindowHeightBy:-5];
397 finalFrame = [window frame];
398 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
399 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
402 // Test to make sure resizing and relaying-out subviews works correctly.
403 TEST_F(BrowserWindowControllerTest, TestResizeViews) {
404 TabStripView* tabstrip = [controller_ tabStripView];
405 NSView* contentView = [[tabstrip window] contentView];
406 NSView* toolbar = [controller_ toolbarView];
407 NSView* infobar = [controller_ infoBarContainerView];
409 // We need to muck with the views a bit to put us in a consistent state before
410 // we start resizing. In particular, we need to move the tab strip to be
411 // immediately above the content area, since we layout views to be directly
412 // under the tab strip.
413 NSRect tabstripFrame = [tabstrip frame];
414 tabstripFrame.origin.y = NSMaxY([contentView frame]);
415 [tabstrip setFrame:tabstripFrame];
417 // The download shelf is created lazily. Force-create it and set its initial
419 NSView* download = [[controller_ downloadShelf] view];
420 NSRect downloadFrame = [download frame];
421 downloadFrame.size.height = 0;
422 [download setFrame:downloadFrame];
424 // Force a layout and check each view's frame.
425 [controller_ layoutSubviews];
426 CheckViewPositions(controller_);
428 // Expand the infobar to 60px and recheck
429 [controller_ resizeView:infobar newHeight:60];
430 CheckViewPositions(controller_);
432 // Expand the toolbar to 64px and recheck
433 [controller_ resizeView:toolbar newHeight:64];
434 CheckViewPositions(controller_);
436 // Add a 30px download shelf and recheck
437 [controller_ resizeView:download newHeight:30];
438 CheckViewPositions(controller_);
440 // Shrink the infobar to 0px and toolbar to 39px and recheck
441 [controller_ resizeView:infobar newHeight:0];
442 [controller_ resizeView:toolbar newHeight:39];
443 CheckViewPositions(controller_);
446 TEST_F(BrowserWindowControllerTest, TestResizeViewsWithBookmarkBar) {
447 // Force a display of the bookmark bar.
448 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
449 [controller_ browserWindow]->BookmarkBarStateChanged(
450 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
452 TabStripView* tabstrip = [controller_ tabStripView];
453 NSView* contentView = [[tabstrip window] contentView];
454 NSView* toolbar = [controller_ toolbarView];
455 NSView* bookmark = [controller_ bookmarkView];
456 NSView* infobar = [controller_ infoBarContainerView];
458 // We need to muck with the views a bit to put us in a consistent state before
459 // we start resizing. In particular, we need to move the tab strip to be
460 // immediately above the content area, since we layout views to be directly
461 // under the tab strip.
462 NSRect tabstripFrame = [tabstrip frame];
463 tabstripFrame.origin.y = NSMaxY([contentView frame]);
464 [tabstrip setFrame:tabstripFrame];
466 // The download shelf is created lazily. Force-create it and set its initial
468 NSView* download = [[controller_ downloadShelf] view];
469 NSRect downloadFrame = [download frame];
470 downloadFrame.size.height = 0;
471 [download setFrame:downloadFrame];
473 // Force a layout and check each view's frame.
474 [controller_ layoutSubviews];
475 CheckViewPositions(controller_);
477 // Add the bookmark bar and recheck.
478 [controller_ resizeView:bookmark newHeight:40];
479 CheckViewPositions(controller_);
481 // Expand the infobar to 60px and recheck
482 [controller_ resizeView:infobar newHeight:60];
483 CheckViewPositions(controller_);
485 // Expand the toolbar to 64px and recheck
486 [controller_ resizeView:toolbar newHeight:64];
487 CheckViewPositions(controller_);
489 // Add a 30px download shelf and recheck
490 [controller_ resizeView:download newHeight:30];
491 CheckViewPositions(controller_);
493 // Remove the bookmark bar and recheck
494 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, false);
495 [controller_ resizeView:bookmark newHeight:0];
496 CheckViewPositions(controller_);
498 // Shrink the infobar to 0px and toolbar to 39px and recheck
499 [controller_ resizeView:infobar newHeight:0];
500 [controller_ resizeView:toolbar newHeight:39];
501 CheckViewPositions(controller_);
504 // Make sure, by default, the bookmark bar and the toolbar are the same width.
505 TEST_F(BrowserWindowControllerTest, BookmarkBarIsSameWidth) {
506 // Set the pref to the bookmark bar is visible when the toolbar is
508 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
510 // Make sure the bookmark bar is the same width as the toolbar
511 NSView* bookmarkBarView = [controller_ bookmarkView];
512 NSView* toolbarView = [controller_ toolbarView];
513 EXPECT_EQ([toolbarView frame].size.width,
514 [bookmarkBarView frame].size.width);
517 TEST_F(BrowserWindowControllerTest, TestTopRightForBubble) {
518 // The bookmark bubble must be attached to a lit and visible star.
519 [controller_ setStarredState:YES];
520 NSPoint p = [controller_ bookmarkBubblePoint];
521 NSRect all = [[controller_ window] frame];
523 // As a sanity check make sure the point is vaguely in the top right
525 EXPECT_GT(p.y, all.origin.y + (all.size.height/2));
526 EXPECT_GT(p.x, all.origin.x + (all.size.width/2));
529 // By the "zoom frame", we mean what Apple calls the "standard frame".
530 TEST_F(BrowserWindowControllerTest, TestZoomFrame) {
531 NSWindow* window = [controller_ window];
533 NSRect screenFrame = [[window screen] visibleFrame];
534 ASSERT_FALSE(NSIsEmptyRect(screenFrame));
536 // Minimum zoomed width is the larger of 60% of available horizontal space or
537 // 60% of available vertical space, subject to available horizontal space.
538 CGFloat minZoomWidth =
539 std::min(std::max((CGFloat)0.6 * screenFrame.size.width,
540 (CGFloat)0.6 * screenFrame.size.height),
541 screenFrame.size.width);
543 // |testFrame| is the size of the window we start out with, and |zoomFrame| is
544 // the one returned by |-windowWillUseStandardFrame:defaultFrame:|.
548 // 1. Test a case where it zooms the window both horizontally and vertically,
549 // and only moves it vertically. "+ 32", etc. are just arbitrary constants
550 // used to check that the window is moved properly and not just to the origin;
551 // they should be small enough to not shove windows off the screen.
552 testFrame.size.width = 0.5 * minZoomWidth;
553 testFrame.size.height = 0.5 * screenFrame.size.height;
554 testFrame.origin.x = screenFrame.origin.x + 32; // See above.
555 testFrame.origin.y = screenFrame.origin.y + 23;
556 [window setFrame:testFrame display:NO];
557 zoomFrame = [controller_ windowWillUseStandardFrame:window
558 defaultFrame:screenFrame];
559 EXPECT_LE(minZoomWidth, zoomFrame.size.width);
560 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
561 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
562 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
564 // 2. Test a case where it zooms the window only horizontally, and only moves
566 testFrame.size.width = 0.5 * minZoomWidth;
567 testFrame.size.height = screenFrame.size.height;
568 testFrame.origin.x = screenFrame.origin.x + screenFrame.size.width -
569 testFrame.size.width;
570 testFrame.origin.y = screenFrame.origin.y;
571 [window setFrame:testFrame display:NO];
572 zoomFrame = [controller_ windowWillUseStandardFrame:window
573 defaultFrame:screenFrame];
574 EXPECT_LE(minZoomWidth, zoomFrame.size.width);
575 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
576 EXPECT_EQ(screenFrame.origin.x + screenFrame.size.width -
577 zoomFrame.size.width, zoomFrame.origin.x);
578 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
580 // 3. Test a case where it zooms the window only vertically, and only moves it
582 testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
583 screenFrame.size.width);
584 testFrame.size.height = 0.3 * screenFrame.size.height;
585 testFrame.origin.x = screenFrame.origin.x + 32; // See above (in 1.).
586 testFrame.origin.y = screenFrame.origin.y + 123;
587 [window setFrame:testFrame display:NO];
588 zoomFrame = [controller_ windowWillUseStandardFrame:window
589 defaultFrame:screenFrame];
590 // Use the actual width of the window frame, since it's subject to rounding.
591 EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
592 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
593 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
594 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
596 // 4. Test a case where zooming should do nothing (i.e., we're already at a
598 testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
599 screenFrame.size.width);
600 testFrame.size.height = screenFrame.size.height;
601 testFrame.origin.x = screenFrame.origin.x + 32; // See above (in 1.).
602 testFrame.origin.y = screenFrame.origin.y;
603 [window setFrame:testFrame display:NO];
604 zoomFrame = [controller_ windowWillUseStandardFrame:window
605 defaultFrame:screenFrame];
606 // Use the actual width of the window frame, since it's subject to rounding.
607 EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
608 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
609 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
610 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
613 TEST_F(BrowserWindowControllerTest, TestFindBarOnTop) {
614 FindBarBridge bridge(NULL);
615 [controller_ addFindBar:bridge.find_bar_cocoa_controller()];
617 // Test that the Z-order of the find bar is on top of everything.
618 NSArray* subviews = [[[controller_ window] contentView] subviews];
619 NSUInteger findBar_index =
620 [subviews indexOfObject:[controller_ findBarView]];
621 EXPECT_NE(NSNotFound, findBar_index);
622 NSUInteger toolbar_index =
623 [subviews indexOfObject:[controller_ toolbarView]];
624 EXPECT_NE(NSNotFound, toolbar_index);
625 NSUInteger bookmark_index =
626 [subviews indexOfObject:[controller_ bookmarkView]];
627 EXPECT_NE(NSNotFound, bookmark_index);
629 EXPECT_GT(findBar_index, toolbar_index);
630 EXPECT_GT(findBar_index, bookmark_index);
633 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemNoErrors) {
634 base::scoped_nsobject<NSMenuItem> syncMenuItem(
635 [[NSMenuItem alloc] initWithTitle:@""
636 action:@selector(commandDispatch)
638 [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
640 NSString* startSignin =
641 l10n_util::GetNSStringFWithFixup(
642 IDS_SYNC_MENU_PRE_SYNCED_LABEL,
643 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
645 // Make sure shouldShow parameter is obeyed, and we get the default
646 // label if not signed in.
647 [BrowserWindowController updateSigninItem:syncMenuItem
649 currentProfile:profile()];
651 EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
652 EXPECT_FALSE([syncMenuItem isHidden]);
654 [BrowserWindowController updateSigninItem:syncMenuItem
656 currentProfile:profile()];
657 EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
658 EXPECT_TRUE([syncMenuItem isHidden]);
661 std::string username = "foo@example.com";
662 NSString* alreadySignedIn =
663 l10n_util::GetNSStringFWithFixup(IDS_SYNC_MENU_SYNCED_LABEL,
664 base::UTF8ToUTF16(username));
665 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
666 signin->SetAuthenticatedUsername(username);
667 ProfileSyncService* sync =
668 ProfileSyncServiceFactory::GetForProfile(profile());
669 sync->SetSyncSetupCompleted();
670 [BrowserWindowController updateSigninItem:syncMenuItem
672 currentProfile:profile()];
673 EXPECT_TRUE([[syncMenuItem title] isEqualTo:alreadySignedIn]);
674 EXPECT_FALSE([syncMenuItem isHidden]);
677 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemAuthError) {
678 base::scoped_nsobject<NSMenuItem> syncMenuItem(
679 [[NSMenuItem alloc] initWithTitle:@""
680 action:@selector(commandDispatch)
682 [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
685 std::string username = "foo@example.com";
686 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
687 signin->SetAuthenticatedUsername(username);
688 ProfileSyncService* sync =
689 ProfileSyncServiceFactory::GetForProfile(profile());
690 sync->SetSyncSetupCompleted();
691 // Force an auth error.
692 FakeAuthStatusProvider provider(
693 ProfileOAuth2TokenServiceFactory::GetForProfile(profile())->
694 signin_error_controller());
695 GoogleServiceAuthError error(
696 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
697 provider.SetAuthError("user@gmail.com", "user@gmail.com", error);
698 [BrowserWindowController updateSigninItem:syncMenuItem
700 currentProfile:profile()];
701 NSString* authError =
702 l10n_util::GetNSStringWithFixup(IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM);
703 EXPECT_TRUE([[syncMenuItem title] isEqualTo:authError]);
704 EXPECT_FALSE([syncMenuItem isHidden]);
708 // If there's a separator after the signin menu item, make sure it is hidden/
709 // shown when the signin menu item is.
710 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithSeparator) {
711 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
712 NSMenuItem* signinMenuItem =
713 [menu addItemWithTitle:@""
714 action:@selector(commandDispatch)
716 [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
717 NSMenuItem* followingSeparator = [NSMenuItem separatorItem];
718 [menu addItem:followingSeparator];
719 [signinMenuItem setHidden:NO];
720 [followingSeparator setHidden:NO];
722 [BrowserWindowController updateSigninItem:signinMenuItem
724 currentProfile:profile()];
726 EXPECT_FALSE([followingSeparator isEnabled]);
727 EXPECT_TRUE([signinMenuItem isHidden]);
728 EXPECT_TRUE([followingSeparator isHidden]);
730 [BrowserWindowController updateSigninItem:signinMenuItem
732 currentProfile:profile()];
734 EXPECT_FALSE([followingSeparator isEnabled]);
735 EXPECT_FALSE([signinMenuItem isHidden]);
736 EXPECT_FALSE([followingSeparator isHidden]);
739 // If there's a non-separator item after the signin menu item, it should not
740 // change state when the signin menu item is hidden/shown.
741 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithNonSeparator) {
742 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
743 NSMenuItem* signinMenuItem =
744 [menu addItemWithTitle:@""
745 action:@selector(commandDispatch)
747 [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
748 NSMenuItem* followingNonSeparator =
749 [menu addItemWithTitle:@""
750 action:@selector(commandDispatch)
752 [signinMenuItem setHidden:NO];
753 [followingNonSeparator setHidden:NO];
755 [BrowserWindowController updateSigninItem:signinMenuItem
757 currentProfile:profile()];
759 EXPECT_TRUE([followingNonSeparator isEnabled]);
760 EXPECT_TRUE([signinMenuItem isHidden]);
761 EXPECT_FALSE([followingNonSeparator isHidden]);
763 [followingNonSeparator setHidden:YES];
764 [BrowserWindowController updateSigninItem:signinMenuItem
766 currentProfile:profile()];
768 EXPECT_TRUE([followingNonSeparator isEnabled]);
769 EXPECT_FALSE([signinMenuItem isHidden]);
770 EXPECT_TRUE([followingNonSeparator isHidden]);
773 // Verify that hit testing works correctly when the bookmark bar overlaps
775 TEST_F(BrowserWindowControllerTest, BookmarkBarHitTest) {
776 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
777 [controller_ browserWindow]->BookmarkBarStateChanged(
778 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
780 NSView* bookmarkView = [controller_ bookmarkView];
781 NSView* contentView = [[controller_ window] contentView];
782 NSPoint point = [bookmarkView convertPoint:NSMakePoint(1, 1)
783 toView:[contentView superview]];
785 EXPECT_TRUE([[contentView hitTest:point] isDescendantOf:bookmarkView]);
788 @interface BrowserWindowControllerFakeFullscreen : BrowserWindowController {
790 // We release the window ourselves, so we don't have to rely on the unittest
792 base::scoped_nsobject<NSWindow> testFullscreenWindow_;
796 class BrowserWindowFullScreenControllerTest : public CocoaProfileTest {
798 virtual void SetUp() {
799 CocoaProfileTest::SetUp();
800 ASSERT_TRUE(browser());
803 [[BrowserWindowControllerFakeFullscreen alloc] initWithBrowser:browser()
807 virtual void TearDown() {
809 CocoaProfileTest::TearDown();
813 BrowserWindowController* controller_;
816 // Check if the window is front most or if one of its child windows (such
817 // as a status bubble) is front most.
818 static bool IsFrontWindow(NSWindow *window) {
819 NSWindow* frontmostWindow = [[NSApp orderedWindows] objectAtIndex:0];
820 return [frontmostWindow isEqual:window] ||
821 [[frontmostWindow parentWindow] isEqual:window];
824 void WaitForFullScreenTransition() {
825 content::WindowedNotificationObserver observer(
826 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
827 content::NotificationService::AllSources());
831 TEST_F(BrowserWindowFullScreenControllerTest, TestFullscreen) {
832 [controller_ showWindow:nil];
833 EXPECT_FALSE([controller_ isFullscreen]);
835 [controller_ enterFullscreen];
836 WaitForFullScreenTransition();
837 EXPECT_TRUE([controller_ isFullscreen]);
839 [controller_ exitFullscreen];
840 WaitForFullScreenTransition();
841 EXPECT_FALSE([controller_ isFullscreen]);
844 // If this test fails, it is usually a sign that the bots have some sort of
845 // problem (such as a modal dialog up). This tests is a very useful canary, so
846 // please do not mark it as flaky without first verifying that there are no bot
848 TEST_F(BrowserWindowFullScreenControllerTest, TestActivate) {
849 [controller_ showWindow:nil];
851 EXPECT_FALSE([controller_ isFullscreen]);
853 [controller_ activate];
854 EXPECT_TRUE(IsFrontWindow([controller_ window]));
856 [controller_ enterFullscreen];
857 WaitForFullScreenTransition();
858 [controller_ activate];
860 // No fullscreen window on 10.7+.
861 if (base::mac::IsOSSnowLeopard())
862 EXPECT_TRUE(IsFrontWindow([controller_ createFullscreenWindow]));
864 // We have to cleanup after ourselves by unfullscreening.
865 [controller_ exitFullscreen];
866 WaitForFullScreenTransition();
869 @implementation BrowserWindowControllerFakeFullscreen
870 // Override |-createFullscreenWindow| to return a dummy window. This isn't
871 // needed to pass the test, but because the dummy window is only 100x100, it
872 // prevents the real fullscreen window from flashing up and taking over the
873 // whole screen. We have to return an actual window because |-layoutSubviews|
874 // looks at the window's frame.
875 - (NSWindow*)createFullscreenWindow {
876 if (testFullscreenWindow_.get())
877 return testFullscreenWindow_.get();
879 testFullscreenWindow_.reset(
880 [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,400,400)
881 styleMask:NSBorderlessWindowMask
882 backing:NSBackingStoreBuffered
884 return testFullscreenWindow_.get();
888 /* TODO(???): test other methods of BrowserWindowController */