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#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/grit/chromium_strings.h"
29#include "chrome/grit/generated_resources.h"
30#include "chrome/test/base/testing_profile.h"
31#include "components/signin/core/browser/fake_auth_status_provider.h"
32#include "components/signin/core/browser/profile_oauth2_token_service.h"
33#include "components/signin/core/browser/signin_error_controller.h"
34#include "components/signin/core/browser/signin_manager.h"
35#include "content/public/browser/notification_service.h"
36#include "content/public/test/test_utils.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"
40
41using ::testing::Return;
42
43@interface BrowserWindowController (JustForTesting)
44// Already defined in BWC.
45- (void)saveWindowPositionIfNeeded;
46- (void)layoutSubviews;
47@end
48
49@interface BrowserWindowController (ExposedForTesting)
50// Implementations are below.
51- (NSView*)infoBarContainerView;
52- (NSView*)toolbarView;
53- (NSView*)bookmarkView;
54- (BOOL)bookmarkBarVisible;
55@end
56
57@implementation BrowserWindowController (ExposedForTesting)
58- (NSView*)infoBarContainerView {
59  return [infoBarContainerController_ view];
60}
61
62- (NSView*)toolbarView {
63  return [toolbarController_ view];
64}
65
66- (NSView*)bookmarkView {
67  return [bookmarkBarController_ view];
68}
69
70- (NSView*)findBarView {
71  return [findBarCocoaController_ view];
72}
73
74- (BOOL)bookmarkBarVisible {
75  return [bookmarkBarController_ isVisible];
76}
77@end
78
79class BrowserWindowControllerTest : public CocoaProfileTest {
80 public:
81  virtual void SetUp() {
82    CocoaProfileTest::SetUp();
83    ASSERT_TRUE(browser());
84
85    controller_ = [[BrowserWindowController alloc] initWithBrowser:browser()
86                                                     takeOwnership:NO];
87  }
88
89  virtual void TearDown() {
90    [controller_ close];
91    CocoaProfileTest::TearDown();
92  }
93
94 public:
95  BrowserWindowController* controller_;
96};
97
98TEST_F(BrowserWindowControllerTest, TestSaveWindowPosition) {
99  PrefService* prefs = profile()->GetPrefs();
100  ASSERT_TRUE(prefs != NULL);
101
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());
107
108  // Ask the window to save its position, then check that a preference
109  // exists.
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());
116}
117
118TEST_F(BrowserWindowControllerTest, TestFullScreenWindow) {
119  // Confirm that |-createFullscreenWindow| doesn't return nil.
120  // See BrowserWindowFullScreenControllerTest for more fullscreen tests.
121  EXPECT_TRUE([controller_ createFullscreenWindow]);
122}
123
124TEST_F(BrowserWindowControllerTest, TestNormal) {
125  // Force the bookmark bar to be shown.
126  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true);
127  [controller_ browserWindow]->BookmarkBarStateChanged(
128      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
129
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]);
135
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]);
149  [controller close];
150}
151
152TEST_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]);
161
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());
168
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());
174
175  [controller close];
176}
177
178TEST_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]);
187
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());
194
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());
200
201  [controller close];
202}
203
204TEST_F(BrowserWindowControllerTest, TestTheme) {
205  [controller_ userChangedTheme];
206}
207
208TEST_F(BrowserWindowControllerTest, BookmarkBarControllerIndirection) {
209  EXPECT_FALSE([controller_ isBookmarkBarVisible]);
210
211  // Explicitly show the bar. Can't use chrome::ToggleBookmarkBarWhenVisible()
212  // because of the notification issues.
213  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true);
214
215  [controller_ browserWindow]->BookmarkBarStateChanged(
216      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
217  EXPECT_TRUE([controller_ isBookmarkBarVisible]);
218}
219
220#if 0
221// TODO(jrg): This crashes trying to create the BookmarkBarController, adding
222// an observer to the BookmarkModel.
223TEST_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()
231                                takeOwnership:NO]);
232
233  NSRect tabFrame = [[controller_ tabStripView] frame];
234  [controller_ installIncognitoBadge];
235  NSRect newTabFrame = [[controller_ tabStripView] frame];
236  EXPECT_GT(tabFrame.size.width, newTabFrame.size.width);
237
238  controller_.release();
239}
240#endif
241
242namespace {
243
244// Verifies that the toolbar, infobar, tab content area, and download shelf
245// completely fill the area under the tabstrip.
246void 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 = NSZeroRect;
253  if ([[[controller downloadShelf] view] superview])
254    download = [[[controller downloadShelf] view] frame];
255
256  EXPECT_EQ(NSMinY(contentView), NSMinY(download));
257  EXPECT_EQ(NSMaxY(download), NSMinY(contentArea));
258  EXPECT_EQ(NSMaxY(contentArea), NSMinY(infobar));
259
260  // Bookmark bar frame is random memory when hidden.
261  if ([controller bookmarkBarVisible]) {
262    NSRect bookmark = [[controller bookmarkView] frame];
263    EXPECT_EQ(NSMaxY(infobar), NSMinY(bookmark));
264    EXPECT_EQ(NSMaxY(bookmark), NSMinY(toolbar));
265    EXPECT_FALSE([[controller bookmarkView] isHidden]);
266  } else {
267    EXPECT_EQ(NSMaxY(infobar), NSMinY(toolbar));
268    EXPECT_TRUE([[controller bookmarkView] isHidden]);
269  }
270
271  // Toolbar should start immediately under the tabstrip, but the tabstrip is
272  // not necessarily fixed with respect to the content view.
273  EXPECT_EQ(NSMinY(tabstrip), NSMaxY(toolbar));
274}
275
276}  // end namespace
277
278TEST_F(BrowserWindowControllerTest, TestAdjustWindowHeight) {
279  NSWindow* window = [controller_ window];
280  NSRect workarea = [[window screen] visibleFrame];
281
282  // Place the window well above the bottom of the screen and try to adjust its
283  // height. It should change appropriately (and only downwards). Then get it to
284  // shrink by the same amount; it should return to its original state.
285  NSRect initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 100,
286                                   200, 200);
287  [window setFrame:initialFrame display:YES];
288  [controller_ resetWindowGrowthState];
289  [controller_ adjustWindowHeightBy:40];
290  NSRect finalFrame = [window frame];
291  EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
292  EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
293  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
294  [controller_ adjustWindowHeightBy:-40];
295  finalFrame = [window frame];
296  EXPECT_FLOAT_EQ(NSMaxY(finalFrame), NSMaxY(initialFrame));
297  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
298
299  // Place the window at the bottom of the screen and try again.  Its height
300  // should still change, but it should not grow down below the work area; it
301  // should instead move upwards. Then shrink it and make sure it goes back to
302  // the way it was.
303  initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
304  [window setFrame:initialFrame display:YES];
305  [controller_ resetWindowGrowthState];
306  [controller_ adjustWindowHeightBy:40];
307  finalFrame = [window frame];
308  EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
309  EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
310  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
311  [controller_ adjustWindowHeightBy:-40];
312  finalFrame = [window frame];
313  EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
314  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame));
315
316  // Put the window slightly offscreen and try again.  The height should not
317  // change this time.
318  initialFrame = NSMakeRect(workarea.origin.x - 10, 0, 200, 200);
319  [window setFrame:initialFrame display:YES];
320  [controller_ resetWindowGrowthState];
321  [controller_ adjustWindowHeightBy:40];
322  EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
323  [controller_ adjustWindowHeightBy:-40];
324  EXPECT_TRUE(NSEqualRects([window frame], initialFrame));
325
326  // Make the window the same size as the workarea.  Resizing both larger and
327  // smaller should have no effect.
328  [window setFrame:workarea display:YES];
329  [controller_ resetWindowGrowthState];
330  [controller_ adjustWindowHeightBy:40];
331  EXPECT_TRUE(NSEqualRects([window frame], workarea));
332  [controller_ adjustWindowHeightBy:-40];
333  EXPECT_TRUE(NSEqualRects([window frame], workarea));
334
335  // Make the window smaller than the workarea and place it near the bottom of
336  // the workarea.  The window should grow down until it hits the bottom and
337  // then continue to grow up. Then shrink it, and it should return to where it
338  // was.
339  initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y + 5,
340                            200, 200);
341  [window setFrame:initialFrame display:YES];
342  [controller_ resetWindowGrowthState];
343  [controller_ adjustWindowHeightBy:40];
344  finalFrame = [window frame];
345  EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
346  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
347  [controller_ adjustWindowHeightBy:-40];
348  finalFrame = [window frame];
349  EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
350  EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
351
352  // Inset the window slightly from the workarea.  It should not grow to be
353  // larger than the workarea. Shrink it; it should return to where it started.
354  initialFrame = NSInsetRect(workarea, 0, 5);
355  [window setFrame:initialFrame display:YES];
356  [controller_ resetWindowGrowthState];
357  [controller_ adjustWindowHeightBy:40];
358  finalFrame = [window frame];
359  EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
360  EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
361  [controller_ adjustWindowHeightBy:-40];
362  finalFrame = [window frame];
363  EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
364  EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
365
366  // Place the window at the bottom of the screen and grow; it should grow
367  // upwards. Move the window off the bottom, then shrink. It should then shrink
368  // from the bottom.
369  initialFrame = NSMakeRect(workarea.origin.x, workarea.origin.y, 200, 200);
370  [window setFrame:initialFrame display:YES];
371  [controller_ resetWindowGrowthState];
372  [controller_ adjustWindowHeightBy:40];
373  finalFrame = [window frame];
374  EXPECT_FALSE(NSEqualRects(finalFrame, initialFrame));
375  EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame));
376  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) + 40);
377  NSPoint oldOrigin = initialFrame.origin;
378  NSPoint newOrigin = NSMakePoint(oldOrigin.x, oldOrigin.y + 10);
379  [window setFrameOrigin:newOrigin];
380  initialFrame = [window frame];
381  EXPECT_FLOAT_EQ(NSMinY(initialFrame), oldOrigin.y + 10);
382  [controller_ adjustWindowHeightBy:-40];
383  finalFrame = [window frame];
384  EXPECT_FLOAT_EQ(NSMinY(finalFrame), NSMinY(initialFrame) + 40);
385  EXPECT_FLOAT_EQ(NSHeight(finalFrame), NSHeight(initialFrame) - 40);
386
387  // Do the "inset" test above, but using multiple calls to
388  // |-adjustWindowHeightBy|; the result should be the same.
389  initialFrame = NSInsetRect(workarea, 0, 5);
390  [window setFrame:initialFrame display:YES];
391  [controller_ resetWindowGrowthState];
392  for (int i = 0; i < 8; i++)
393    [controller_ adjustWindowHeightBy:5];
394  finalFrame = [window frame];
395  EXPECT_FLOAT_EQ(NSMinY(workarea), NSMinY(finalFrame));
396  EXPECT_FLOAT_EQ(NSHeight(workarea), NSHeight(finalFrame));
397  for (int i = 0; i < 8; i++)
398    [controller_ adjustWindowHeightBy:-5];
399  finalFrame = [window frame];
400  EXPECT_FLOAT_EQ(NSMinY(initialFrame), NSMinY(finalFrame));
401  EXPECT_FLOAT_EQ(NSHeight(initialFrame), NSHeight(finalFrame));
402}
403
404// Test to make sure resizing and relaying-out subviews works correctly.
405TEST_F(BrowserWindowControllerTest, TestResizeViews) {
406  TabStripView* tabstrip = [controller_ tabStripView];
407  NSView* contentView = [[tabstrip window] contentView];
408  NSView* toolbar = [controller_ toolbarView];
409  NSView* infobar = [controller_ infoBarContainerView];
410
411  // We need to muck with the views a bit to put us in a consistent state before
412  // we start resizing.  In particular, we need to move the tab strip to be
413  // immediately above the content area, since we layout views to be directly
414  // under the tab strip.
415  NSRect tabstripFrame = [tabstrip frame];
416  tabstripFrame.origin.y = NSMaxY([contentView frame]);
417  [tabstrip setFrame:tabstripFrame];
418
419  // Make the download shelf and set its initial height to 0.
420  [controller_ createAndAddDownloadShelf];
421  NSView* download = [[controller_ downloadShelf] view];
422  NSRect downloadFrame = [download frame];
423  downloadFrame.size.height = 0;
424  [download setFrame:downloadFrame];
425
426  // Force a layout and check each view's frame.
427  [controller_ layoutSubviews];
428  CheckViewPositions(controller_);
429
430  // Expand the infobar to 60px and recheck
431  [controller_ resizeView:infobar newHeight:60];
432  CheckViewPositions(controller_);
433
434  // Expand the toolbar to 64px and recheck
435  [controller_ resizeView:toolbar newHeight:64];
436  CheckViewPositions(controller_);
437
438  // Add a 30px download shelf and recheck
439  [controller_ resizeView:download newHeight:30];
440  CheckViewPositions(controller_);
441
442  // Shrink the infobar to 0px and toolbar to 39px and recheck
443  [controller_ resizeView:infobar newHeight:0];
444  [controller_ resizeView:toolbar newHeight:39];
445  CheckViewPositions(controller_);
446}
447
448TEST_F(BrowserWindowControllerTest, TestResizeViewsWithBookmarkBar) {
449  // Force a display of the bookmark bar.
450  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true);
451  [controller_ browserWindow]->BookmarkBarStateChanged(
452      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
453
454  TabStripView* tabstrip = [controller_ tabStripView];
455  NSView* contentView = [[tabstrip window] contentView];
456  NSView* toolbar = [controller_ toolbarView];
457  NSView* bookmark = [controller_ bookmarkView];
458  NSView* infobar = [controller_ infoBarContainerView];
459
460  // We need to muck with the views a bit to put us in a consistent state before
461  // we start resizing.  In particular, we need to move the tab strip to be
462  // immediately above the content area, since we layout views to be directly
463  // under the tab strip.
464  NSRect tabstripFrame = [tabstrip frame];
465  tabstripFrame.origin.y = NSMaxY([contentView frame]);
466  [tabstrip setFrame:tabstripFrame];
467
468  // The download shelf is created lazily.  Force-create it and set its initial
469  // height to 0.
470  [controller_ createAndAddDownloadShelf];
471  NSView* download = [[controller_ downloadShelf] view];
472  NSRect downloadFrame = [download frame];
473  downloadFrame.size.height = 0;
474  [download setFrame:downloadFrame];
475
476  // Force a layout and check each view's frame.
477  [controller_ layoutSubviews];
478  CheckViewPositions(controller_);
479
480  // Add the bookmark bar and recheck.
481  [controller_ resizeView:bookmark newHeight:40];
482  CheckViewPositions(controller_);
483
484  // Expand the infobar to 60px and recheck
485  [controller_ resizeView:infobar newHeight:60];
486  CheckViewPositions(controller_);
487
488  // Expand the toolbar to 64px and recheck
489  [controller_ resizeView:toolbar newHeight:64];
490  CheckViewPositions(controller_);
491
492  // Add a 30px download shelf and recheck
493  [controller_ resizeView:download newHeight:30];
494  CheckViewPositions(controller_);
495
496  // Remove the bookmark bar and recheck
497  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, false);
498  [controller_ browserWindow]->BookmarkBarStateChanged(
499      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
500  [controller_ resizeView:bookmark newHeight:0];
501  CheckViewPositions(controller_);
502
503  // Shrink the infobar to 0px and toolbar to 39px and recheck
504  [controller_ resizeView:infobar newHeight:0];
505  [controller_ resizeView:toolbar newHeight:39];
506  CheckViewPositions(controller_);
507}
508
509// Make sure, by default, the bookmark bar and the toolbar are the same width.
510TEST_F(BrowserWindowControllerTest, BookmarkBarIsSameWidth) {
511  // Set the pref to the bookmark bar is visible when the toolbar is
512  // first created.
513  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true);
514
515  // Make sure the bookmark bar is the same width as the toolbar
516  NSView* bookmarkBarView = [controller_ bookmarkView];
517  NSView* toolbarView = [controller_ toolbarView];
518  EXPECT_EQ([toolbarView frame].size.width,
519            [bookmarkBarView frame].size.width);
520}
521
522TEST_F(BrowserWindowControllerTest, TestTopRightForBubble) {
523  // The bookmark bubble must be attached to a lit and visible star.
524  [controller_ setStarredState:YES];
525  NSPoint p = [controller_ bookmarkBubblePoint];  // Window coordinates.
526  NSRect all = [[controller_ window] frame];      // Screen coordinates.
527
528  // As a sanity check make sure the point is vaguely in the top right
529  // of the window.
530  EXPECT_GT(p.y, all.size.height / 2);
531  EXPECT_GT(p.x, all.size.width / 2);
532}
533
534// By the "zoom frame", we mean what Apple calls the "standard frame".
535TEST_F(BrowserWindowControllerTest, TestZoomFrame) {
536  NSWindow* window = [controller_ window];
537  ASSERT_TRUE(window);
538  NSRect screenFrame = [[window screen] visibleFrame];
539  ASSERT_FALSE(NSIsEmptyRect(screenFrame));
540
541  // Minimum zoomed width is the larger of 60% of available horizontal space or
542  // 60% of available vertical space, subject to available horizontal space.
543  CGFloat minZoomWidth =
544      std::min(std::max((CGFloat)0.6 * screenFrame.size.width,
545                        (CGFloat)0.6 * screenFrame.size.height),
546               screenFrame.size.width);
547
548  // |testFrame| is the size of the window we start out with, and |zoomFrame| is
549  // the one returned by |-windowWillUseStandardFrame:defaultFrame:|.
550  NSRect testFrame;
551  NSRect zoomFrame;
552
553  // 1. Test a case where it zooms the window both horizontally and vertically,
554  // and only moves it vertically. "+ 32", etc. are just arbitrary constants
555  // used to check that the window is moved properly and not just to the origin;
556  // they should be small enough to not shove windows off the screen.
557  testFrame.size.width = 0.5 * minZoomWidth;
558  testFrame.size.height = 0.5 * screenFrame.size.height;
559  testFrame.origin.x = screenFrame.origin.x + 32;  // See above.
560  testFrame.origin.y = screenFrame.origin.y + 23;
561  [window setFrame:testFrame display:NO];
562  zoomFrame = [controller_ windowWillUseStandardFrame:window
563                                         defaultFrame:screenFrame];
564  EXPECT_LE(minZoomWidth, zoomFrame.size.width);
565  EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
566  EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
567  EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
568
569  // 2. Test a case where it zooms the window only horizontally, and only moves
570  // it horizontally.
571  testFrame.size.width = 0.5 * minZoomWidth;
572  testFrame.size.height = screenFrame.size.height;
573  testFrame.origin.x = screenFrame.origin.x + screenFrame.size.width -
574                       testFrame.size.width;
575  testFrame.origin.y = screenFrame.origin.y;
576  [window setFrame:testFrame display:NO];
577  zoomFrame = [controller_ windowWillUseStandardFrame:window
578                                         defaultFrame:screenFrame];
579  EXPECT_LE(minZoomWidth, zoomFrame.size.width);
580  EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
581  EXPECT_EQ(screenFrame.origin.x + screenFrame.size.width -
582            zoomFrame.size.width, zoomFrame.origin.x);
583  EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
584
585  // 3. Test a case where it zooms the window only vertically, and only moves it
586  // vertically.
587  testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
588                                  screenFrame.size.width);
589  testFrame.size.height = 0.3 * screenFrame.size.height;
590  testFrame.origin.x = screenFrame.origin.x + 32;  // See above (in 1.).
591  testFrame.origin.y = screenFrame.origin.y + 123;
592  [window setFrame:testFrame display:NO];
593  zoomFrame = [controller_ windowWillUseStandardFrame:window
594                                         defaultFrame:screenFrame];
595  // Use the actual width of the window frame, since it's subject to rounding.
596  EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
597  EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
598  EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
599  EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
600
601  // 4. Test a case where zooming should do nothing (i.e., we're already at a
602  // zoomed frame).
603  testFrame.size.width = std::min((CGFloat)1.1 * minZoomWidth,
604                                  screenFrame.size.width);
605  testFrame.size.height = screenFrame.size.height;
606  testFrame.origin.x = screenFrame.origin.x + 32;  // See above (in 1.).
607  testFrame.origin.y = screenFrame.origin.y;
608  [window setFrame:testFrame display:NO];
609  zoomFrame = [controller_ windowWillUseStandardFrame:window
610                                         defaultFrame:screenFrame];
611  // Use the actual width of the window frame, since it's subject to rounding.
612  EXPECT_EQ([window frame].size.width, zoomFrame.size.width);
613  EXPECT_EQ(screenFrame.size.height, zoomFrame.size.height);
614  EXPECT_EQ(testFrame.origin.x, zoomFrame.origin.x);
615  EXPECT_EQ(screenFrame.origin.y, zoomFrame.origin.y);
616}
617
618TEST_F(BrowserWindowControllerTest, TestFindBarOnTop) {
619  FindBarBridge bridge(NULL);
620  [controller_ addFindBar:bridge.find_bar_cocoa_controller()];
621
622  // Test that the Z-order of the find bar is on top of everything.
623  NSArray* subviews = [[[controller_ window] contentView] subviews];
624  NSUInteger findBar_index =
625      [subviews indexOfObject:[controller_ findBarView]];
626  EXPECT_NE(NSNotFound, findBar_index);
627  NSUInteger toolbar_index =
628      [subviews indexOfObject:[controller_ toolbarView]];
629  EXPECT_NE(NSNotFound, toolbar_index);
630  NSUInteger bookmark_index =
631      [subviews indexOfObject:[controller_ bookmarkView]];
632  EXPECT_NE(NSNotFound, bookmark_index);
633
634  EXPECT_GT(findBar_index, toolbar_index);
635  EXPECT_GT(findBar_index, bookmark_index);
636}
637
638TEST_F(BrowserWindowControllerTest, TestSigninMenuItemNoErrors) {
639  base::scoped_nsobject<NSMenuItem> syncMenuItem(
640      [[NSMenuItem alloc] initWithTitle:@""
641                                 action:@selector(commandDispatch)
642                          keyEquivalent:@""]);
643  [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
644
645  NSString* startSignin =
646    l10n_util::GetNSStringFWithFixup(
647        IDS_SYNC_MENU_PRE_SYNCED_LABEL,
648        l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
649
650  // Make sure shouldShow parameter is obeyed, and we get the default
651  // label if not signed in.
652  [BrowserWindowController updateSigninItem:syncMenuItem
653                                 shouldShow:YES
654                             currentProfile:profile()];
655
656  EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
657  EXPECT_FALSE([syncMenuItem isHidden]);
658
659  [BrowserWindowController updateSigninItem:syncMenuItem
660                                 shouldShow:NO
661                             currentProfile:profile()];
662  EXPECT_TRUE([[syncMenuItem title] isEqualTo:startSignin]);
663  EXPECT_TRUE([syncMenuItem isHidden]);
664
665  // Now sign in.
666  std::string username = "foo@example.com";
667  NSString* alreadySignedIn =
668    l10n_util::GetNSStringFWithFixup(IDS_SYNC_MENU_SYNCED_LABEL,
669                                     base::UTF8ToUTF16(username));
670  SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
671  signin->SetAuthenticatedUsername(username);
672  ProfileSyncService* sync =
673    ProfileSyncServiceFactory::GetForProfile(profile());
674  sync->SetSyncSetupCompleted();
675  [BrowserWindowController updateSigninItem:syncMenuItem
676                                 shouldShow:YES
677                             currentProfile:profile()];
678  EXPECT_TRUE([[syncMenuItem title] isEqualTo:alreadySignedIn]);
679  EXPECT_FALSE([syncMenuItem isHidden]);
680}
681
682TEST_F(BrowserWindowControllerTest, TestSigninMenuItemAuthError) {
683  base::scoped_nsobject<NSMenuItem> syncMenuItem(
684      [[NSMenuItem alloc] initWithTitle:@""
685                                 action:@selector(commandDispatch)
686                          keyEquivalent:@""]);
687  [syncMenuItem setTag:IDC_SHOW_SYNC_SETUP];
688
689  // Now sign in.
690  std::string username = "foo@example.com";
691  SigninManager* signin = SigninManagerFactory::GetForProfile(profile());
692  signin->SetAuthenticatedUsername(username);
693  ProfileSyncService* sync =
694      ProfileSyncServiceFactory::GetForProfile(profile());
695  sync->SetSyncSetupCompleted();
696  // Force an auth error.
697  FakeAuthStatusProvider provider(
698      ProfileOAuth2TokenServiceFactory::GetForProfile(profile())->
699          signin_error_controller());
700  GoogleServiceAuthError error(
701      GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
702  provider.SetAuthError("user@gmail.com", "user@gmail.com", error);
703  [BrowserWindowController updateSigninItem:syncMenuItem
704                                 shouldShow:YES
705                             currentProfile:profile()];
706  NSString* authError =
707    l10n_util::GetNSStringWithFixup(IDS_SYNC_SIGN_IN_ERROR_WRENCH_MENU_ITEM);
708  EXPECT_TRUE([[syncMenuItem title] isEqualTo:authError]);
709  EXPECT_FALSE([syncMenuItem isHidden]);
710
711}
712
713// If there's a separator after the signin menu item, make sure it is hidden/
714// shown when the signin menu item is.
715TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithSeparator) {
716  base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
717  NSMenuItem* signinMenuItem =
718      [menu addItemWithTitle:@""
719                      action:@selector(commandDispatch)
720               keyEquivalent:@""];
721  [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
722  NSMenuItem* followingSeparator = [NSMenuItem separatorItem];
723  [menu addItem:followingSeparator];
724  [signinMenuItem setHidden:NO];
725  [followingSeparator setHidden:NO];
726
727  [BrowserWindowController updateSigninItem:signinMenuItem
728                                 shouldShow:NO
729                             currentProfile:profile()];
730
731  EXPECT_FALSE([followingSeparator isEnabled]);
732  EXPECT_TRUE([signinMenuItem isHidden]);
733  EXPECT_TRUE([followingSeparator isHidden]);
734
735  [BrowserWindowController updateSigninItem:signinMenuItem
736                                 shouldShow:YES
737                             currentProfile:profile()];
738
739  EXPECT_FALSE([followingSeparator isEnabled]);
740  EXPECT_FALSE([signinMenuItem isHidden]);
741  EXPECT_FALSE([followingSeparator isHidden]);
742}
743
744// If there's a non-separator item after the signin menu item, it should not
745// change state when the signin menu item is hidden/shown.
746TEST_F(BrowserWindowControllerTest, TestSigninMenuItemWithNonSeparator) {
747  base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] initWithTitle:@""]);
748  NSMenuItem* signinMenuItem =
749      [menu addItemWithTitle:@""
750                      action:@selector(commandDispatch)
751               keyEquivalent:@""];
752  [signinMenuItem setTag:IDC_SHOW_SYNC_SETUP];
753  NSMenuItem* followingNonSeparator =
754      [menu addItemWithTitle:@""
755                      action:@selector(commandDispatch)
756               keyEquivalent:@""];
757  [signinMenuItem setHidden:NO];
758  [followingNonSeparator setHidden:NO];
759
760  [BrowserWindowController updateSigninItem:signinMenuItem
761                                 shouldShow:NO
762                             currentProfile:profile()];
763
764  EXPECT_TRUE([followingNonSeparator isEnabled]);
765  EXPECT_TRUE([signinMenuItem isHidden]);
766  EXPECT_FALSE([followingNonSeparator isHidden]);
767
768  [followingNonSeparator setHidden:YES];
769  [BrowserWindowController updateSigninItem:signinMenuItem
770                                 shouldShow:YES
771                             currentProfile:profile()];
772
773  EXPECT_TRUE([followingNonSeparator isEnabled]);
774  EXPECT_FALSE([signinMenuItem isHidden]);
775  EXPECT_TRUE([followingNonSeparator isHidden]);
776}
777
778// Verify that hit testing works correctly when the bookmark bar overlaps
779// web contents.
780TEST_F(BrowserWindowControllerTest, BookmarkBarHitTest) {
781  profile()->GetPrefs()->SetBoolean(bookmarks::prefs::kShowBookmarkBar, true);
782  [controller_ browserWindow]->BookmarkBarStateChanged(
783      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
784
785  NSView* bookmarkView = [controller_ bookmarkView];
786  NSView* contentView = [[controller_ window] contentView];
787  NSPoint point = [bookmarkView convertPoint:NSMakePoint(1, 1)
788                                      toView:[contentView superview]];
789
790  EXPECT_TRUE([[contentView hitTest:point] isDescendantOf:bookmarkView]);
791}
792
793@interface BrowserWindowControllerFakeFullscreen : BrowserWindowController {
794 @private
795  // We release the window ourselves, so we don't have to rely on the unittest
796  // doing it for us.
797  base::scoped_nsobject<NSWindow> testFullscreenWindow_;
798}
799@end
800
801class BrowserWindowFullScreenControllerTest : public CocoaProfileTest {
802 public:
803  virtual void SetUp() {
804    CocoaProfileTest::SetUp();
805    ASSERT_TRUE(browser());
806
807    controller_ =
808        [[BrowserWindowControllerFakeFullscreen alloc] initWithBrowser:browser()
809                                                         takeOwnership:NO];
810  }
811
812  virtual void TearDown() {
813    [controller_ close];
814    CocoaProfileTest::TearDown();
815  }
816
817 public:
818  BrowserWindowController* controller_;
819};
820
821// Check if the window is front most or if one of its child windows (such
822// as a status bubble) is front most.
823static bool IsFrontWindow(NSWindow *window) {
824  NSWindow* frontmostWindow = [[NSApp orderedWindows] objectAtIndex:0];
825  return [frontmostWindow isEqual:window] ||
826         [[frontmostWindow parentWindow] isEqual:window];
827}
828
829void WaitForFullScreenTransition() {
830  content::WindowedNotificationObserver observer(
831      chrome::NOTIFICATION_FULLSCREEN_CHANGED,
832      content::NotificationService::AllSources());
833  observer.Wait();
834}
835
836// http://crbug.com/53586
837TEST_F(BrowserWindowFullScreenControllerTest, DISABLED_TestFullscreen) {
838  [controller_ showWindow:nil];
839  EXPECT_FALSE([controller_ isInAnyFullscreenMode]);
840
841  [controller_ enterFullscreenWithChrome];
842  WaitForFullScreenTransition();
843  EXPECT_TRUE([controller_ isInAnyFullscreenMode]);
844
845  [controller_ exitAnyFullscreen];
846  WaitForFullScreenTransition();
847  EXPECT_FALSE([controller_ isInAnyFullscreenMode]);
848}
849
850// If this test fails, it is usually a sign that the bots have some sort of
851// problem (such as a modal dialog up).  This tests is a very useful canary, so
852// please do not mark it as flaky without first verifying that there are no bot
853// problems.
854// http://crbug.com/53586
855TEST_F(BrowserWindowFullScreenControllerTest, DISABLED_TestActivate) {
856  [controller_ showWindow:nil];
857
858  EXPECT_FALSE([controller_ isInAnyFullscreenMode]);
859
860  [controller_ activate];
861  EXPECT_TRUE(IsFrontWindow([controller_ window]));
862
863  [controller_ enterFullscreenWithChrome];
864  WaitForFullScreenTransition();
865  [controller_ activate];
866
867  // No fullscreen window on 10.7+.
868  if (base::mac::IsOSSnowLeopard())
869    EXPECT_TRUE(IsFrontWindow([controller_ createFullscreenWindow]));
870
871  // We have to cleanup after ourselves by unfullscreening.
872  [controller_ exitAnyFullscreen];
873  WaitForFullScreenTransition();
874}
875
876@implementation BrowserWindowControllerFakeFullscreen
877// Override |-createFullscreenWindow| to return a dummy window. This isn't
878// needed to pass the test, but because the dummy window is only 100x100, it
879// prevents the real fullscreen window from flashing up and taking over the
880// whole screen. We have to return an actual window because |-layoutSubviews|
881// looks at the window's frame.
882- (NSWindow*)createFullscreenWindow {
883  if (testFullscreenWindow_.get())
884    return testFullscreenWindow_.get();
885
886  testFullscreenWindow_.reset(
887      [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,400,400)
888                                  styleMask:NSBorderlessWindowMask
889                                    backing:NSBackingStoreBuffered
890                                      defer:NO]);
891  return testFullscreenWindow_.get();
892}
893@end
894
895/* TODO(???): test other methods of BrowserWindowController */
896