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_auth_status_provider.h"
15 #include "chrome/browser/signin/fake_signin_manager.h"
16 #include "chrome/browser/signin/signin_global_error.h"
17 #include "chrome/browser/signin/signin_manager.h"
18 #include "chrome/browser/signin/signin_manager_factory.h"
19 #include "chrome/browser/sync/profile_sync_service.h"
20 #include "chrome/browser/sync/profile_sync_service_factory.h"
21 #include "chrome/browser/sync/profile_sync_service_mock.h"
22 #include "chrome/browser/sync/sync_global_error.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
26 #include "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
27 #include "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
28 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
29 #include "chrome/browser/ui/host_desktop.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/test/base/testing_profile.h"
32 #include "content/public/browser/notification_service.h"
33 #include "content/public/test/test_utils.h"
34 #include "grit/chromium_strings.h"
35 #include "grit/generated_resources.h"
36 #include "testing/gmock/include/gmock/gmock.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/l10n/l10n_util_mac.h"
40 using ::testing::Return;
42 @interface BrowserWindowController (JustForTesting)
43 // Already defined in BWC.
44 - (void)saveWindowPositionIfNeeded;
45 - (void)layoutSubviews;
48 @interface BrowserWindowController (ExposedForTesting)
49 // Implementations are below.
50 - (NSView*)infoBarContainerView;
51 - (NSView*)toolbarView;
52 - (NSView*)bookmarkView;
53 - (BOOL)bookmarkBarVisible;
56 @implementation BrowserWindowController (ExposedForTesting)
57 - (NSView*)infoBarContainerView {
58 return [infoBarContainerController_ view];
61 - (NSView*)toolbarView {
62 return [toolbarController_ view];
65 - (NSView*)bookmarkView {
66 return [bookmarkBarController_ view];
69 - (NSView*)findBarView {
70 return [findBarCocoaController_ view];
73 - (BOOL)bookmarkBarVisible {
74 return [bookmarkBarController_ isVisible];
78 class BrowserWindowControllerTest : public CocoaProfileTest {
80 virtual void SetUp() {
81 CocoaProfileTest::SetUp();
82 ASSERT_TRUE(browser());
84 controller_ = [[BrowserWindowController alloc] initWithBrowser:browser()
88 virtual void TearDown() {
90 CocoaProfileTest::TearDown();
94 BrowserWindowController* controller_;
97 TEST_F(BrowserWindowControllerTest, TestSaveWindowPosition) {
98 PrefService* prefs = profile()->GetPrefs();
99 ASSERT_TRUE(prefs != NULL);
101 // Check to make sure there is no existing pref for window placement.
102 const base::DictionaryValue* browser_window_placement =
103 prefs->GetDictionary(prefs::kBrowserWindowPlacement);
104 ASSERT_TRUE(browser_window_placement);
105 EXPECT_TRUE(browser_window_placement->empty());
107 // Ask the window to save its position, then check that a preference
109 BrowserList::SetLastActive(browser());
110 [controller_ saveWindowPositionIfNeeded];
111 browser_window_placement =
112 prefs->GetDictionary(prefs::kBrowserWindowPlacement);
113 ASSERT_TRUE(browser_window_placement);
114 EXPECT_FALSE(browser_window_placement->empty());
117 TEST_F(BrowserWindowControllerTest, TestFullScreenWindow) {
118 // Confirm that |-createFullscreenWindow| doesn't return nil.
119 // See BrowserWindowFullScreenControllerTest for more fullscreen tests.
120 EXPECT_TRUE([controller_ createFullscreenWindow]);
123 TEST_F(BrowserWindowControllerTest, TestNormal) {
124 // Force the bookmark bar to be shown.
125 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
126 [controller_ browserWindow]->BookmarkBarStateChanged(
127 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
129 // Make sure a normal BrowserWindowController is, uh, normal.
130 EXPECT_TRUE([controller_ isTabbedWindow]);
131 EXPECT_TRUE([controller_ hasTabStrip]);
132 EXPECT_FALSE([controller_ hasTitleBar]);
133 EXPECT_TRUE([controller_ isBookmarkBarVisible]);
135 // And make sure a controller for a pop-up window is not normal.
136 // popup_browser will be owned by its window.
137 Browser* popup_browser(new Browser(
138 Browser::CreateParams(Browser::TYPE_POPUP, profile(),
139 chrome::GetActiveDesktop())));
140 NSWindow *cocoaWindow = popup_browser->window()->GetNativeWindow();
141 BrowserWindowController* controller =
142 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
143 ASSERT_TRUE([controller isKindOfClass:[BrowserWindowController class]]);
144 EXPECT_FALSE([controller isTabbedWindow]);
145 EXPECT_FALSE([controller hasTabStrip]);
146 EXPECT_TRUE([controller hasTitleBar]);
147 EXPECT_FALSE([controller isBookmarkBarVisible]);
151 TEST_F(BrowserWindowControllerTest, TestSetBounds) {
152 // Create a normal browser with bounds smaller than the minimum.
153 Browser::CreateParams params(Browser::TYPE_TABBED, profile(),
154 chrome::GetActiveDesktop());
155 params.initial_bounds = gfx::Rect(0, 0, 50, 50);
156 Browser* browser = new Browser(params);
157 NSWindow *cocoaWindow = browser->window()->GetNativeWindow();
158 BrowserWindowController* controller =
159 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
161 ASSERT_TRUE([controller isTabbedWindow]);
162 BrowserWindow* browser_window = [controller browserWindow];
163 EXPECT_EQ(browser_window, browser->window());
164 gfx::Rect bounds = browser_window->GetBounds();
165 EXPECT_EQ(400, bounds.width());
166 EXPECT_EQ(272, bounds.height());
168 // Try to set the bounds smaller than the minimum.
169 browser_window->SetBounds(gfx::Rect(0, 0, 50, 50));
170 bounds = browser_window->GetBounds();
171 EXPECT_EQ(400, bounds.width());
172 EXPECT_EQ(272, bounds.height());
177 TEST_F(BrowserWindowControllerTest, TestSetBoundsPopup) {
178 // Create a popup with bounds smaller than the minimum.
179 Browser::CreateParams params(Browser::TYPE_POPUP, profile(),
180 chrome::GetActiveDesktop());
181 params.initial_bounds = gfx::Rect(0, 0, 50, 50);
182 Browser* browser = new Browser(params);
183 NSWindow *cocoaWindow = browser->window()->GetNativeWindow();
184 BrowserWindowController* controller =
185 static_cast<BrowserWindowController*>([cocoaWindow windowController]);
187 ASSERT_FALSE([controller isTabbedWindow]);
188 BrowserWindow* browser_window = [controller browserWindow];
189 EXPECT_EQ(browser_window, browser->window());
190 gfx::Rect bounds = browser_window->GetBounds();
191 EXPECT_EQ(100, bounds.width());
192 EXPECT_EQ(122, bounds.height());
194 // Try to set the bounds smaller than the minimum.
195 browser_window->SetBounds(gfx::Rect(0, 0, 50, 50));
196 bounds = browser_window->GetBounds();
197 EXPECT_EQ(100, bounds.width());
198 EXPECT_EQ(122, bounds.height());
203 TEST_F(BrowserWindowControllerTest, TestTheme) {
204 [controller_ userChangedTheme];
207 TEST_F(BrowserWindowControllerTest, BookmarkBarControllerIndirection) {
208 EXPECT_FALSE([controller_ isBookmarkBarVisible]);
210 // Explicitly show the bar. Can't use chrome::ToggleBookmarkBarWhenVisible()
211 // because of the notification issues.
212 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
214 [controller_ browserWindow]->BookmarkBarStateChanged(
215 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
216 EXPECT_TRUE([controller_ isBookmarkBarVisible]);
220 // TODO(jrg): This crashes trying to create the BookmarkBarController, adding
221 // an observer to the BookmarkModel.
222 TEST_F(BrowserWindowControllerTest, TestIncognitoWidthSpace) {
223 scoped_ptr<TestingProfile> incognito_profile(new TestingProfile());
224 incognito_profile->set_off_the_record(true);
225 scoped_ptr<Browser> browser(
226 new Browser(Browser::CreateParams(incognito_profile.get(),
227 chrome::GetActiveDesktop()));
228 controller_.reset([[BrowserWindowController alloc]
229 initWithBrowser:browser.get()
232 NSRect tabFrame = [[controller_ tabStripView] frame];
233 [controller_ installIncognitoBadge];
234 NSRect newTabFrame = [[controller_ tabStripView] frame];
235 EXPECT_GT(tabFrame.size.width, newTabFrame.size.width);
237 controller_.release();
243 // Verifies that the toolbar, infobar, tab content area, and download shelf
244 // completely fill the area under the tabstrip.
245 void CheckViewPositions(BrowserWindowController* controller) {
246 NSRect contentView = [[[controller window] contentView] bounds];
247 NSRect tabstrip = [[controller tabStripView] frame];
248 NSRect toolbar = [[controller toolbarView] frame];
249 NSRect infobar = [[controller infoBarContainerView] frame];
250 NSRect contentArea = [[controller tabContentArea] frame];
251 NSRect download = [[[controller downloadShelf] view] frame];
253 EXPECT_EQ(NSMinY(contentView), NSMinY(download));
254 EXPECT_EQ(NSMaxY(download), NSMinY(contentArea));
255 EXPECT_EQ(NSMaxY(contentArea), NSMinY(infobar));
257 // Bookmark bar frame is random memory when hidden.
258 if ([controller bookmarkBarVisible]) {
259 NSRect bookmark = [[controller bookmarkView] frame];
260 EXPECT_EQ(NSMaxY(infobar), NSMinY(bookmark));
261 EXPECT_EQ(NSMaxY(bookmark), NSMinY(toolbar));
262 EXPECT_FALSE([[controller bookmarkView] isHidden]);
264 EXPECT_EQ(NSMaxY(infobar), NSMinY(toolbar));
265 EXPECT_TRUE([[controller bookmarkView] isHidden]);
268 // Toolbar should start immediately under the tabstrip, but the tabstrip is
269 // not necessarily fixed with respect to the content view.
270 EXPECT_EQ(NSMinY(tabstrip), NSMaxY(toolbar));
275 TEST_F(BrowserWindowControllerTest, TestAdjustWindowHeight) {
276 NSWindow* window = [controller_ window];
277 NSRect workarea = [[window screen] visibleFrame];
279 // Place the window well above the bottom of the screen and try to adjust its
280 // height. It should change appropriately (and only downwards). Then get it to
281 // shrink by the same amount; it should return to its original state.
282 NSRect initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 100,
284 [window setFrame:initialFrame display:YES];
285 [controller_ resetWindowGrowthState];
286 [controller_ adjustWindowHeightBy:40];
287 NSRect finalFrame = [window frame];
288 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
289 EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
290 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
291 [controller_ adjustWindowHeightBy:-40];
292 finalFrame = [window frame];
293 EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
294 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
296 // Place the window at the bottom of the screen and try again. Its height
297 // should still change, but it should not grow down below the work area; it
298 // should instead move upwards. Then shrink it and make sure it goes back to
300 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
301 [window setFrame:initialFrame display:YES];
302 [controller_ resetWindowGrowthState];
303 [controller_ adjustWindowHeightBy:40];
304 finalFrame = [window frame];
305 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
306 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
307 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
308 [controller_ adjustWindowHeightBy:-40];
309 finalFrame = [window frame];
310 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
311 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
313 // Put the window slightly offscreen and try again. The height should not
315 initialFrame = NSMakeRect(workarea.origin.x - 10, 0, 200, 200);
316 [window setFrame:initialFrame display:YES];
317 [controller_ resetWindowGrowthState];
318 [controller_ adjustWindowHeightBy:40];
319 EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
320 [controller_ adjustWindowHeightBy:-40];
321 EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
323 // Make the window the same size as the workarea. Resizing both larger and
324 // smaller should have no effect.
325 [window setFrame:workarea display:YES];
326 [controller_ resetWindowGrowthState];
327 [controller_ adjustWindowHeightBy:40];
328 EXPECT_TRUE(NSEqualRects([window frame], workarea));
329 [controller_ adjustWindowHeightBy:-40];
330 EXPECT_TRUE(NSEqualRects([window frame], workarea));
332 // Make the window smaller than the workarea and place it near the bottom of
333 // the workarea. The window should grow down until it hits the bottom and
334 // then continue to grow up. Then shrink it, and it should return to where it
336 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 5,
338 [window setFrame:initialFrame display:YES];
339 [controller_ resetWindowGrowthState];
340 [controller_ adjustWindowHeightBy:40];
341 finalFrame = [window frame];
342 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
343 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
344 [controller_ adjustWindowHeightBy:-40];
345 finalFrame = [window frame];
346 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
347 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
349 // Inset the window slightly from the workarea. It should not grow to be
350 // larger than the workarea. Shrink it; it should return to where it started.
351 initialFrame = NSInsetRect(workarea, 0, 5);
352 [window setFrame:initialFrame display:YES];
353 [controller_ resetWindowGrowthState];
354 [controller_ adjustWindowHeightBy:40];
355 finalFrame = [window frame];
356 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
357 EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
358 [controller_ adjustWindowHeightBy:-40];
359 finalFrame = [window frame];
360 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
361 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
363 // Place the window at the bottom of the screen and grow; it should grow
364 // upwards. Move the window off the bottom, then shrink. It should then shrink
366 initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
367 [window setFrame:initialFrame display:YES];
368 [controller_ resetWindowGrowthState];
369 [controller_ adjustWindowHeightBy:40];
370 finalFrame = [window frame];
371 EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
372 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
373 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
374 NSPoint oldOrigin = initialFrame.origin;
375 NSPoint newOrigin = NSMakePoint(oldOrigin.x, oldOrigin.y + 10);
376 [window setFrameOrigin:newOrigin];
377 initialFrame = [window frame];
378 EXPECT_FLOAT_EQ(NSMinY(initialFrame), oldOrigin.y + 10);
379 [controller_ adjustWindowHeightBy:-40];
380 finalFrame = [window frame];
381 EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame) + 40);
382 EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) - 40);
384 // Do the "inset" test above, but using multiple calls to
385 // |-adjustWindowHeightBy|; the result should be the same.
386 initialFrame = NSInsetRect(workarea, 0, 5);
387 [window setFrame:initialFrame display:YES];
388 [controller_ resetWindowGrowthState];
389 for (int i = 0; i < 8; i++)
390 [controller_ adjustWindowHeightBy:5];
391 finalFrame = [window frame];
392 EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
393 EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
394 for (int i = 0; i < 8; i++)
395 [controller_ adjustWindowHeightBy:-5];
396 finalFrame = [window frame];
397 EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
398 EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
401 // Test to make sure resizing and relaying-out subviews works correctly.
402 TEST_F(BrowserWindowControllerTest, TestResizeViews) {
403 TabStripView* tabstrip = [controller_ tabStripView];
404 NSView* contentView = [[tabstrip window] contentView];
405 NSView* toolbar = [controller_ toolbarView];
406 NSView* infobar = [controller_ infoBarContainerView];
408 // We need to muck with the views a bit to put us in a consistent state before
409 // we start resizing. In particular, we need to move the tab strip to be
410 // immediately above the content area, since we layout views to be directly
411 // under the tab strip.
412 NSRect tabstripFrame = [tabstrip frame];
413 tabstripFrame.origin.y = NSMaxY([contentView frame]);
414 [tabstrip setFrame:tabstripFrame];
416 // The download shelf is created lazily. Force-create it and set its initial
418 NSView* download = [[controller_ downloadShelf] view];
419 NSRect downloadFrame = [download frame];
420 downloadFrame.size.height = 0;
421 [download setFrame:downloadFrame];
423 // Force a layout and check each view's frame.
424 [controller_ layoutSubviews];
425 CheckViewPositions(controller_);
427 // Expand the infobar to 60px and recheck
428 [controller_ resizeView:infobar newHeight:60];
429 CheckViewPositions(controller_);
431 // Expand the toolbar to 64px and recheck
432 [controller_ resizeView:toolbar newHeight:64];
433 CheckViewPositions(controller_);
435 // Add a 30px download shelf and recheck
436 [controller_ resizeView:download newHeight:30];
437 CheckViewPositions(controller_);
439 // Shrink the infobar to 0px and toolbar to 39px and recheck
440 [controller_ resizeView:infobar newHeight:0];
441 [controller_ resizeView:toolbar newHeight:39];
442 CheckViewPositions(controller_);
445 TEST_F(BrowserWindowControllerTest, TestResizeViewsWithBookmarkBar) {
446 // Force a display of the bookmark bar.
447 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
448 [controller_ browserWindow]->BookmarkBarStateChanged(
449 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
451 TabStripView* tabstrip = [controller_ tabStripView];
452 NSView* contentView = [[tabstrip window] contentView];
453 NSView* toolbar = [controller_ toolbarView];
454 NSView* bookmark = [controller_ bookmarkView];
455 NSView* infobar = [controller_ infoBarContainerView];
457 // We need to muck with the views a bit to put us in a consistent state before
458 // we start resizing. In particular, we need to move the tab strip to be
459 // immediately above the content area, since we layout views to be directly
460 // under the tab strip.
461 NSRect tabstripFrame = [tabstrip frame];
462 tabstripFrame.origin.y = NSMaxY([contentView frame]);
463 [tabstrip setFrame:tabstripFrame];
465 // The download shelf is created lazily. Force-create it and set its initial
467 NSView* download = [[controller_ downloadShelf] view];
468 NSRect downloadFrame = [download frame];
469 downloadFrame.size.height = 0;
470 [download setFrame:downloadFrame];
472 // Force a layout and check each view's frame.
473 [controller_ layoutSubviews];
474 CheckViewPositions(controller_);
476 // Add the bookmark bar and recheck.
477 [controller_ resizeView:bookmark newHeight:40];
478 CheckViewPositions(controller_);
480 // Expand the infobar to 60px and recheck
481 [controller_ resizeView:infobar newHeight:60];
482 CheckViewPositions(controller_);
484 // Expand the toolbar to 64px and recheck
485 [controller_ resizeView:toolbar newHeight:64];
486 CheckViewPositions(controller_);
488 // Add a 30px download shelf and recheck
489 [controller_ resizeView:download newHeight:30];
490 CheckViewPositions(controller_);
492 // Remove the bookmark bar and recheck
493 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, false);
494 [controller_ resizeView:bookmark newHeight:0];
495 CheckViewPositions(controller_);
497 // Shrink the infobar to 0px and toolbar to 39px and recheck
498 [controller_ resizeView:infobar newHeight:0];
499 [controller_ resizeView:toolbar newHeight:39];
500 CheckViewPositions(controller_);
503 // Make sure, by default, the bookmark bar and the toolbar are the same width.
504 TEST_F(BrowserWindowControllerTest, BookmarkBarIsSameWidth) {
505 // Set the pref to the bookmark bar is visible when the toolbar is
507 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
509 // Make sure the bookmark bar is the same width as the toolbar
510 NSView* bookmarkBarView = [controller_ bookmarkView];
511 NSView* toolbarView = [controller_ toolbarView];
512 EXPECT_EQ([toolbarView frame].size.width,
513 [bookmarkBarView frame].size.width);
516 TEST_F(BrowserWindowControllerTest, TestTopRightForBubble) {
517 // The bookmark bubble must be attached to a lit and visible star.
518 [controller_ setStarredState:YES];
519 NSPoint p = [controller_ bookmarkBubblePoint];
520 NSRect all = [[controller_ window] frame];
522 // As a sanity check make sure the point is vaguely in the top right
524 EXPECT_GT(p.y, all.origin.y + (all.size.height/2));
525 EXPECT_GT(p.x, all.origin.x + (all.size.width/2));
528 // By the "zoom frame", we mean what Apple calls the "standard frame".
529 TEST_F(BrowserWindowControllerTest, TestZoomFrame) {
530 NSWindow* window = [controller_ window];
532 NSRect screenFrame = [[window screen] visibleFrame];
533 ASSERT_FALSE(NSIsEmptyRect(screenFrame));
535 // Minimum zoomed width is the larger of 60% of available horizontal space or
536 // 60% of available vertical space, subject to available horizontal space.
537 CGFloat minZoomWidth =
538 std::min(std::max((CGFloat)0.6 * screenFrame.size.width,
539 (CGFloat)0.6 * screenFrame.size.height),
540 screenFrame.size.width);
542 // |testFrame| is the size of the window we start out with, and |zoomFrame| is
543 // the one returned by |-windowWillUseStandardFrame:defaultFrame:|.
547 // 1. Test a case where it zooms the window both horizontally and vertically,
548 // and only moves it vertically. "+ 32", etc. are just arbitrary constants
549 // used to check that the window is moved properly and not just to the origin;
550 // they should be small enough to not shove windows off the screen.
551 testFrame.size.width = 0.5 * minZoomWidth;
552 testFrame.size.height = 0.5 * screenFrame.size.height;
553 testFrame.origin.x = screenFrame.origin.x + 32; // See above.
554 testFrame.origin.y = screenFrame.origin.y + 23;
555 [window setFrame:testFrame display:NO];
556 zoomFrame = [controller_ windowWillUseStandardFrame:window
557 defaultFrame:screenFrame];
558 EXPECT_LE(minZoomWidth, zoomFrame.size.width);
559 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
560 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
561 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
563 // 2. Test a case where it zooms the window only horizontally, and only moves
565 testFrame.size.width = 0.5 * minZoomWidth;
566 testFrame.size.height = screenFrame.size.height;
567 testFrame.origin.x = screenFrame.origin.x + screenFrame.size.width -
568 testFrame.size.width;
569 testFrame.origin.y = screenFrame.origin.y;
570 [window setFrame:testFrame display:NO];
571 zoomFrame = [controller_ windowWillUseStandardFrame:window
572 defaultFrame:screenFrame];
573 EXPECT_LE(minZoomWidth, zoomFrame.size.width);
574 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
575 EXPECT_EQ(screenFrame.origin.x + screenFrame.size.width -
576 zoomFrame.size.width, zoomFrame.origin.x);
577 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
579 // 3. Test a case where it zooms the window only vertically, and only moves it
581 testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
582 screenFrame.size.width);
583 testFrame.size.height = 0.3 * screenFrame.size.height;
584 testFrame.origin.x = screenFrame.origin.x + 32; // See above (in 1.).
585 testFrame.origin.y = screenFrame.origin.y + 123;
586 [window setFrame:testFrame display:NO];
587 zoomFrame = [controller_ windowWillUseStandardFrame:window
588 defaultFrame:screenFrame];
589 // Use the actual width of the window frame, since it's subject to rounding.
590 EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
591 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
592 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
593 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
595 // 4. Test a case where zooming should do nothing (i.e., we're already at a
597 testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
598 screenFrame.size.width);
599 testFrame.size.height = screenFrame.size.height;
600 testFrame.origin.x = screenFrame.origin.x + 32; // See above (in 1.).
601 testFrame.origin.y = screenFrame.origin.y;
602 [window setFrame:testFrame display:NO];
603 zoomFrame = [controller_ windowWillUseStandardFrame:window
604 defaultFrame:screenFrame];
605 // Use the actual width of the window frame, since it's subject to rounding.
606 EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
607 EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
608 EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
609 EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
612 TEST_F(BrowserWindowControllerTest, TestFindBarOnTop) {
613 FindBarBridge bridge(NULL);
614 [controller_ addFindBar:bridge.find_bar_cocoa_controller()];
616 // Test that the Z-order of the find bar is on top of everything.
617 NSArray* subviews = [[[controller_ window] contentView] subviews];
618 NSUInteger findBar_index =
619 [subviews indexOfObject:[controller_ findBarView]];
620 EXPECT_NE(NSNotFound, findBar_index);
621 NSUInteger toolbar_index =
622 [subviews indexOfObject:[controller_ toolbarView]];
623 EXPECT_NE(NSNotFound, toolbar_index);
624 NSUInteger bookmark_index =
625 [subviews indexOfObject:[controller_ bookmarkView]];
626 EXPECT_NE(NSNotFound, bookmark_index);
628 EXPECT_GT(findBar_index, toolbar_index);
629 EXPECT_GT(findBar_index, bookmark_index);
632 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemNoErrors) {
633 base::scoped_nsobject<NSMenuItem> syncMenuItem(
634 [[NSMenuItem alloc] initWithTitle:@""
635 action:@selector(commandDispatch)
637 [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
639 NSString* startSignin =
640 l10n_util::GetNSStringFWithFixup(
641 IDS_SYNC_MENU_PRE_SYNCED_LABEL,
642 l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
644 // Make sure shouldShow parameter is obeyed, and we get the default
645 // label if not signed in.
646 [BrowserWindowController updateSigninItem:syncMenuItem
648 currentProfile:profile()];
650 EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
651 EXPECT_FALSE([syncMenuItem isHidden]);
653 [BrowserWindowController updateSigninItem:syncMenuItem
655 currentProfile:profile()];
656 EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
657 EXPECT_TRUE([syncMenuItem isHidden]);
660 std::string username = "foo@example.com";
661 NSString* alreadySignedIn =
662 l10n_util::GetNSStringFWithFixup(IDS_SYNC_MENU_SYNCED_LABEL,
663 base::UTF8ToUTF16(username));
664 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
665 signin->SetAuthenticatedUsername(username);
666 ProfileSyncService* sync =
667 ProfileSyncServiceFactory::GetForProfile(profile());
668 sync->SetSyncSetupCompleted();
669 [BrowserWindowController updateSigninItem:syncMenuItem
671 currentProfile:profile()];
672 EXPECT_TRUE([[syncMenuItem title] isEqualTo:alreadySignedIn]);
673 EXPECT_FALSE([syncMenuItem isHidden]);
676 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemAuthError) {
677 base::scoped_nsobject<NSMenuItem> syncMenuItem(
678 [[NSMenuItem alloc] initWithTitle:@""
679 action:@selector(commandDispatch)
681 [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
684 std::string username = "foo@example.com";
685 SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
686 signin->SetAuthenticatedUsername(username);
687 ProfileSyncService* sync =
688 ProfileSyncServiceFactory::GetForProfile(profile());
689 sync->SetSyncSetupCompleted();
690 // Force an auth error.
691 FakeAuthStatusProvider provider(SigninGlobalError::GetForProfile(profile()));
692 GoogleServiceAuthError error(
693 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
694 provider.SetAuthError("user@gmail.com", error);
695 [BrowserWindowController updateSigninItem:syncMenuItem
697 currentProfile:profile()];
698 NSString* authError =
699 l10n_util::GetNSStringWithFixup(IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM);
700 EXPECT_TRUE([[syncMenuItem title] isEqualTo:authError]);
701 EXPECT_FALSE([syncMenuItem isHidden]);
705 // If there's a separator after the signin menu item, make sure it is hidden/
706 // shown when the signin menu item is.
707 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithSeparator) {
708 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
709 NSMenuItem* signinMenuItem =
710 [menu addItemWithTitle:@""
711 action:@selector(commandDispatch)
713 [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
714 NSMenuItem* followingSeparator = [NSMenuItem separatorItem];
715 [menu addItem:followingSeparator];
716 [signinMenuItem setHidden:NO];
717 [followingSeparator setHidden:NO];
719 [BrowserWindowController updateSigninItem:signinMenuItem
721 currentProfile:profile()];
723 EXPECT_FALSE([followingSeparator isEnabled]);
724 EXPECT_TRUE([signinMenuItem isHidden]);
725 EXPECT_TRUE([followingSeparator isHidden]);
727 [BrowserWindowController updateSigninItem:signinMenuItem
729 currentProfile:profile()];
731 EXPECT_FALSE([followingSeparator isEnabled]);
732 EXPECT_FALSE([signinMenuItem isHidden]);
733 EXPECT_FALSE([followingSeparator isHidden]);
736 // If there's a non-separator item after the signin menu item, it should not
737 // change state when the signin menu item is hidden/shown.
738 TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithNonSeparator) {
739 base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
740 NSMenuItem* signinMenuItem =
741 [menu addItemWithTitle:@""
742 action:@selector(commandDispatch)
744 [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
745 NSMenuItem* followingNonSeparator =
746 [menu addItemWithTitle:@""
747 action:@selector(commandDispatch)
749 [signinMenuItem setHidden:NO];
750 [followingNonSeparator setHidden:NO];
752 [BrowserWindowController updateSigninItem:signinMenuItem
754 currentProfile:profile()];
756 EXPECT_TRUE([followingNonSeparator isEnabled]);
757 EXPECT_TRUE([signinMenuItem isHidden]);
758 EXPECT_FALSE([followingNonSeparator isHidden]);
760 [followingNonSeparator setHidden:YES];
761 [BrowserWindowController updateSigninItem:signinMenuItem
763 currentProfile:profile()];
765 EXPECT_TRUE([followingNonSeparator isEnabled]);
766 EXPECT_FALSE([signinMenuItem isHidden]);
767 EXPECT_TRUE([followingNonSeparator isHidden]);
770 // Verify that hit testing works correctly when the bookmark bar overlaps
772 TEST_F(BrowserWindowControllerTest, BookmarkBarHitTest) {
773 profile()->GetPrefs()->SetBoolean(prefs::kShowBookmarkBar, true);
774 [controller_ browserWindow]->BookmarkBarStateChanged(
775 BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
777 NSView* bookmarkView = [controller_ bookmarkView];
778 NSView* contentView = [[controller_ window] contentView];
779 NSPoint point = [bookmarkView convertPoint:NSMakePoint(1, 1)
780 toView:[contentView superview]];
782 EXPECT_TRUE([[contentView hitTest:point] isDescendantOf:bookmarkView]);
785 @interface BrowserWindowControllerFakeFullscreen : BrowserWindowController {
787 // We release the window ourselves, so we don't have to rely on the unittest
789 base::scoped_nsobject<NSWindow> testFullscreenWindow_;
793 class BrowserWindowFullScreenControllerTest : public CocoaProfileTest {
795 virtual void SetUp() {
796 CocoaProfileTest::SetUp();
797 ASSERT_TRUE(browser());
800 [[BrowserWindowControllerFakeFullscreen alloc] initWithBrowser:browser()
804 virtual void TearDown() {
806 CocoaProfileTest::TearDown();
810 BrowserWindowController* controller_;
813 // Check if the window is front most or if one of its child windows (such
814 // as a status bubble) is front most.
815 static bool IsFrontWindow(NSWindow *window) {
816 NSWindow* frontmostWindow = [[NSApp orderedWindows] objectAtIndex:0];
817 return [frontmostWindow isEqual:window] ||
818 [[frontmostWindow parentWindow] isEqual:window];
821 void WaitForFullScreenTransition() {
822 content::WindowedNotificationObserver observer(
823 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
824 content::NotificationService::AllSources());
828 TEST_F(BrowserWindowFullScreenControllerTest, TestFullscreen) {
829 [controller_ showWindow:nil];
830 EXPECT_FALSE([controller_ isFullscreen]);
832 [controller_ enterFullscreen];
833 WaitForFullScreenTransition();
834 EXPECT_TRUE([controller_ isFullscreen]);
836 [controller_ exitFullscreen];
837 WaitForFullScreenTransition();
838 EXPECT_FALSE([controller_ isFullscreen]);
841 // If this test fails, it is usually a sign that the bots have some sort of
842 // problem (such as a modal dialog up). This tests is a very useful canary, so
843 // please do not mark it as flaky without first verifying that there are no bot
845 TEST_F(BrowserWindowFullScreenControllerTest, TestActivate) {
846 [controller_ showWindow:nil];
848 EXPECT_FALSE([controller_ isFullscreen]);
850 [controller_ activate];
851 EXPECT_TRUE(IsFrontWindow([controller_ window]));
853 [controller_ enterFullscreen];
854 WaitForFullScreenTransition();
855 [controller_ activate];
857 // No fullscreen window on 10.7+.
858 if (base::mac::IsOSSnowLeopard())
859 EXPECT_TRUE(IsFrontWindow([controller_ createFullscreenWindow]));
861 // We have to cleanup after ourselves by unfullscreening.
862 [controller_ exitFullscreen];
863 WaitForFullScreenTransition();
866 @implementation BrowserWindowControllerFakeFullscreen
867 // Override |-createFullscreenWindow| to return a dummy window. This isn't
868 // needed to pass the test, but because the dummy window is only 100x100, it
869 // prevents the real fullscreen window from flashing up and taking over the
870 // whole screen. We have to return an actual window because |-layoutSubviews|
871 // looks at the window's frame.
872 - (NSWindow*)createFullscreenWindow {
873 if (testFullscreenWindow_.get())
874 return testFullscreenWindow_.get();
876 testFullscreenWindow_.reset(
877 [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,400,400)
878 styleMask:NSBorderlessWindowMask
879 backing:NSBackingStoreBuffered
881 return testFullscreenWindow_.get();
885 /* TODO(???): test other methods of BrowserWindowController */