summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authorrohitrao@chromium.org <rohitrao@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-17 03:16:58 +0000
committerrohitrao@chromium.org <rohitrao@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-17 03:16:58 +0000
commit91cea0e774541297a46197d87e486cf8a4199775 (patch)
treebe5058bd3eec65a923abbaef68342249574b7ce2 /chrome/browser/cocoa
parent8a1d601fcd2f8bf457ea4e9592e918acc9835f87 (diff)
downloadchromium_src-91cea0e774541297a46197d87e486cf8a4199775.zip
chromium_src-91cea0e774541297a46197d87e486cf8a4199775.tar.gz
chromium_src-91cea0e774541297a46197d87e486cf8a4199775.tar.bz2
First cut at infobars on Mac. These are not expected to be
pretty. Animations and aesthetic appeal will come in a future CL. BUG=http://crbug.com/14462 BUG=http://crbug.com/14937 BUG=http://crbug.com/15839 BUG=http://crbug.com/16487 TEST=Infobars should show up when expected. Review URL: http://codereview.chromium.org/155494 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20930 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm9
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.h4
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller.mm8
-rw-r--r--chrome/browser/cocoa/bookmark_bar_controller_unittest.mm13
-rw-r--r--chrome/browser/cocoa/browser_window_controller.h6
-rw-r--r--chrome/browser/cocoa/browser_window_controller.mm62
-rw-r--r--chrome/browser/cocoa/infobar.h47
-rw-r--r--chrome/browser/cocoa/infobar_container_controller.h80
-rw-r--r--chrome/browser/cocoa/infobar_container_controller.mm191
-rw-r--r--chrome/browser/cocoa/infobar_container_controller_unittest.mm123
-rw-r--r--chrome/browser/cocoa/infobar_controller.h63
-rw-r--r--chrome/browser/cocoa/infobar_controller.mm283
-rw-r--r--chrome/browser/cocoa/infobar_controller_unittest.mm191
-rw-r--r--chrome/browser/cocoa/infobar_test_helper.h152
-rw-r--r--chrome/browser/cocoa/infobar_text_field.h14
-rw-r--r--chrome/browser/cocoa/infobar_text_field.mm20
-rw-r--r--chrome/browser/cocoa/infobar_text_field_unittest.mm67
-rw-r--r--chrome/browser/cocoa/toolbar_controller.h2
-rw-r--r--chrome/browser/cocoa/toolbar_controller.mm3
-rw-r--r--chrome/browser/cocoa/toolbar_controller_unittest.mm1
20 files changed, 1328 insertions, 11 deletions
diff --git a/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm b/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm
index b32f165..158069b 100644
--- a/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_bridge_unittest.mm
@@ -34,10 +34,12 @@ typedef std::pair<GURL,WindowOpenDisposition> OpenInfo;
- (id)initWithProfile:(Profile*)profile
parentView:(NSView*)parentView
- webContentView:(NSView*)webContentView {
+ webContentView:(NSView*)webContentView
+ infoBarsView:(NSView*)infoBarsView {
if ((self = [super initWithProfile:profile
parentView:parentView
webContentView:webContentView
+ infoBarsView:infoBarsView
delegate:self])) {
callbacks_.reset([[NSMutableArray alloc] init]);
}
@@ -113,12 +115,15 @@ TEST_F(BookmarkBarBridgeTest, TestRedirect) {
initWithFrame:NSMakeRect(0,0,100,100)]);
scoped_nsobject<NSView> webView([[NSView alloc]
initWithFrame:NSMakeRect(0,0,100,100)]);
+ scoped_nsobject<NSView> infoBarsView(
+ [[NSView alloc] initWithFrame:NSMakeRect(0,0,100,100)]);
scoped_nsobject<FakeBookmarkBarController>
controller([[FakeBookmarkBarController alloc]
initWithProfile:profile
parentView:parentView.get()
- webContentView:webView.get()]);
+ webContentView:webView.get()
+ infoBarsView:infoBarsView.get()]);
EXPECT_TRUE(controller.get());
scoped_ptr<BookmarkBarBridge> bridge(new BookmarkBarBridge(controller.get(),
model));
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.h b/chrome/browser/cocoa/bookmark_bar_controller.h
index 5d401de..1e98b93 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.h
+++ b/chrome/browser/cocoa/bookmark_bar_controller.h
@@ -46,6 +46,7 @@ class PrefService;
NSView* parentView_; // weak; our parent view
NSView* webContentView_; // weak; where the web goes
+ NSView* infoBarsView_; // weak; where the infobars go
// Bridge from Chrome-style C++ notifications (e.g. derived from
// BookmarkModelObserver)
@@ -60,9 +61,12 @@ class PrefService;
// Initializes the bookmark bar controller with the given browser
// profile, parent view (the toolbar), web content view, and delegate.
// |delegate| is used for opening URLs.
+// TODO(rohitrao, jrg): The bookmark bar shouldn't know about the
+// infoBarsView or the webContentView.
- (id)initWithProfile:(Profile*)profile
parentView:(NSView*)parentView
webContentView:(NSView*)webContentView
+ infoBarsView:(NSView*)infoBarsView
delegate:(id<BookmarkURLOpener>)delegate;
// Returns whether or not the bookmark bar is visible.
diff --git a/chrome/browser/cocoa/bookmark_bar_controller.mm b/chrome/browser/cocoa/bookmark_bar_controller.mm
index 678d4ef..7d1ea55 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller.mm
@@ -44,6 +44,7 @@ const CGFloat kBookmarkHorizontalPadding = 8.0;
- (id)initWithProfile:(Profile*)profile
parentView:(NSView*)parentView
webContentView:(NSView*)webContentView
+ infoBarsView:(NSView*)infoBarsView
delegate:(id<BookmarkURLOpener>)delegate {
if ((self = [super initWithNibName:@"BookmarkBar"
bundle:mac_util::MainAppBundle()])) {
@@ -51,6 +52,7 @@ const CGFloat kBookmarkHorizontalPadding = 8.0;
preferences_ = profile->GetPrefs();
parentView_ = parentView;
webContentView_ = webContentView;
+ infoBarsView_ = infoBarsView;
delegate_ = delegate;
}
return self;
@@ -120,6 +122,7 @@ const CGFloat kBookmarkHorizontalPadding = 8.0;
NSRect superframe = [parentView_ frame];
NSRect frame = [[self view] frame];
NSRect webframe = [webContentView_ frame];
+ NSRect infoframe = [infoBarsView_ frame];
if (apply) {
superframe.size.height += kBookmarkBarSuperviewHeightAdjustment;
// TODO(jrg): y=0 if we add the bookmark bar before the parent
@@ -131,11 +134,13 @@ const CGFloat kBookmarkHorizontalPadding = 8.0;
webframe.size.height -= kBookmarkBarWebframeHeightAdjustment;
}
frame.size.height += kBookmarkBarHeight;
+ infoframe.origin.y -= kBookmarkBarWebframeHeightAdjustment;
} else {
superframe.size.height -= kBookmarkBarSuperviewHeightAdjustment;
superframe.origin.y += kBookmarkBarSuperviewHeightAdjustment;
frame.size.height -= kBookmarkBarHeight;
webframe.size.height += kBookmarkBarWebframeHeightAdjustment;
+ infoframe.origin.y += kBookmarkBarWebframeHeightAdjustment;
}
// TODO(jrg): Animators can be a little fussy. Setting these three
@@ -145,16 +150,19 @@ const CGFloat kBookmarkHorizontalPadding = 8.0;
if (1 /* immediately */) {
[parentView_ setFrame:superframe];
[webContentView_ setFrame:webframe];
+ [infoBarsView_ setFrame:infoframe];
[[self view] setFrame:frame];
} else {
[[parentView_ animator] setFrame:superframe];
[[webContentView_ animator] setFrame:webframe];
+ [[infoBarsView_ animator] setFrame:infoframe];
[[[self view] animator] setFrame:frame];
}
[[self view] setNeedsDisplay:YES];
[parentView_ setNeedsDisplay:YES];
[webContentView_ setNeedsDisplay:YES];
+ [infoBarsView_ setNeedsDisplay:YES];
}
- (BOOL)isBookmarkBarVisible {
diff --git a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
index 1103556..f08978e 100644
--- a/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
+++ b/chrome/browser/cocoa/bookmark_bar_controller_unittest.mm
@@ -30,25 +30,32 @@
namespace {
static const int kContentAreaHeight = 500;
+static const int kInfoBarViewHeight = 30;
class BookmarkBarControllerTest : public testing::Test {
public:
BookmarkBarControllerTest() {
NSRect content_frame = NSMakeRect(0, 0, 800, kContentAreaHeight);
+ // |infobar_frame| is set to be directly above |content_frame|.
+ NSRect infobar_frame = NSMakeRect(0, kContentAreaHeight,
+ 800, kInfoBarViewHeight);
NSRect parent_frame = NSMakeRect(0, 0, 800, 50);
content_area_.reset([[NSView alloc] initWithFrame:content_frame]);
+ infobar_view_.reset([[NSView alloc] initWithFrame:infobar_frame]);
parent_view_.reset([[NSView alloc] initWithFrame:parent_frame]);
[parent_view_ setHidden:YES];
bar_.reset(
[[BookmarkBarController alloc] initWithProfile:helper_.profile()
parentView:parent_view_.get()
webContentView:content_area_.get()
+ infoBarsView:infobar_view_.get()
delegate:nil]);
[bar_ view]; // force loading of the nib
}
CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc...
scoped_nsobject<NSView> content_area_;
+ scoped_nsobject<NSView> infobar_view_;
scoped_nsobject<NSView> parent_view_;
BrowserTestHelper helper_;
scoped_nsobject<BookmarkBarController> bar_;
@@ -70,14 +77,20 @@ TEST_F(BookmarkBarControllerTest, ShowHide) {
EXPECT_TRUE([bar_ isBookmarkBarVisible]);
EXPECT_FALSE([[bar_ view] isHidden]);
NSRect content_frame = [content_area_ frame];
+ NSRect infobar_frame = [infobar_view_ frame];
EXPECT_NE(content_frame.size.height, kContentAreaHeight);
+ EXPECT_EQ(NSMaxY(content_frame), NSMinY(infobar_frame));
+ EXPECT_EQ(kInfoBarViewHeight, infobar_frame.size.height);
EXPECT_GT([[bar_ view] frame].size.height, 0);
[bar_ toggleBookmarkBar];
EXPECT_FALSE([bar_ isBookmarkBarVisible]);
EXPECT_TRUE([[bar_ view] isHidden]);
content_frame = [content_area_ frame];
+ infobar_frame = [infobar_view_ frame];
EXPECT_EQ(content_frame.size.height, kContentAreaHeight);
+ EXPECT_EQ(NSMaxY(content_frame), NSMinY(infobar_frame));
+ EXPECT_EQ(kInfoBarViewHeight, infobar_frame.size.height);
EXPECT_EQ([[bar_ view] frame].size.height, 0);
}
diff --git a/chrome/browser/cocoa/browser_window_controller.h b/chrome/browser/cocoa/browser_window_controller.h
index c2c47c1..104742c 100644
--- a/chrome/browser/cocoa/browser_window_controller.h
+++ b/chrome/browser/cocoa/browser_window_controller.h
@@ -23,6 +23,7 @@ class BrowserWindow;
class BrowserWindowCocoa;
@class DownloadShelfController;
@class FindBarCocoaController;
+@class InfoBarContainerController;
class LocationBar;
class StatusBubble;
class TabContents;
@@ -55,6 +56,7 @@ class TabStripModelObserverBridge;
scoped_nsobject<TitlebarController> titlebarController_;
scoped_nsobject<TabStripController> tabStripController_;
scoped_nsobject<FindBarCocoaController> findBarCocoaController_;
+ scoped_nsobject<InfoBarContainerController> infoBarContainerController_;
scoped_ptr<StatusBubble> statusBubble_;
scoped_nsobject<DownloadShelfController> downloadShelfController_;
scoped_nsobject<GTMTheme> theme_;
@@ -122,6 +124,10 @@ class TabStripModelObserverBridge;
// Returns fullscreen state.
- (BOOL)isFullscreen;
+// Sent when the infobar view has been resized and other content needs
+// to be shifted around it.
+- (void)infoBarResized:(float)newHeight;
+
// The user changed the theme.
- (void)userChangedTheme;
diff --git a/chrome/browser/cocoa/browser_window_controller.mm b/chrome/browser/cocoa/browser_window_controller.mm
index f43724c..4c3f2be 100644
--- a/chrome/browser/cocoa/browser_window_controller.mm
+++ b/chrome/browser/cocoa/browser_window_controller.mm
@@ -24,6 +24,7 @@
#import "chrome/browser/cocoa/find_bar_cocoa_controller.h"
#include "chrome/browser/cocoa/find_bar_bridge.h"
#import "chrome/browser/cocoa/fullscreen_window.h"
+#import "chrome/browser/cocoa/infobar_container_controller.h"
#import "chrome/browser/cocoa/status_bubble_mac.h"
#import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
#import "chrome/browser/cocoa/tab_strip_view.h"
@@ -64,6 +65,7 @@ const int kWindowGradientHeight = 24;
@interface BrowserWindowController(Private)
+- (void)positionInfoBar;
- (void)positionToolbar;
- (void)removeToolbar;
- (void)installIncognitoBadge;
@@ -135,14 +137,6 @@ willPositionSheet:(NSWindow*)sheet
[self setTheme];
- // Register ourselves for frame changed notifications from the
- // tabContentArea.
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(tabContentAreaFrameChanged:)
- name:nil
- object:[self tabContentArea]];
-
// Get the most appropriate size for the window, then enforce the
// minimum width and height. The window shim will handle flipping
// the coordinates for us so we can use it to save some code.
@@ -169,6 +163,14 @@ willPositionSheet:(NSWindow*)sheet
// Puts the incognito badge on the window frame, if necessary.
[self installIncognitoBadge];
+ // Create the infobar container view, so we can pass it to the
+ // ToolbarController, but do not position the view until after the
+ // toolbar is in place, as positionToolbar will move the tab content area.
+ infoBarContainerController_.reset(
+ [[InfoBarContainerController alloc]
+ initWithTabStripModel:(browser_->tabstrip_model())
+ browserWindowController:self]);
+
// Create a controller for the toolbar, giving it the toolbar model object
// and the toolbar view from the nib. The controller will handle
// registering for the appropriate command state changes from the back-end.
@@ -177,10 +179,25 @@ willPositionSheet:(NSWindow*)sheet
commands:browser->command_updater()
profile:browser->profile()
webContentView:[self tabContentArea]
+ infoBarsView:[infoBarContainerController_ view]
bookmarkDelegate:self]);
[self positionToolbar];
[self fixWindowGradient];
+ // Put the infobar container view into the window above the
+ // tabcontentarea. There are no infobars when starting up, so its
+ // initial height is 0.
+ [self positionInfoBar];
+
+ // Register ourselves for frame changed notifications from the
+ // tabContentArea. This has to come after all of the resizing and
+ // positioning above.
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(tabContentAreaFrameChanged:)
+ name:nil
+ object:[self tabContentArea]];
+
// Create the bridge for the status bubble.
statusBubble_.reset(new StatusBubbleMac([self window]));
@@ -212,6 +229,8 @@ willPositionSheet:(NSWindow*)sheet
// delegate so nothing tries to call us back in the meantime as part of
// window destruction.
[window_ setDelegate:nil];
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
@@ -751,7 +770,6 @@ willPositionSheet:(NSWindow*)sheet
// TabContents.
#if 0
// TODO(pinkerton):Update as more things become window-specific
- infobar_container_->ChangeTabContents(new_contents);
contents_container_->SetTabContents(new_contents);
#endif
@@ -778,6 +796,22 @@ willPositionSheet:(NSWindow*)sheet
[self applyTheme];
}
+// TODO(rohitrao, jrg): Move this logic out of BrowserWindowController?
+- (void)infoBarResized:(float)newHeight {
+ // The top edge of the infobar is fixed.
+ NSView* infoBarView = [infoBarContainerController_ view];
+ NSRect infoBarFrame = [infoBarView frame];
+ int maxY = NSMaxY(infoBarFrame);
+ int minY = maxY - newHeight;
+
+ [infoBarView setFrame:NSMakeRect(infoBarFrame.origin.x, minY,
+ infoBarFrame.size.width, newHeight)];
+
+ NSRect contentFrame = [[self tabContentArea] frame];
+ contentFrame.size.height = minY - contentFrame.origin.y;
+ [[self tabContentArea] setFrame:contentFrame];
+}
+
- (GTMTheme *)gtm_themeForWindow:(NSWindow*)window {
return theme_ ? theme_ : [GTMTheme defaultTheme];
}
@@ -786,6 +820,16 @@ willPositionSheet:(NSWindow*)sheet
@implementation BrowserWindowController (Private)
+// TODO(rohitrao, jrg): Move this logic out of BrowserWindowController?
+- (void)positionInfoBar {
+ NSView* infoBarView = [infoBarContainerController_ view];
+ NSRect infoBarFrame = [[self tabContentArea] frame];
+ infoBarFrame.origin.y = NSMaxY(infoBarFrame);
+ infoBarFrame.size.height = 0;
+ [infoBarView setFrame:infoBarFrame];
+ [[[self window] contentView] addSubview:infoBarView];
+}
+
// If |add| is YES:
// Position |toolbarView_| below the tab strip, but not as a
// sibling. The toolbar is part of the window's contentView, mainly
diff --git a/chrome/browser/cocoa/infobar.h b/chrome/browser/cocoa/infobar.h
new file mode 100644
index 0000000..0ebd2a9
--- /dev/null
+++ b/chrome/browser/cocoa/infobar.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COCOA_INFOBAR_H_
+#define CHROME_BROWSER_COCOA_INFOBAR_H_
+
+#include "base/logging.h" // for DCHECK
+
+@class InfoBarController;
+
+// A C++ wrapper around an Objective-C InfoBarController. This class
+// exists solely to be the return value for InfoBarDelegate::CreateInfoBar(),
+// as defined in chrome/browser/tab_contents/infobar_delegate.h. This
+// class would be analogous to the various bridge classes we already
+// have, but since there is no pre-defined InfoBar interface, it is
+// easier to simply throw away this object and deal with the
+// controller directly rather than pass messages through a bridge.
+//
+// Callers should delete the returned InfoBar immediately after
+// calling CreateInfoBar(), as the returned InfoBar* object is not
+// pointed to by anyone. Expected usage:
+//
+// scoped_ptr<InfoBar> infobar(delegate->CreateInfoBar());
+// InfoBarController* controller = infobar->controller();
+// // Do something with the controller, and save a pointer so it can be
+// // deleted later. |infobar| will be deleted automatically.
+
+class InfoBar {
+ public:
+ InfoBar(InfoBarController* controller) {
+ DCHECK(controller);
+ controller_ = controller;
+ }
+
+ InfoBarController* controller() {
+ return controller_;
+ }
+
+ private:
+ // Pointer to the infobar controller. Is never null.
+ InfoBarController* controller_; // weak
+
+ DISALLOW_COPY_AND_ASSIGN(InfoBar);
+};
+
+#endif // CHROME_BROWSER_COCOA_INFOBAR_H_
diff --git a/chrome/browser/cocoa/infobar_container_controller.h b/chrome/browser/cocoa/infobar_container_controller.h
new file mode 100644
index 0000000..a565a10
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_container_controller.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#include "base/scoped_ptr.h"
+#include "chrome/common/notification_registrar.h"
+
+@class BrowserWindowController;
+class InfoBarDelegate;
+class InfoBarNotificationObserver;
+class TabContents;
+class TabStripModel;
+class TabStripModelObserverBridge;
+
+// Controller for the infobar container view, which is the superview
+// of all the infobar views. This class owns zero or more
+// InfoBarControllers, which manage the infobar views. This class
+// also receives tab strip model notifications and handles
+// adding/removing infobars when needed.
+@interface InfoBarContainerController : NSViewController {
+ @private
+ // Needed to send infoBarResized: messages when infobars are added or removed.
+ BrowserWindowController* browserController_; // weak, owns us.
+
+ // The TabContents we are currently showing infobars for.
+ TabContents* currentTabContents_; // weak
+
+ // Holds the InfoBarControllers currently owned by this container.
+ scoped_nsobject<NSMutableArray> infobarControllers_;
+
+ // Lets us get TabChanged/TabDetachedAt notifications.
+ scoped_ptr<TabStripModelObserverBridge> tabObserver_;
+
+ // Lets us registers for INFOBAR_ADDED/INFOBAR_REMOVED
+ // notifications. The actual notifications are sent to the
+ // InfoBarNotificationObserver object, which proxies them back to us.
+ NotificationRegistrar registrar_;
+ scoped_ptr<InfoBarNotificationObserver> infoBarObserver_;
+}
+
+- (id)initWithTabStripModel:(TabStripModel*)model
+ browserWindowController:(BrowserWindowController*)controller;
+
+// Informs the selected TabContents that the infobars for the given
+// |delegate| need to be removed. Does not remove any infobar views
+// directly, as they will be removed when handling the subsequent
+// INFOBAR_REMOVED notification. Does not notify |delegate| that the
+// infobar was closed.
+- (void)removeDelegate:(InfoBarDelegate*)delegate;
+
+@end
+
+
+@interface InfoBarContainerController (ForTheObserverAndTesting)
+
+// Adds an infobar view for the given delegate. Callers must call
+// positionInfoBarsAndRedraw after calling this method.
+- (void)addInfoBar:(InfoBarDelegate*)delegate;
+
+// Removes all the infobar views for a given delegate. Callers must
+// call positionInfoBarsAndRedraw after calling this method.
+- (void)removeInfoBarsForDelegate:(InfoBarDelegate*)delegate;
+
+// Positions the infobar views in the container view and notifies
+// |browser_controller_| that it needs to resize the container view.
+- (void)positionInfoBarsAndRedraw;
+
+@end
+
+
+@interface InfoBarContainerController (JustForTesting)
+
+// Removes all infobar views. Callers must call
+// positionInfoBarsAndRedraw() after calling this method.
+- (void)removeAllInfoBars;
+
+@end
diff --git a/chrome/browser/cocoa/infobar_container_controller.mm b/chrome/browser/cocoa/infobar_container_controller.mm
new file mode 100644
index 0000000..06eaf67
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_container_controller.mm
@@ -0,0 +1,191 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/logging.h"
+#include "base/mac_util.h"
+#import "chrome/browser/cocoa/browser_window_controller.h"
+#include "chrome/browser/cocoa/infobar.h"
+#import "chrome/browser/cocoa/infobar_container_controller.h"
+#import "chrome/browser/cocoa/infobar_controller.h"
+#include "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/notification_service.h"
+#include "skia/ext/skia_utils_mac.h"
+
+// C++ class that receives INFOBAR_ADDED and INFOBAR_REMOVED
+// notifications and proxies them back to |controller|.
+class InfoBarNotificationObserver : public NotificationObserver {
+ public:
+ InfoBarNotificationObserver(InfoBarContainerController* controller)
+ : controller_(controller) {
+ }
+
+ private:
+ // NotificationObserver implementation
+ void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::TAB_CONTENTS_INFOBAR_ADDED:
+ [controller_ addInfoBar:Details<InfoBarDelegate>(details).ptr()];
+ break;
+ case NotificationType::TAB_CONTENTS_INFOBAR_REMOVED:
+ [controller_
+ removeInfoBarsForDelegate:Details<InfoBarDelegate>(details).ptr()];
+ break;
+ default:
+ NOTREACHED(); // we don't ask for anything else!
+ break;
+ }
+
+ [controller_ positionInfoBarsAndRedraw];
+ }
+
+ InfoBarContainerController* controller_; // weak, owns us.
+};
+
+
+@interface InfoBarContainerController (PrivateMethods)
+// Returns the desired height of the container view, computed by
+// adding together the heights of all its subviews.
+- (float)desiredHeight;
+
+// Modifies this container to display infobars for the given
+// |contents|. Registers for INFOBAR_ADDED and INFOBAR_REMOVED
+// notifications for |contents|. If we are currently showing any
+// infobars, removes them first and deregisters for any
+// notifications. |contents| can be NULL, in which case no infobars
+// are shown and no notifications are registered for.
+- (void)changeTabContents:(TabContents*)contents;
+
+@end
+
+
+@implementation InfoBarContainerController
+- (id)initWithTabStripModel:(TabStripModel*)model
+ browserWindowController:(BrowserWindowController*)controller {
+ DCHECK(controller);
+ if ((self = [super initWithNibName:@"InfoBarContainer"
+ bundle:mac_util::MainAppBundle()])) {
+ browserController_ = controller;
+ tabObserver_.reset(new TabStripModelObserverBridge(model, self));
+ infoBarObserver_.reset(new InfoBarNotificationObserver(self));
+
+ // NSMutableArray needs an initial capacity, and we rarely ever see
+ // more than two infobars at a time, so that seems like a good choice.
+ infobarControllers_.reset([[NSMutableArray alloc] initWithCapacity:2]);
+ }
+ return self;
+}
+
+- (void)dealloc {
+ DCHECK([infobarControllers_ count] == 0);
+ [super dealloc];
+}
+
+- (void)removeDelegate:(InfoBarDelegate*)delegate {
+ DCHECK(currentTabContents_);
+ currentTabContents_->RemoveInfoBar(delegate);
+}
+
+// TabStripModelObserverBridge notifications
+- (void)selectTabWithContents:(TabContents*)newContents
+ previousContents:(TabContents*)oldContents
+ atIndex:(NSInteger)index
+ userGesture:(bool)wasUserGesture {
+ [self changeTabContents:newContents];
+}
+
+- (void)tabDetachedWithContents:(TabContents*)contents
+ atIndex:(NSInteger)index {
+ [self changeTabContents:NULL];
+}
+
+@end
+
+@implementation InfoBarContainerController (PrivateMethods)
+
+- (float)desiredHeight {
+ float height = 0;
+
+ for (InfoBarController* controller in infobarControllers_.get()) {
+ height += [[controller view] frame].size.height;
+ }
+
+ return height;
+}
+
+- (void)changeTabContents:(TabContents*)contents {
+ registrar_.RemoveAll();
+ [self removeAllInfoBars];
+
+ currentTabContents_ = contents;
+ if (currentTabContents_) {
+ for (int i = 0; i < currentTabContents_->infobar_delegate_count(); ++i) {
+ [self addInfoBar:currentTabContents_->GetInfoBarDelegateAt(i)];
+ }
+
+ Source<TabContents> source(currentTabContents_);
+ registrar_.Add(infoBarObserver_.get(),
+ NotificationType::TAB_CONTENTS_INFOBAR_ADDED, source);
+ registrar_.Add(infoBarObserver_.get(),
+ NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, source);
+ }
+
+ [self positionInfoBarsAndRedraw];
+}
+
+- (void)addInfoBar:(InfoBarDelegate*)delegate {
+ scoped_ptr<InfoBar> infobar(delegate->CreateInfoBar());
+ InfoBarController* controller = infobar->controller();
+ [controller setContainerController:self];
+ [[self view] addSubview:[controller view]];
+ [infobarControllers_ addObject:[controller autorelease]];
+}
+
+- (void)removeInfoBarsForDelegate:(InfoBarDelegate*)delegate {
+ for (InfoBarController* controller in
+ [NSArray arrayWithArray:infobarControllers_.get()]) {
+ if ([controller delegate] == delegate) {
+ // This code can be executed while -[InfoBarController closeInfoBar] is
+ // still on the stack, so we retain and autorelease the controller to
+ // prevent it from being dealloc'ed too early.
+ [[controller retain] autorelease];
+ [[controller view] removeFromSuperview];
+ [infobarControllers_ removeObject:controller];
+ }
+ }
+}
+
+- (void)removeAllInfoBars {
+ for (InfoBarController* controller in infobarControllers_.get()) {
+ [[controller view] removeFromSuperview];
+ }
+ [infobarControllers_ removeAllObjects];
+}
+
+- (void)positionInfoBarsAndRedraw {
+ NSRect containerBounds = [[self view] bounds];
+ int minY = 0;
+
+ // Stack the infobars at the bottom of the view, starting with the
+ // last infobar and working our way to the front of the array. This
+ // way we ensure that the first infobar added shows up on top, with
+ // the others below.
+ for (InfoBarController* controller in
+ [infobarControllers_ reverseObjectEnumerator]) {
+ NSView* view = [controller view];
+ NSRect frame = [view frame];
+ frame.origin.x = NSMinX(containerBounds);
+ frame.size.width = NSWidth(containerBounds);
+ frame.origin.y = minY;
+ minY += frame.size.height;
+ // TODO(rohitrao, jrg): Replace with an animator.
+ [view setFrame:frame];
+ }
+
+ [browserController_ infoBarResized:[self desiredHeight]];
+}
+
+@end
diff --git a/chrome/browser/cocoa/infobar_container_controller_unittest.mm b/chrome/browser/cocoa/infobar_container_controller_unittest.mm
new file mode 100644
index 0000000..77e8e82
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_container_controller_unittest.mm
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsautorelease_pool.h"
+#include "base/scoped_nsobject.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#import "chrome/browser/cocoa/browser_window_controller.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/infobar_container_controller.h"
+#include "chrome/browser/cocoa/infobar_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Objective-C classes must be defined outside the namespace.
+@interface BrowserWindowControllerPong : BrowserWindowController {
+ BOOL pong_;
+}
+@property(readonly) BOOL pong;
+@end
+
+@implementation BrowserWindowControllerPong
+@synthesize pong = pong_;
+
+- (id)initWithBrowser:(Browser*)browser {
+ if ((self = [super initWithBrowser:browser takeOwnership:NO])) {
+ pong_ = NO;
+ }
+ return self;
+}
+
+- (void)infoBarResized:(float)newHeight {
+ pong_ = TRUE;
+}
+@end
+
+namespace {
+
+class InfoBarContainerControllerTest : public testing::Test {
+ virtual void SetUp() {
+ browserController_.reset([[BrowserWindowControllerPong alloc]
+ initWithBrowser:browser_helper_.browser()]);
+ TabStripModel* model = browser_helper_.browser()->tabstrip_model();
+ controller_.reset([[InfoBarContainerController alloc]
+ initWithTabStripModel:model
+ browserWindowController:browserController_]);
+ }
+
+ public:
+ // Order is very important here. We want the controller deleted
+ // before the pool, and want the pool deleted before
+ // BrowserTestHelper.
+ CocoaTestHelper cocoa_helper_;
+ BrowserTestHelper browser_helper_;
+ base::ScopedNSAutoreleasePool pool_;
+ scoped_nsobject<BrowserWindowControllerPong> browserController_;
+ scoped_nsobject<InfoBarContainerController> controller_;
+};
+
+TEST_F(InfoBarContainerControllerTest, Show) {
+ // Make sure the container's view is non-nil and draws without crashing.
+ NSView* view = [controller_ view];
+ EXPECT_TRUE(view != nil);
+
+ [cocoa_helper_.contentView() addSubview:view];
+}
+
+TEST_F(InfoBarContainerControllerTest, BWCPong) {
+ // Call positionInfoBarsAndResize and check that the BWC got a resize message.
+ [controller_ positionInfoBarsAndRedraw];
+ EXPECT_TRUE([browserController_ pong]);
+}
+
+TEST_F(InfoBarContainerControllerTest, AddAndRemoveInfoBars) {
+ NSView* view = [controller_ view];
+ [cocoa_helper_.contentView() addSubview:view];
+
+ // Add three infobars, one of each type, and then remove them.
+ // After each step check to make sure we have the correct number of
+ // infobar subviews.
+ MockAlertInfoBarDelegate alertDelegate;
+ MockLinkInfoBarDelegate linkDelegate;
+ MockConfirmInfoBarDelegate confirmDelegate;
+
+ [controller_ addInfoBar:&alertDelegate];
+ EXPECT_EQ(1U, [[view subviews] count]);
+
+ [controller_ addInfoBar:&linkDelegate];
+ EXPECT_EQ(2U, [[view subviews] count]);
+
+ [controller_ addInfoBar:&confirmDelegate];
+ EXPECT_EQ(3U, [[view subviews] count]);
+
+ // Just to mix things up, remove them in a different order.
+ [controller_ removeInfoBarsForDelegate:&linkDelegate];
+ EXPECT_EQ(2U, [[view subviews] count]);
+
+ [controller_ removeInfoBarsForDelegate:&confirmDelegate];
+ EXPECT_EQ(1U, [[view subviews] count]);
+
+ [controller_ removeInfoBarsForDelegate:&alertDelegate];
+ EXPECT_EQ(0U, [[view subviews] count]);
+}
+
+TEST_F(InfoBarContainerControllerTest, RemoveAllInfoBars) {
+ NSView* view = [controller_ view];
+ [cocoa_helper_.contentView() addSubview:view];
+
+ // Add three infobars and then remove them all.
+ MockAlertInfoBarDelegate alertDelegate;
+ MockLinkInfoBarDelegate linkDelegate;
+ MockConfirmInfoBarDelegate confirmDelegate;
+
+ [controller_ addInfoBar:&alertDelegate];
+ [controller_ addInfoBar:&linkDelegate];
+ [controller_ addInfoBar:&confirmDelegate];
+ EXPECT_EQ(3U, [[view subviews] count]);
+
+ [controller_ removeAllInfoBars];
+ EXPECT_EQ(0U, [[view subviews] count]);
+}
+} // namespace
diff --git a/chrome/browser/cocoa/infobar_controller.h b/chrome/browser/cocoa/infobar_controller.h
new file mode 100644
index 0000000..b8d7231
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_controller.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+@class InfoBarContainerController;
+class InfoBarDelegate;
+
+// A controller for an infobar in the browser window. There is one
+// controller per infobar view. The base InfoBarController is able to
+// draw an icon, a text message, and a close button. Subclasses can
+// override addAdditionalControls to customize the UI.
+@interface InfoBarController : NSViewController {
+ @private
+ InfoBarContainerController* containerController_; // weak, owns us
+
+ @protected
+ InfoBarDelegate* delegate_; // weak
+ IBOutlet NSImageView* image_;
+ IBOutlet NSTextField* label_;
+ IBOutlet NSButton* closeButton_;
+};
+
+// Initializes a new InfoBarController.
+- (id)initWithDelegate:(InfoBarDelegate*)delegate;
+
+// Dismisses the infobar without taking any action.
+- (IBAction)dismiss:(id)sender;
+
+// Subclasses can override this method to add additional controls to
+// the infobar view. This method is called by awakeFromNib. The
+// default implementation does nothing.
+- (void)addAdditionalControls;
+
+@property(assign, nonatomic) InfoBarContainerController* containerController;
+@property(readonly) InfoBarDelegate* delegate;
+
+@end
+
+/////////////////////////////////////////////////////////////////////////
+// InfoBarController subclasses, one for each InfoBarDelegate
+// subclass. Each of these subclasses overrides addAdditionalControls to
+// configure its view as necessary.
+
+@interface AlertInfoBarController : InfoBarController {
+}
+@end
+
+
+@interface LinkInfoBarController : InfoBarController {
+}
+// Called when there is a click on the link in the infobar.
+- (void)linkClicked;
+@end
+
+
+@interface ConfirmInfoBarController : InfoBarController {
+}
+// Called when the ok and cancel buttons are clicked.
+- (IBAction)ok:(id)sender;
+- (IBAction)cancel:(id)sender;
+@end
diff --git a/chrome/browser/cocoa/infobar_controller.mm b/chrome/browser/cocoa/infobar_controller.mm
new file mode 100644
index 0000000..5f8083e
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_controller.mm
@@ -0,0 +1,283 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac_util.h"
+#include "base/sys_string_conversions.h"
+#include "chrome/browser/cocoa/infobar.h"
+#import "chrome/browser/cocoa/infobar_container_controller.h"
+#import "chrome/browser/cocoa/infobar_controller.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "skia/ext/skia_utils_mac.h"
+#include "webkit/glue/window_open_disposition.h"
+
+
+@interface InfoBarController (PrivateMethods)
+// Closes the infobar by calling RemoveDelegate on the container.
+// This will remove the infobar from its associated TabContents as
+// well as trigger the deletion of this InfoBarController. Once the
+// delegate is removed from the container, it is no longer needed, so
+// we ask it to delete itself.
+- (void)closeInfoBar;
+@end
+
+@implementation InfoBarController
+
+@synthesize containerController = containerController_;
+@synthesize delegate = delegate_;
+
+- (id)initWithDelegate:(InfoBarDelegate*)delegate {
+ DCHECK(delegate);
+ if ((self = [super initWithNibName:@"InfoBar"
+ bundle:mac_util::MainAppBundle()])) {
+ delegate_ = delegate;
+ }
+ return self;
+}
+
+// All infobars have an icon, so we set up the icon in the base class
+// awakeFromNib.
+- (void)awakeFromNib {
+ if (delegate_->GetIcon()) {
+ [image_ setImage:gfx::SkBitmapToNSImage(*(delegate_->GetIcon()))];
+ }
+
+ [self addAdditionalControls];
+}
+
+// Called when someone clicks on the close button.
+- (void)dismiss:(id)sender {
+ [self closeInfoBar];
+}
+
+- (void)addAdditionalControls {
+ // Default implementation does nothing.
+}
+
+@end
+
+@implementation InfoBarController (PrivateMethods)
+- (void)closeInfoBar {
+ // Calling RemoveDelegate() triggers notifications which will remove
+ // the infobar view from the infobar container. At that point it is
+ // safe to ask the delegate to delete itself.
+ DCHECK(delegate_);
+ [containerController_ removeDelegate:delegate_];
+ delegate_->InfoBarClosed();
+ delegate_ = NULL;
+}
+@end
+
+
+/////////////////////////////////////////////////////////////////////////
+// AlertInfoBarController implementation
+
+@implementation AlertInfoBarController
+
+// Alert infobars have a text message.
+- (void)addAdditionalControls {
+ AlertInfoBarDelegate* delegate = delegate_->AsAlertInfoBarDelegate();
+ [label_ setStringValue:base::SysWideToNSString(
+ delegate->GetMessageText())];
+}
+
+@end
+
+
+/////////////////////////////////////////////////////////////////////////
+// LinkInfoBarController implementation
+
+@implementation LinkInfoBarController
+
+// Link infobars have a text message, of which part is linkified. We
+// use an NSAttributedString to display styled text, and we set a
+// NSLink attribute on the hyperlink portion of the message. Infobars
+// use a custom NSTextField subclass, which allows us to override
+// textView:clickedOnLink:atIndex: and intercept clicks.
+//
+// TODO(rohitrao): Using an NSTextField here has some weird UI side
+// effects, such as showing the wrong cursor at times. Explore other
+// solutions.
+- (void)addAdditionalControls {
+ LinkInfoBarDelegate* delegate = delegate_->AsLinkInfoBarDelegate();
+ size_t offset = std::wstring::npos;
+ std::wstring message = delegate->GetMessageTextWithOffset(&offset);
+
+ // Create an attributes dictionary for the entire message. We have
+ // to expicitly set the font to the system font, because
+ // NSAttributedString defaults to Helvetica 12. We also override
+ // the cursor to give us the normal cursor rather than the text
+ // insertion cursor.
+ NSMutableDictionary* linkAttributes =
+ [NSMutableDictionary dictionaryWithObject:[NSCursor arrowCursor]
+ forKey:NSCursorAttributeName];
+ [linkAttributes setObject:[NSFont systemFontOfSize:[NSFont systemFontSize]]
+ forKey:NSFontAttributeName];
+
+ // Create the attributed string for the main message text.
+ NSMutableAttributedString* infoText =
+ [[NSMutableAttributedString alloc]
+ initWithString:base::SysWideToNSString(message)];
+ [infoText addAttributes:linkAttributes
+ range:NSMakeRange(0, [infoText length])];
+
+ // Add additional attributes to style the link text appropriately as
+ // well as linkify it. We use an empty string for the NSLink
+ // attribute because the actual object we pass doesn't matter, but
+ // it cannot be nil.
+ [linkAttributes setObject:[NSColor blueColor]
+ forKey:NSForegroundColorAttributeName];
+ [linkAttributes setObject:[NSNumber numberWithBool:YES]
+ forKey:NSUnderlineStyleAttributeName];
+ [linkAttributes setObject:[NSCursor pointingHandCursor]
+ forKey:NSCursorAttributeName];
+ [linkAttributes setObject:[NSString string] // dummy value
+ forKey:NSLinkAttributeName];
+
+ // Insert the link text into the string at the appropriate offset.
+ [infoText insertAttributedString:
+ [[[NSAttributedString alloc]
+ initWithString:base::SysWideToNSString(delegate->GetLinkText())
+ attributes:linkAttributes] autorelease]
+ atIndex:offset];
+
+ // Update the label view with the new text. The view must be
+ // selectable and allow editing text attributes for the
+ // linkification to work correctly.
+ [label_ setAllowsEditingTextAttributes: YES];
+ [label_ setSelectable: YES];
+ [label_ setAttributedStringValue:infoText];
+}
+
+// Called when someone clicks on the link in the infobar. This method
+// is called by the InfobarTextField on its delegate (the
+// LinkInfoBarController).
+- (void)linkClicked {
+ // TODO(rohitrao): Set the disposition correctly based on modifier keys.
+ WindowOpenDisposition disposition = CURRENT_TAB;
+ if (delegate_->AsLinkInfoBarDelegate()->LinkClicked(disposition))
+ [self closeInfoBar];
+}
+
+@end
+
+
+/////////////////////////////////////////////////////////////////////////
+// ConfirmInfoBarController implementation
+
+@implementation ConfirmInfoBarController
+
+// Called when someone clicks on the "OK" button.
+- (IBAction)ok:(id)sender {
+ if (delegate_->AsConfirmInfoBarDelegate()->Accept())
+ [self closeInfoBar];
+}
+
+// Called when someone clicks on the "Cancel" button.
+- (IBAction)cancel:(id)sender {
+ if (delegate_->AsConfirmInfoBarDelegate()->Cancel())
+ [self closeInfoBar];
+}
+
+// Confirm infobars can have OK and/or cancel buttons, depending on
+// the return value of GetButtons(). We create each button if
+// required and position them to the left of the close button.
+- (void)addAdditionalControls {
+ ConfirmInfoBarDelegate* delegate = delegate_->AsConfirmInfoBarDelegate();
+ [label_ setStringValue:base::SysWideToNSString(delegate->GetMessageText())];
+
+ int visibleButtons = delegate->GetButtons();
+ NSButton *okButton = nil;
+ NSButton *cancelButton = nil;
+
+ // Create the OK button if needed.
+ if (visibleButtons & ConfirmInfoBarDelegate::BUTTON_OK) {
+ okButton = [[[NSButton alloc] initWithFrame:NSZeroRect] autorelease];
+ [okButton setBezelStyle:NSRoundedBezelStyle];
+ [okButton setTitle:base::SysWideToNSString(
+ delegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_OK))];
+ [okButton sizeToFit];
+ [okButton setAutoresizingMask:NSViewMinXMargin];
+ [okButton setTarget:self];
+ [okButton setAction:@selector(ok:)];
+ }
+
+ // Create the cancel button if needed.
+ if (visibleButtons & ConfirmInfoBarDelegate::BUTTON_CANCEL) {
+ cancelButton = [[[NSButton alloc] initWithFrame:NSZeroRect] autorelease];
+ [cancelButton setBezelStyle:NSRoundedBezelStyle];
+ [cancelButton setTitle:base::SysWideToNSString(
+ delegate->GetButtonLabel(ConfirmInfoBarDelegate::BUTTON_CANCEL))];
+ [cancelButton sizeToFit];
+ [cancelButton setAutoresizingMask:NSViewMinXMargin];
+ [cancelButton setTarget:self];
+ [cancelButton setAction:@selector(cancel:)];
+ }
+
+ // Position the cancel button, if it exists.
+ int cancelWidth = 0;
+ if (cancelButton) {
+ NSRect cancelFrame = [cancelButton frame];
+ cancelWidth = cancelFrame.size.width + 10;
+
+ // Position the cancel button to the left of the close button. A 10px
+ // margin is already built into cancelWidth.
+ cancelFrame.origin.x = NSMinX([closeButton_ frame]) - cancelWidth;
+ cancelFrame.origin.y = 0;
+ [cancelButton setFrame:cancelFrame];
+ [[self view] addSubview:cancelButton];
+
+ // Resize the label box to extend all the way to the cancel button,
+ // minus a 10px argin.
+ NSRect labelFrame = [label_ frame];
+ labelFrame.size.width = NSMinX(cancelFrame) - 10 - NSMinX(labelFrame);
+ [label_ setFrame:labelFrame];
+ }
+
+ // Position the OK button, if it exists.
+ if (okButton) {
+ NSRect okFrame = [okButton frame];
+ int okWidth = okFrame.size.width + 10;
+
+ // Position the OK button to the left of the close button as
+ // well. If a cancel button is present, |cancelWidth| will be positive.
+ // In either case, a 10px margin is built into okWidth.
+ okFrame.origin.x =
+ NSMinX([closeButton_ frame]) - cancelWidth - okWidth;
+ okFrame.origin.y = 0;
+ [okButton setFrame:okFrame];
+ [[self view] addSubview:okButton];
+
+ // Resize the label box to extend all the way to the OK button,
+ // minus a 10px argin.
+ NSRect labelFrame = [label_ frame];
+ labelFrame.size.width = NSMinX(okFrame) - 10 - NSMinX(labelFrame);
+ [label_ setFrame:labelFrame];
+ }
+}
+
+@end
+
+
+//////////////////////////////////////////////////////////////////////////
+// CreateInfoBar() implementations
+
+InfoBar* AlertInfoBarDelegate::CreateInfoBar() {
+ AlertInfoBarController* controller =
+ [[AlertInfoBarController alloc] initWithDelegate:this];
+ return new InfoBar(controller);
+}
+
+InfoBar* LinkInfoBarDelegate::CreateInfoBar() {
+ LinkInfoBarController* controller =
+ [[LinkInfoBarController alloc] initWithDelegate:this];
+ return new InfoBar(controller);
+}
+
+InfoBar* ConfirmInfoBarDelegate::CreateInfoBar() {
+ ConfirmInfoBarController* controller =
+ [[ConfirmInfoBarController alloc] initWithDelegate:this];
+ return new InfoBar(controller);
+}
diff --git a/chrome/browser/cocoa/infobar_controller_unittest.mm b/chrome/browser/cocoa/infobar_controller_unittest.mm
new file mode 100644
index 0000000..c6c45c9
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_controller_unittest.mm
@@ -0,0 +1,191 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#include "base/string_util.h"
+#include "base/sys_string_conversions.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/infobar_controller.h"
+#include "chrome/browser/cocoa/infobar_test_helper.h"
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface InfoBarController (ExposedForTesting)
+- (NSTextField*)label;
+@end
+
+@implementation InfoBarController (ExposedForTesting)
+- (NSTextField*)label {
+ return label_;
+}
+@end
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////
+// Test fixtures
+
+class AlertInfoBarControllerTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+
+ controller_.reset(
+ [[AlertInfoBarController alloc] initWithDelegate:&delegate_]);
+ [helper_.contentView() addSubview:[controller_ view]];
+ }
+
+ protected:
+ CocoaTestHelper helper_;
+ MockAlertInfoBarDelegate delegate_;
+ scoped_nsobject<AlertInfoBarController> controller_;
+};
+
+class LinkInfoBarControllerTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+
+ controller_.reset(
+ [[LinkInfoBarController alloc] initWithDelegate:&delegate_]);
+ [helper_.contentView() addSubview:[controller_ view]];
+ }
+
+ protected:
+ CocoaTestHelper helper_;
+ MockLinkInfoBarDelegate delegate_;
+ scoped_nsobject<LinkInfoBarController> controller_;
+};
+
+class ConfirmInfoBarControllerTest : public PlatformTest {
+ public:
+ virtual void SetUp() {
+ PlatformTest::SetUp();
+
+ controller_.reset(
+ [[ConfirmInfoBarController alloc] initWithDelegate:&delegate_]);
+ [helper_.contentView() addSubview:[controller_ view]];
+ }
+
+ protected:
+ CocoaTestHelper helper_;
+ MockConfirmInfoBarDelegate delegate_;
+ scoped_nsobject<ConfirmInfoBarController> controller_;
+};
+
+
+////////////////////////////////////////////////////////////////////////////
+// Tests
+
+TEST_F(AlertInfoBarControllerTest, ShowAndDismiss) {
+ // Make sure someone looked at the message and icon.
+ EXPECT_TRUE(delegate_.message_text_accessed);
+ EXPECT_TRUE(delegate_.icon_accessed);
+
+ // Check to make sure the infobar message was set properly.
+ EXPECT_EQ(std::wstring(kMockAlertInfoBarMessage),
+ base::SysNSStringToWide([[controller_.get() label] stringValue]));
+
+ // Check that dismissing the infobar calls InfoBarClosed() on the delegate.
+ [controller_ dismiss:nil];
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(AlertInfoBarControllerTest, DeallocController) {
+ // Test that dealloc'ing the controller does not send an
+ // InfoBarClosed() message to the delegate.
+ controller_.reset(nil);
+ EXPECT_FALSE(delegate_.closed);
+}
+
+TEST_F(LinkInfoBarControllerTest, ShowAndDismiss) {
+ // Make sure someone looked at the message, link, and icon.
+ EXPECT_TRUE(delegate_.message_text_accessed);
+ EXPECT_TRUE(delegate_.link_text_accessed);
+ EXPECT_TRUE(delegate_.icon_accessed);
+
+ // Check that dismissing the infobar calls InfoBarClosed() on the delegate.
+ [controller_ dismiss:nil];
+ EXPECT_FALSE(delegate_.link_clicked);
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(LinkInfoBarControllerTest, ShowAndClickLink) {
+ // Check that clicking on the link calls LinkClicked() on the
+ // delegate. It should also close the infobar.
+ [controller_ linkClicked];
+ EXPECT_TRUE(delegate_.link_clicked);
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(LinkInfoBarControllerTest, ShowAndClickLinkWithoutClosing) {
+ delegate_.closes_on_action = false;
+
+ // Check that clicking on the link calls LinkClicked() on the
+ // delegate. It should not close the infobar.
+ [controller_ linkClicked];
+ EXPECT_TRUE(delegate_.link_clicked);
+ EXPECT_FALSE(delegate_.closed);
+}
+
+TEST_F(ConfirmInfoBarControllerTest, ShowAndDismiss) {
+ // Make sure someone looked at the message and icon.
+ EXPECT_TRUE(delegate_.message_text_accessed);
+ EXPECT_TRUE(delegate_.icon_accessed);
+
+ // Check to make sure the infobar message was set properly.
+ EXPECT_EQ(std::wstring(kMockConfirmInfoBarMessage),
+ base::SysNSStringToWide([[controller_.get() label] stringValue]));
+
+ // Check that dismissing the infobar calls InfoBarClosed() on the delegate.
+ [controller_ dismiss:nil];
+ EXPECT_FALSE(delegate_.ok_clicked);
+ EXPECT_FALSE(delegate_.cancel_clicked);
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(ConfirmInfoBarControllerTest, ShowAndClickOK) {
+ // Check that clicking the OK button calls Accept() and then closes
+ // the infobar.
+ [controller_ ok:nil];
+ EXPECT_TRUE(delegate_.ok_clicked);
+ EXPECT_FALSE(delegate_.cancel_clicked);
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(ConfirmInfoBarControllerTest, ShowAndClickOKWithoutClosing) {
+ delegate_.closes_on_action = false;
+
+ // Check that clicking the OK button calls Accept() but does not close
+ // the infobar.
+ [controller_ ok:nil];
+ EXPECT_TRUE(delegate_.ok_clicked);
+ EXPECT_FALSE(delegate_.cancel_clicked);
+ EXPECT_FALSE(delegate_.closed);
+}
+
+TEST_F(ConfirmInfoBarControllerTest, ShowAndClickCancel) {
+ // Check that clicking the cancel button calls Cancel() and closes
+ // the infobar.
+ [controller_ cancel:nil];
+ EXPECT_FALSE(delegate_.ok_clicked);
+ EXPECT_TRUE(delegate_.cancel_clicked);
+ EXPECT_TRUE(delegate_.closed);
+}
+
+TEST_F(ConfirmInfoBarControllerTest, ShowAndClickCancelWithoutClosing) {
+ delegate_.closes_on_action = false;
+
+ // Check that clicking the cancel button calls Cancel() but does not close
+ // the infobar.
+ [controller_ cancel:nil];
+ EXPECT_FALSE(delegate_.ok_clicked);
+ EXPECT_TRUE(delegate_.cancel_clicked);
+ EXPECT_FALSE(delegate_.closed);
+}
+
+} // namespace
diff --git a/chrome/browser/cocoa/infobar_test_helper.h b/chrome/browser/cocoa/infobar_test_helper.h
new file mode 100644
index 0000000..e8f5876
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_test_helper.h
@@ -0,0 +1,152 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/tab_contents/infobar_delegate.h"
+
+namespace {
+const wchar_t* kMockAlertInfoBarMessage = L"MockAlertInfoBarMessage";
+const wchar_t* kMockLinkInfoBarMessage = L"MockLinkInfoBarMessage";
+const wchar_t* kMockLinkInfoBarLink = L"http://dev.chromium.org";
+const wchar_t* kMockConfirmInfoBarMessage = L"MockConfirmInfoBarMessage";
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Mock InfoBarDelgates
+
+class MockAlertInfoBarDelegate : public AlertInfoBarDelegate {
+ public:
+ explicit MockAlertInfoBarDelegate()
+ : AlertInfoBarDelegate(NULL),
+ message_text_accessed(false),
+ icon_accessed(false),
+ closed(false) {
+ }
+
+ virtual std::wstring GetMessageText() const {
+ message_text_accessed = true;
+ return kMockAlertInfoBarMessage;
+ }
+
+ virtual SkBitmap* GetIcon() const {
+ icon_accessed = true;
+ return NULL;
+ }
+
+ virtual void InfoBarClosed() {
+ closed = true;
+ }
+
+ // These are declared mutable to get around const-ness issues.
+ mutable bool message_text_accessed;
+ mutable bool icon_accessed;
+ bool closed;
+};
+
+class MockLinkInfoBarDelegate : public LinkInfoBarDelegate {
+ public:
+ explicit MockLinkInfoBarDelegate()
+ : LinkInfoBarDelegate(NULL),
+ message_text_accessed(false),
+ link_text_accessed(false),
+ icon_accessed(false),
+ link_clicked(false),
+ closed(false),
+ closes_on_action(true) {
+ }
+
+ virtual std::wstring GetMessageTextWithOffset(size_t* link_offset) const {
+ message_text_accessed = true;
+ *link_offset = 1;
+ return kMockLinkInfoBarMessage;
+ }
+
+ virtual std::wstring GetLinkText() const {
+ link_text_accessed = true;
+ return kMockLinkInfoBarLink;
+ }
+
+ virtual SkBitmap* GetIcon() const {
+ icon_accessed = true;
+ return NULL;
+ }
+
+ virtual bool LinkClicked(WindowOpenDisposition disposition) {
+ link_clicked = true;
+ return closes_on_action;
+ }
+
+ virtual void InfoBarClosed() {
+ closed = true;
+ }
+
+ // These are declared mutable to get around const-ness issues.
+ mutable bool message_text_accessed;
+ mutable bool link_text_accessed;
+ mutable bool icon_accessed;
+ bool link_clicked;
+ bool closed;
+
+ // Determines whether the infobar closes when an action is taken or not.
+ bool closes_on_action;
+};
+
+class MockConfirmInfoBarDelegate : public ConfirmInfoBarDelegate {
+ public:
+ explicit MockConfirmInfoBarDelegate()
+ : ConfirmInfoBarDelegate(NULL),
+ message_text_accessed(false),
+ link_text_accessed(false),
+ icon_accessed(false),
+ ok_clicked(false),
+ cancel_clicked(false),
+ closed(false),
+ closes_on_action(true) {
+ }
+
+ virtual int GetButtons() const {
+ return (BUTTON_OK | BUTTON_CANCEL);
+ }
+
+ virtual std::wstring GetButtonLabel(InfoBarButton button) const {
+ if (button == BUTTON_OK)
+ return L"OK";
+ else
+ return L"Cancel";
+ }
+
+ virtual bool Accept() {
+ ok_clicked = true;
+ return closes_on_action;
+ }
+
+ virtual bool Cancel() {
+ cancel_clicked = true;
+ return closes_on_action;
+ }
+
+ virtual std::wstring GetMessageText() const {
+ message_text_accessed = true;
+ return kMockConfirmInfoBarMessage;
+ }
+
+ virtual SkBitmap* GetIcon() const {
+ icon_accessed = true;
+ return NULL;
+ }
+
+ virtual void InfoBarClosed() {
+ closed = true;
+ }
+
+ // These are declared mutable to get around const-ness issues.
+ mutable bool message_text_accessed;
+ mutable bool link_text_accessed;
+ mutable bool icon_accessed;
+ bool ok_clicked;
+ bool cancel_clicked;
+ bool closed;
+
+ // Determines whether the infobar closes when an action is taken or not.
+ bool closes_on_action;
+};
diff --git a/chrome/browser/cocoa/infobar_text_field.h b/chrome/browser/cocoa/infobar_text_field.h
new file mode 100644
index 0000000..6f7d482
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_text_field.h
@@ -0,0 +1,14 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+// A subclass of NSTextField that allows us to customize NSTextView
+// delegate method implementations. We override
+// textView:clickedOnLink:atIndex: to handle users clicking on URLs
+// embedded in LinkInfoBars.
+@interface InfoBarTextField : NSTextField {
+}
+@end
+
diff --git a/chrome/browser/cocoa/infobar_text_field.mm b/chrome/browser/cocoa/infobar_text_field.mm
new file mode 100644
index 0000000..accea6a
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_text_field.mm
@@ -0,0 +1,20 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#import "chrome/browser/cocoa/infobar_text_field.h"
+
+@implementation InfoBarTextField
+
+- (BOOL)textView:(NSTextView*)aTextView
+ clickedOnLink:(id)link
+ atIndex:(NSUInteger)charIndex {
+ if ([[self delegate] respondsToSelector:@selector(linkClicked)])
+ [[self delegate] performSelector:@selector(linkClicked)];
+
+ return YES; // We handled the click, so Cocoa does not need to do anything.
+}
+
+@end
diff --git a/chrome/browser/cocoa/infobar_text_field_unittest.mm b/chrome/browser/cocoa/infobar_text_field_unittest.mm
new file mode 100644
index 0000000..5df9e3f
--- /dev/null
+++ b/chrome/browser/cocoa/infobar_text_field_unittest.mm
@@ -0,0 +1,67 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/cocoa/infobar_text_field.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+@interface TextFieldDelegatePong: NSObject {
+ @private
+ BOOL pong_;
+}
+@property(readonly) BOOL pong;
+@end
+
+@implementation TextFieldDelegatePong
+@synthesize pong = pong_;
+
+- (id)init {
+ if ((self == [super init])) {
+ pong_ = NO;
+ }
+ return self;
+}
+
+- (void)linkClicked {
+ pong_ = YES;
+}
+@end
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////
+// Test fixtures
+
+class InfoBarTextFieldTest : public PlatformTest {
+ protected:
+ CocoaTestHelper helper_;
+};
+
+////////////////////////////////////////////////////////////////////////////
+// Tests
+
+TEST_F(InfoBarTextFieldTest, Show) {
+ // Test basic drawing.
+ scoped_nsobject<InfoBarTextField> field(
+ [[InfoBarTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 200)]);
+ [helper_.contentView() addSubview:field];
+}
+
+TEST_F(InfoBarTextFieldTest, LinkClicked) {
+ scoped_nsobject<InfoBarTextField> field(
+ [[InfoBarTextField alloc] initWithFrame:NSMakeRect(0, 0, 200, 200)]);
+ scoped_nsobject<TextFieldDelegatePong> delegate(
+ [[TextFieldDelegatePong alloc] init]);
+ [field setDelegate:delegate];
+
+ // Our implementation doesn't look at any of these fields, so they
+ // can all be nil.
+ [field textView:nil clickedOnLink:nil atIndex:0];
+ EXPECT_TRUE([delegate pong]);
+}
+
+} // namespace
diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h
index fc93070..73891ec 100644
--- a/chrome/browser/cocoa/toolbar_controller.h
+++ b/chrome/browser/cocoa/toolbar_controller.h
@@ -42,6 +42,7 @@ class ToolbarView;
scoped_nsobject<BookmarkBarController> bookmarkBarController_;
id<BookmarkURLOpener> bookmarkBarDelegate_; // weak
NSView* webContentView_; // weak; where the web goes
+ NSView* infoBarsView_; // weak; where the infobars go
// Used for monitoring the optional toolbar button prefs.
scoped_ptr<ToolbarControllerInternal::PrefObserverBridge> prefObserver_;
@@ -72,6 +73,7 @@ class ToolbarView;
commands:(CommandUpdater*)commands
profile:(Profile*)profile
webContentView:(NSView*)webContentView
+ infoBarsView:(NSView*)infoBarsView
bookmarkDelegate:(id<BookmarkURLOpener>)delegate;
// Get the C++ bridge object representing the location bar for this tab.
diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm
index bb28cfa..681795b 100644
--- a/chrome/browser/cocoa/toolbar_controller.mm
+++ b/chrome/browser/cocoa/toolbar_controller.mm
@@ -55,6 +55,7 @@ class PrefObserverBridge : public NotificationObserver {
commands:(CommandUpdater*)commands
profile:(Profile*)profile
webContentView:(NSView*)webContentView
+ infoBarsView:(NSView*)infoBarsView
bookmarkDelegate:(id<BookmarkURLOpener>)delegate {
DCHECK(model && commands && profile);
if ((self = [super initWithNibName:@"Toolbar"
@@ -64,6 +65,7 @@ class PrefObserverBridge : public NotificationObserver {
profile_ = profile;
bookmarkBarDelegate_ = delegate;
webContentView_ = webContentView;
+ infoBarsView_ = infoBarsView;
hasToolbar_ = YES;
// Register for notifications about state changes for the toolbar buttons
@@ -109,6 +111,7 @@ class PrefObserverBridge : public NotificationObserver {
initWithProfile:profile_
parentView:[self view]
webContentView:webContentView_
+ infoBarsView:infoBarsView_
delegate:bookmarkBarDelegate_]);
// Add bookmark bar to the view hierarchy. This also triggers the
diff --git a/chrome/browser/cocoa/toolbar_controller_unittest.mm b/chrome/browser/cocoa/toolbar_controller_unittest.mm
index 58493a2..a3a7649 100644
--- a/chrome/browser/cocoa/toolbar_controller_unittest.mm
+++ b/chrome/browser/cocoa/toolbar_controller_unittest.mm
@@ -37,6 +37,7 @@ class ToolbarControllerTest : public testing::Test {
commands:browser->command_updater()
profile:helper_.profile()
webContentView:nil
+ infoBarsView:nil
bookmarkDelegate:nil]);
EXPECT_TRUE([bar_ view]);
NSView* parent = [cocoa_helper_.window() contentView];