summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjohnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-09 23:16:56 +0000
committerjohnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-09 23:16:56 +0000
commit6a760c99f8a9590f8643a832d50980c60c5c3931 (patch)
tree24cc61cdac8f30b937fe6c39ff342a142835c4c1
parent751fae0d3da720758f736b515012e72ce0a1e7e7 (diff)
downloadchromium_src-6a760c99f8a9590f8643a832d50980c60c5c3931.zip
chromium_src-6a760c99f8a9590f8643a832d50980c60c5c3931.tar.gz
chromium_src-6a760c99f8a9590f8643a832d50980c60c5c3931.tar.bz2
Desktop Notifications for the mac
BUG=23066 TEST=notify_demo.html Review URL: http://codereview.chromium.org/548208 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38533 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/cocoa/notifications/balloon_controller.h74
-rw-r--r--chrome/browser/cocoa/notifications/balloon_controller.mm306
-rw-r--r--chrome/browser/cocoa/notifications/balloon_controller_unittest.mm93
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view.h28
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view.mm72
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view_bridge.h34
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view_bridge.mm36
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view_host_mac.h130
-rw-r--r--chrome/browser/cocoa/notifications/balloon_view_host_mac.mm131
-rw-r--r--chrome/browser/notifications/balloon_collection.cc6
-rw-r--r--chrome/browser/notifications/balloon_collection_mac.mm8
-rw-r--r--chrome/browser/notifications/notification_object_proxy.cc9
-rwxr-xr-xchrome/chrome_browser.gypi8
-rwxr-xr-xchrome/chrome_tests.gypi1
-rw-r--r--chrome/common/desktop_notifications/active_notification_tracker.cc7
-rw-r--r--chrome/renderer/render_thread.cc3
16 files changed, 932 insertions, 14 deletions
diff --git a/chrome/browser/cocoa/notifications/balloon_controller.h b/chrome/browser/cocoa/notifications/balloon_controller.h
new file mode 100644
index 0000000..d71c8e3
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_controller.h
@@ -0,0 +1,74 @@
+// Copyright (c) 2010 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_NOTIFICATIONS_BALLOON_CONTROLLER_H_
+#define CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#include "base/cocoa_protocols_mac.h"
+#import "chrome/browser/cocoa/notifications/balloon_view.h"
+#import "chrome/browser/cocoa/notifications/balloon_view_host_mac.h"
+#include "chrome/browser/notifications/balloon.h"
+
+// The Balloon controller creates the view elements to display a
+// notification balloon, resize it if the HTML contents of that
+// balloon change, and move it when the collection of balloons is
+// modified.
+@interface BalloonController : NSWindowController<NSWindowDelegate> {
+ @private
+ // The balloon which represents the contents of this view. Weak pointer
+ // owned by the browser's NotificationUIManager.
+ Balloon* balloon_;
+
+ // The window that contains the frame of the notification.
+ scoped_nsobject<NSWindow> frameContainer_;
+
+ // The view that contains the contents of the notification
+ scoped_nsobject<NSView> htmlContainer_;
+
+ // The view that contains the frame around the contents.
+ scoped_nsobject<NSView> frameView_;
+
+ // The options menu that appears when "options" is pressed.
+ scoped_nsobject<NSMenu> optionsMenu_;
+
+ // An animation for moving the balloon smoothly.
+ scoped_nsobject<NSViewAnimation> animation_;
+
+ // The host for the renderer of the HTML contents.
+ scoped_ptr<BalloonViewHost> htmlContents_;
+}
+
+// Initialize with a balloon object containing the notification data.
+- (id)initWithBalloon:(Balloon*)balloon;
+
+// Callback function for the close button.
+- (IBAction)closeButtonPressed:(id)sender;
+
+// Callback function for the options button.
+- (IBAction)optionsButtonPressed:(id)sender;
+
+// Callback function for the "revoke" option in the menu.
+- (IBAction)permissionRevoked:(id)sender;
+
+// Closes the balloon. Can be called by the bridge or by the close
+// button handler.
+- (void)closeBalloon:(bool)byUser;
+
+// Repositions the view to match the position and size of the balloon.
+// Called by the bridge when the size changes.
+- (void)repositionToBalloon;
+
+// The current size of the view, possibly subject to an animation completing.
+- (int)desiredTotalWidth;
+- (int)desiredTotalHeight;
+@end
+
+@interface BalloonController (UnitTesting)
+- (void)initializeHost;
+@end
+
+#endif // CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/notifications/balloon_controller.mm b/chrome/browser/cocoa/notifications/balloon_controller.mm
new file mode 100644
index 0000000..f452cb4
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_controller.mm
@@ -0,0 +1,306 @@
+// Copyright (c) 2010 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/cocoa/notifications/balloon_controller.h"
+
+#include "app/l10n_util.h"
+#import "base/cocoa_protocols_mac.h"
+#import "base/scoped_nsobject.h"
+#include "base/string_util.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_window.h"
+#include "chrome/browser/cocoa/notifications/balloon_view_host_mac.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/desktop_notification_service.h"
+#include "chrome/browser/profile.h"
+#include "grit/generated_resources.h"
+
+namespace {
+
+// Margin, in pixels, between the notification frame and the contents
+// of the notification.
+const int kTopMargin = 2;
+const int kBottomMargin = 2;
+const int kLeftMargin = 2;
+const int kRightMargin = 2;
+
+// How many pixels of overlap there is between the shelf top and the
+// balloon bottom.
+const int kShelfBorderTopOverlap = 6;
+
+// Properties of the dismiss button.
+const int kDismissButtonWidth = 52;
+const int kDismissButtonHeight = 18;
+
+// Properties of the options menu.
+const int kOptionsMenuWidth = 52;
+const int kOptionsMenuHeight = 18;
+
+// Properties of the origin label.
+const int kLeftLabelMargin = 4;
+const int kLabelHeight = 16;
+
+// TODO(johnnyg): http://crbug.com/34826 Add a shadow for the frame.
+const int kLeftShadowWidth = 0;
+const int kRightShadowWidth = 0;
+const int kTopShadowWidth = 0;
+const int kBottomShadowWidth = 0;
+
+// The shelf height for the system default font size. It is scaled
+// with changes in the default font size.
+const int kDefaultShelfHeight = 22;
+
+} // namespace
+
+@interface BalloonController (InternalLayout)
+// The following are all internal methods to calculate the dimensions of
+// subviews.
+- (NSPoint)contentsOffset;
+- (int)shelfHeight;
+- (int)balloonFrameHeight;
+- (NSRect)contentsRectangle;
+- (NSRect)closeButtonBounds;
+- (NSRect)optionsMenuBounds;
+- (NSRect)labelBounds;
+@end
+
+@implementation BalloonController
+
+- (id)initWithBalloon:(Balloon*)balloon {
+ NSString* sourceLabelText = l10n_util::GetNSStringF(
+ IDS_NOTIFICATION_BALLOON_SOURCE_LABEL,
+ WideToUTF16(balloon->notification().display_source()));
+ NSString* optionsText =
+ l10n_util::GetNSString(IDS_NOTIFICATION_OPTIONS_MENU_LABEL);
+ NSString* dismissText =
+ l10n_util::GetNSString(IDS_NOTIFICATION_BALLOON_DISMISS_LABEL);
+ NSString* revokeText =
+ l10n_util::GetNSStringF(IDS_NOTIFICATION_BALLOON_REVOKE_MESSAGE,
+ WideToUTF16(balloon->notification().display_source()));
+
+ balloon_ = balloon;
+
+ frameContainer_.reset([[NSWindow alloc]
+ initWithContentRect:NSZeroRect
+ styleMask:NSBorderlessWindowMask
+ backing:NSBackingStoreBuffered
+ defer:YES]);
+ [frameContainer_ setAlphaValue:1.0];
+ [frameContainer_ setOpaque:NO];
+ [frameContainer_ setLevel:NSFloatingWindowLevel];
+
+ if ((self = [self initWithWindow:frameContainer_.get()])) {
+ // Position of the balloon.
+ int x = balloon_->position().x();
+ int y = balloon_->position().y();
+ int w = [self desiredTotalWidth];
+ int h = [self desiredTotalHeight];
+
+ NSView* contentView = [frameContainer_ contentView];
+
+ frameView_.reset([[BalloonViewCocoa alloc]
+ initWithFrame:NSMakeRect(0,
+ [self shelfHeight],
+ w,
+ [self balloonFrameHeight])]);
+ [contentView addSubview:frameView_.get()];
+
+ htmlContainer_.reset(
+ [[NSView alloc] initWithFrame:[self contentsRectangle]]);
+ [htmlContainer_ setNeedsDisplay:YES];
+ [htmlContainer_ setFrame:[self contentsRectangle]];
+ [frameView_ addSubview:htmlContainer_.get()];
+
+ htmlContents_.reset(new BalloonViewHost(balloon));
+ [self initializeHost];
+
+ scoped_nsobject<NSView> shelfView([[BalloonShelfViewCocoa alloc]
+ initWithFrame:NSMakeRect(0,
+ 0,
+ w,
+ [self shelfHeight]+kShelfBorderTopOverlap)]);
+ [contentView addSubview:shelfView.get()
+ positioned:NSWindowBelow
+ relativeTo:htmlContainer_.get()];
+
+ optionsMenu_.reset([[NSMenu alloc] init]);
+ [optionsMenu_ addItemWithTitle:revokeText
+ action:@selector(permissionRevoked:)
+ keyEquivalent:@""];
+
+
+ // Creating UI elements by hand for easier parity with other platforms.
+ // TODO(johnnyg): http://crbug.com/34627
+ // Investigate converting this to a nib.
+ scoped_nsobject<BalloonButtonCell> closeButtonCell(
+ [[BalloonButtonCell alloc] initTextCell:dismissText]);
+ scoped_nsobject<NSButton> closeButton(
+ [[NSButton alloc] initWithFrame:[self closeButtonBounds]]);
+ [shelfView addSubview:closeButton.get()];
+ [closeButton setCell:closeButtonCell.get()];
+ [closeButton setTarget:self];
+ [closeButton setAction:@selector(closeButtonPressed:)];
+ [closeButtonCell setTextColor:[NSColor whiteColor]];
+
+ scoped_nsobject<BalloonButtonCell> optionsButtonCell(
+ [[BalloonButtonCell alloc] initTextCell:optionsText]);
+ scoped_nsobject<NSButton> optionsButton(
+ [[NSButton alloc] initWithFrame:[self optionsMenuBounds]]);
+ [shelfView addSubview:optionsButton.get()];
+ [optionsButton setCell:optionsButtonCell.get()];
+ [optionsButton setTarget:self];
+ [optionsButton setAction:@selector(optionsButtonPressed:)];
+ [optionsButtonCell setTextColor:[NSColor whiteColor]];
+
+ scoped_nsobject<NSTextField> originLabel([[NSTextField alloc] init]);
+ [shelfView addSubview:originLabel.get()];
+ [originLabel setEditable:NO];
+ [originLabel setBezeled:NO];
+ [originLabel setSelectable:NO];
+ [originLabel setDrawsBackground:NO];
+ [[originLabel cell] setLineBreakMode:NSLineBreakByTruncatingTail];
+ [[originLabel cell] setTextColor:[NSColor whiteColor]];
+ [originLabel setStringValue:sourceLabelText];
+ [originLabel setFrame:[self labelBounds]];
+ [originLabel setFont:
+ [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+
+ [frameContainer_ setFrame:NSMakeRect(x, y, w, h) display:YES];
+ [frameContainer_ setDelegate:self];
+ }
+ return self;
+}
+
+- (IBAction)closeButtonPressed:(id)sender {
+ [self closeBalloon:YES];
+}
+
+- (IBAction)optionsButtonPressed:(id)sender {
+ [NSMenu popUpContextMenu:optionsMenu_
+ withEvent:[NSApp currentEvent]
+ forView:frameView_];
+}
+
+- (IBAction)permissionRevoked:(id)sender {
+ DesktopNotificationService* service =
+ balloon_->profile()->GetDesktopNotificationService();
+ service->DenyPermission(balloon_->notification().origin_url());
+}
+
+- (void)closeBalloon:(bool)byUser {
+ DCHECK(balloon_);
+ [self close];
+ htmlContents_->Shutdown();
+ balloon_->OnClose(byUser);
+ balloon_ = NULL;
+}
+
+- (void)repositionToBalloon {
+ DCHECK(balloon_);
+ int x = balloon_->position().x();
+ int y = balloon_->position().y();
+ int w = [self desiredTotalWidth];
+ int h = [self desiredTotalHeight];
+
+ NSRect frame = NSMakeRect(x, y, w, h);
+
+ htmlContents_->UpdateActualSize(balloon_->content_size());
+ [htmlContainer_ setFrame:[self contentsRectangle]];
+ [frameView_ setFrame:NSMakeRect(0,
+ [self shelfHeight],
+ w,
+ [self balloonFrameHeight])];
+
+ [animation_ stopAnimation];
+
+ NSDictionary* dict =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ frameContainer_.get(), NSViewAnimationTargetKey,
+ [NSValue valueWithRect:frame], NSViewAnimationEndFrameKey, nil];
+
+ animation_.reset([[NSViewAnimation alloc]
+ initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]]);
+ [animation_ startAnimation];
+}
+
+// Returns the total width the view should be to accommodate the balloon.
+- (int)desiredTotalWidth {
+ return (balloon_ ? balloon_->content_size().width() : 0) +
+ kLeftMargin + kRightMargin + kLeftShadowWidth + kRightShadowWidth;
+}
+
+// Returns the total height the view should be to accommodate the balloon.
+- (int)desiredTotalHeight {
+ return (balloon_ ? balloon_->content_size().height() : 0) +
+ kTopMargin + kBottomMargin + kTopShadowWidth + kBottomShadowWidth +
+ [self shelfHeight];
+}
+
+// Relative to the lower left of the frame, above the shelf.
+- (NSPoint)contentsOffset {
+ return NSMakePoint(kLeftShadowWidth + kLeftMargin,
+ kBottomShadowWidth + kBottomMargin);
+}
+
+// Returns the height of the shelf in pixels.
+- (int)shelfHeight {
+ // TODO(johnnyg): add scaling here.
+ return kDefaultShelfHeight;
+}
+
+// Returns the height of the balloon contents frame in pixels.
+- (int)balloonFrameHeight {
+ return [self desiredTotalHeight] - [self shelfHeight];
+}
+
+// Relative to the lower-left of the frame.
+- (NSRect)contentsRectangle {
+ gfx::Size contentSize = balloon_->content_size();
+ NSPoint offset = [self contentsOffset];
+ return NSMakeRect(offset.x, offset.y,
+ contentSize.width(), contentSize.height());
+}
+
+// Returns the bounds of the close button.
+- (NSRect)closeButtonBounds {
+ return NSMakeRect(
+ [self desiredTotalWidth] - kDismissButtonWidth - kRightMargin,
+ kBottomMargin,
+ kDismissButtonWidth,
+ kDismissButtonHeight);
+}
+
+// Returns the bounds of the button which opens the options menu.
+- (NSRect)optionsMenuBounds {
+ return NSMakeRect(
+ [self desiredTotalWidth] -
+ kDismissButtonWidth - kOptionsMenuWidth - kRightMargin,
+ kBottomMargin,
+ kOptionsMenuWidth,
+ kOptionsMenuHeight);
+}
+
+// Returns the bounds of the label showing the origin of the notification.
+- (NSRect)labelBounds {
+ return NSMakeRect(
+ kLeftLabelMargin,
+ kBottomMargin,
+ [self desiredTotalWidth] -
+ kDismissButtonWidth - kOptionsMenuWidth - kRightMargin,
+ kLabelHeight);
+}
+
+// Initializes the renderer host showing the HTML contents.
+- (void)initializeHost {
+ htmlContents_->Init();
+ gfx::NativeView contents = htmlContents_->native_view();
+ [htmlContainer_ addSubview:contents];
+}
+
+// NSWindowDelegate notification.
+- (void)windowWillClose:(NSNotification*)notif {
+ [self autorelease];
+}
+
+@end
diff --git a/chrome/browser/cocoa/notifications/balloon_controller_unittest.mm b/chrome/browser/cocoa/notifications/balloon_controller_unittest.mm
new file mode 100644
index 0000000..4893982
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_controller_unittest.mm
@@ -0,0 +1,93 @@
+// Copyright (c) 2010 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/scoped_nsobject.h"
+#include "chrome/browser/cocoa/browser_test_helper.h"
+#include "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "chrome/browser/cocoa/notifications/balloon_controller.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/balloon_collection.h"
+#include "chrome/browser/renderer_host/test/test_render_view_host.h"
+#include "chrome/test/testing_profile.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+namespace {
+
+// Use a dummy balloon collection for testing.
+class MockBalloonCollection : public BalloonCollection {
+ virtual void Add(const Notification& notification,
+ Profile* profile) {}
+ virtual bool Remove(const Notification& notification) { return false; }
+ virtual bool HasSpace() const { return true; }
+ virtual void ResizeBalloon(Balloon* balloon, const gfx::Size& size) {};
+ virtual void OnBalloonClosed(Balloon* source) {};
+};
+
+class BalloonControllerTest : public RenderViewHostTestHarness {
+ public:
+ BalloonControllerTest() :
+ ui_thread_(ChromeThread::UI, MessageLoop::current()),
+ io_thread_(ChromeThread::IO, MessageLoop::current()) {
+ }
+
+ virtual void SetUp() {
+ RenderViewHostTestHarness::SetUp();
+ CocoaTest::BootstrapCocoa();
+ profile_.reset(new TestingProfile());
+ profile_->CreateRequestContext();
+ browser_.reset(new Browser(Browser::TYPE_NORMAL, profile_.get()));
+ collection_.reset(new MockBalloonCollection());
+ }
+
+ virtual void TearDown() {
+ MessageLoop::current()->RunAllPending();
+ RenderViewHostTestHarness::TearDown();
+ }
+
+ protected:
+ ChromeThread ui_thread_;
+ ChromeThread io_thread_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<Browser> browser_;
+ scoped_ptr<BalloonCollection> collection_;
+};
+
+TEST_F(BalloonControllerTest, ShowAndCloseTest) {
+ Notification n(GURL("http://www.google.com"), GURL("http://www.google.com"),
+ L"http://www.google.com", new NotificationObjectProxy(-1, -1, -1, false));
+ scoped_ptr<Balloon> balloon(
+ new Balloon(n, profile_.get(), collection_.get()));
+
+ BalloonController* controller = [BalloonController alloc];
+
+ id mock = [OCMockObject partialMockForObject:controller];
+ [[mock expect] initializeHost];
+
+ [controller initWithBalloon:balloon.get()];
+ [controller showWindow:nil];
+ [controller closeBalloon:YES];
+
+ [mock verify];
+}
+
+TEST_F(BalloonControllerTest, SizesTest) {
+ Notification n(GURL("http://www.google.com"), GURL("http://www.google.com"),
+ L"http://www.google.com", new NotificationObjectProxy(-1, -1, -1, false));
+ scoped_ptr<Balloon> balloon(
+ new Balloon(n, profile_.get(), collection_.get()));
+ balloon->set_content_size(gfx::Size(100, 100));
+
+ BalloonController* controller = [BalloonController alloc];
+
+ id mock = [OCMockObject partialMockForObject:controller];
+ [[mock expect] initializeHost];
+
+ [controller initWithBalloon:balloon.get()];
+
+ EXPECT_TRUE([controller desiredTotalWidth] > 100);
+ EXPECT_TRUE([controller desiredTotalHeight] > 100);
+ [mock verify];
+}
+
+}
diff --git a/chrome/browser/cocoa/notifications/balloon_view.h b/chrome/browser/cocoa/notifications/balloon_view.h
new file mode 100644
index 0000000..8720d94
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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_NOTIFICATIONS_BALLOON_VIEW_H_
+#define CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_H_
+
+#import <Cocoa/Cocoa.h>
+
+// This view class draws a frame around the HTML contents of a
+// notification balloon.
+@interface BalloonViewCocoa : NSView {
+}
+@end
+
+// This view class draws the shelf of a notification balloon,
+// containing the controls.
+@interface BalloonShelfViewCocoa : NSView {
+}
+@end
+
+// This view draws a button with the shelf of the balloon.
+@interface BalloonButtonCell : NSButtonCell {
+}
+- (void)setTextColor:(NSColor*)color;
+@end
+
+#endif // CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_H_
diff --git a/chrome/browser/cocoa/notifications/balloon_view.mm b/chrome/browser/cocoa/notifications/balloon_view.mm
new file mode 100644
index 0000000..1186975e
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view.mm
@@ -0,0 +1,72 @@
+// Copyright (c) 2010 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/cocoa/notifications/balloon_view.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/logging.h"
+#include "base/scoped_nsobject.h"
+
+namespace {
+
+const int kRoundedCornerSize = 4;
+
+} // namespace
+
+
+@implementation BalloonViewCocoa
+- (void)drawRect:(NSRect)rect {
+ NSRect bounds = [self bounds];
+ NSBezierPath* path =
+ [NSBezierPath bezierPathWithRoundedRect:bounds
+ xRadius:kRoundedCornerSize
+ yRadius:kRoundedCornerSize];
+ [[NSColor lightGrayColor] set];
+ [path fill];
+ [[NSColor blackColor] set];
+ [path stroke];
+}
+@end
+
+@implementation BalloonShelfViewCocoa
+- (void)drawRect:(NSRect)rect {
+ NSRect bounds = [self bounds];
+ NSBezierPath* path =
+ [NSBezierPath bezierPathWithRoundedRect:bounds
+ xRadius:kRoundedCornerSize
+ yRadius:kRoundedCornerSize];
+ [[NSColor colorWithCalibratedRed:0.304 green:0.549 blue:0.85 alpha:1.0] set];
+ [path fill];
+ [[NSColor blackColor] set];
+ [path stroke];
+}
+@end
+
+@implementation BalloonButtonCell
+
+- (id)initTextCell:(NSString*)string {
+ if ((self = [super initTextCell:string])) {
+ [self setButtonType:NSMomentaryPushInButton];
+ [self setShowsBorderOnlyWhileMouseInside:YES];
+ [self setBezelStyle:NSShadowlessSquareBezelStyle];
+ [self setControlSize:NSSmallControlSize];
+ [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
+ }
+
+ return self;
+}
+
+- (void)setTextColor:(NSColor*)color {
+ NSDictionary* dict = [NSDictionary dictionaryWithObjectsAndKeys:
+ color, NSForegroundColorAttributeName,
+ [self font], NSFontAttributeName, nil];
+ scoped_nsobject<NSAttributedString> title(
+ [[NSAttributedString alloc] initWithString:[self title] attributes:dict]);
+
+ NSButton* button = static_cast<NSButton*>([self controlView]);
+ DCHECK([button isKindOfClass:[NSButton class]]);
+ [button setAttributedTitle:title.get()];
+}
+@end
diff --git a/chrome/browser/cocoa/notifications/balloon_view_bridge.h b/chrome/browser/cocoa/notifications/balloon_view_bridge.h
new file mode 100644
index 0000000..627e156
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view_bridge.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2010 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_NOTIFICATIONS_BALLOON_VIEW_BRIDGE_H_
+#define CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_BRIDGE_H_
+
+#include "base/gfx/size.h"
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/notifications/balloon_controller.h"
+#include "chrome/browser/notifications/balloon.h"
+
+// Bridges from the cross-platform BalloonView interface to the Cocoa
+// controller which will draw the view on screen.
+class BalloonViewBridge : public BalloonView {
+ public:
+ BalloonViewBridge();
+ ~BalloonViewBridge();
+
+ // BalloonView interface.
+ virtual void Show(Balloon* balloon);
+ virtual void RepositionToBalloon();
+ virtual void Close(bool by_user);
+ virtual gfx::Size GetSize() const;
+
+ private:
+ // Weak pointer to the balloon controller which manages the UI.
+ // This object cleans itself up when its windows close.
+ BalloonController* controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(BalloonViewBridge);
+};
+
+#endif // CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_BRIDGE_H_
diff --git a/chrome/browser/cocoa/notifications/balloon_view_bridge.mm b/chrome/browser/cocoa/notifications/balloon_view_bridge.mm
new file mode 100644
index 0000000..89d2f39
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view_bridge.mm
@@ -0,0 +1,36 @@
+// Copyright (c) 2010 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/cocoa/notifications/balloon_view_bridge.h"
+
+#import <Cocoa/Cocoa.h>
+
+BalloonViewBridge::BalloonViewBridge() :
+ controller_(NULL) {
+}
+
+BalloonViewBridge::~BalloonViewBridge() {
+}
+
+void BalloonViewBridge::Close(bool by_user) {
+ [controller_ closeBalloon:by_user];
+}
+
+gfx::Size BalloonViewBridge::GetSize() const {
+ if (controller_)
+ return gfx::Size([controller_ desiredTotalWidth],
+ [controller_ desiredTotalHeight]);
+ else
+ return gfx::Size();
+}
+
+void BalloonViewBridge::RepositionToBalloon() {
+ [controller_ repositionToBalloon];
+}
+
+void BalloonViewBridge::Show(Balloon* balloon) {
+ controller_ = [[BalloonController alloc] initWithBalloon:balloon];
+ [controller_ showWindow:nil];
+}
+
diff --git a/chrome/browser/cocoa/notifications/balloon_view_host_mac.h b/chrome/browser/cocoa/notifications/balloon_view_host_mac.h
new file mode 100644
index 0000000..3568576
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view_host_mac.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2010 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_NOTIFICATIONS_BALLOON_VIEW_HOST_MAC_H_
+#define CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_HOST_MAC_H_
+
+#include "app/gfx/native_widget_types.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/renderer_host/render_view_host_delegate.h"
+#import "chrome/browser/renderer_host/render_widget_host_view_mac.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/tab_contents/render_view_host_delegate_helper.h"
+#include "webkit/glue/webpreferences.h"
+
+class Profile;
+class RenderViewHost;
+
+// BalloonViewHost class is a delegate to the renderer host for the HTML
+// notification. When initialized it creates a new RenderViewHost and loads
+// the contents of the toast into it. It also handles links within the toast,
+// loading them into a new tab.
+class BalloonViewHost : public RenderViewHostDelegate,
+ public RenderViewHostDelegate::View {
+ public:
+ explicit BalloonViewHost(Balloon* balloon);
+
+ ~BalloonViewHost() {
+ Shutdown();
+ }
+
+ // Initialize the view.
+ void Init();
+
+ // Stops showing the balloon.
+ void Shutdown();
+
+ // RenderViewHostDelegate overrides.
+ virtual WebPreferences GetWebkitPrefs();
+ virtual RendererPreferences GetRendererPrefs(Profile* profile) const;
+ virtual SiteInstance* GetSiteInstance() const {
+ return site_instance_.get();
+ }
+ virtual Profile* GetProfile() const { return balloon_->profile(); }
+ virtual const GURL& GetURL() const {
+ return balloon_->notification().content_url();
+ }
+ virtual void Close(RenderViewHost* render_view_host);
+ virtual void RenderViewCreated(RenderViewHost* render_view_host);
+ virtual void DidStopLoading() {}
+ virtual void RendererReady(RenderViewHost* render_view_host);
+ virtual void RendererGone(RenderViewHost* render_view_host);
+ virtual void UpdateTitle(RenderViewHost* /* render_view_host */,
+ int32 /* page_id */, const std::wstring& title) {
+ title_ = title;
+ }
+ virtual int GetBrowserWindowID() const { return -1; }
+ virtual ViewType::Type GetRenderViewType() const {
+ return ViewType::TAB_CONTENTS;
+ }
+ virtual RenderViewHostDelegate::View* GetViewDelegate() {
+ return this;
+ }
+
+ void UpdateActualSize(const gfx::Size& new_size);
+
+ // RenderViewHostDelegate::View methods. Only the ones for opening new
+ // windows are currently implemented.
+ virtual void CreateNewWindow(int route_id);
+ virtual void CreateNewWidget(int route_id, bool activatable) {}
+ virtual void ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture);
+ virtual void ShowCreatedWidget(int route_id,
+ const gfx::Rect& initial_pos) {}
+ virtual void ShowContextMenu(const ContextMenuParams& params) {}
+ virtual void StartDragging(const WebDropData& drop_data,
+ WebKit::WebDragOperationsMask allowed_ops) {}
+ virtual void UpdateDragCursor(WebKit::WebDragOperation operation) {}
+ virtual void GotFocus() {}
+ virtual void TakeFocus(bool reverse) {}
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+ }
+ virtual void HandleKeyboardEvent(const NativeWebKeyboardEvent& event) {}
+ virtual void HandleMouseEvent() {}
+ virtual void HandleMouseLeave() {}
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size);
+
+ // Accessors.
+ RenderViewHost* render_view_host() const { return render_view_host_; }
+ gfx::NativeView native_view() const {
+ return render_widget_host_view_->native_view();
+ }
+ const std::wstring& title() const { return title_; }
+
+ private:
+ // True after Init() has completed.
+ bool initialized_;
+
+ // Non-owned pointer to the associated balloon.
+ Balloon* balloon_;
+
+ // Site instance for the balloon/profile, to be used for opening new links.
+ scoped_refptr<SiteInstance> site_instance_;
+
+ // Owned pointer to to host for the renderer process.
+ RenderViewHost* render_view_host_;
+
+ // Indicates whether we should notify about disconnection of this balloon.
+ // This is used to ensure disconnection notifications only happen if
+ // a connection notification has happened and that they happen only once.
+ bool should_notify_on_disconnect_;
+
+ // The title of the balloon page.
+ std::wstring title_;
+
+ // The Mac-specific widget host view. This is owned by its native view,
+ // which this class frees in its destructor.
+ RenderWidgetHostViewMac* render_widget_host_view_;
+
+ // Common implementations of some RenderViewHostDelegate::View methods.
+ RenderViewHostDelegateViewHelper delegate_view_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(BalloonViewHost);
+};
+
+#endif // CHROME_BROWSER_COCOA_NOTIFICATIONS_BALLOON_VIEW_HOST_MAC_H_
diff --git a/chrome/browser/cocoa/notifications/balloon_view_host_mac.mm b/chrome/browser/cocoa/notifications/balloon_view_host_mac.mm
new file mode 100644
index 0000000..464de04
--- /dev/null
+++ b/chrome/browser/cocoa/notifications/balloon_view_host_mac.mm
@@ -0,0 +1,131 @@
+// Copyright (c) 2010 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/cocoa/notifications/balloon_view_host_mac.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/in_process_webkit/dom_storage_context.h"
+#include "chrome/browser/in_process_webkit/webkit_context.h"
+#include "chrome/browser/notifications/balloon.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
+#include "chrome/browser/renderer_host/site_instance.h"
+#include "chrome/browser/renderer_preferences_util.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/renderer_preferences.h"
+
+BalloonViewHost::BalloonViewHost(Balloon* balloon)
+ : initialized_(false),
+ balloon_(balloon),
+ site_instance_(SiteInstance::CreateSiteInstance(balloon->profile())),
+ render_view_host_(NULL),
+ should_notify_on_disconnect_(false),
+ render_widget_host_view_(NULL) {
+ DCHECK(balloon_);
+}
+
+void BalloonViewHost::Shutdown() {
+ if (render_view_host_) {
+ render_view_host_->Shutdown();
+ render_view_host_ = NULL;
+ }
+}
+
+WebPreferences BalloonViewHost::GetWebkitPrefs() {
+ WebPreferences prefs;
+ prefs.allow_scripts_to_close_windows = true;
+ return prefs;
+}
+
+RendererPreferences BalloonViewHost::GetRendererPrefs(Profile* profile) const {
+ RendererPreferences prefs;
+ renderer_preferences_util::UpdateFromSystemSettings(&prefs, profile);
+ return prefs;
+}
+
+void BalloonViewHost::Close(RenderViewHost* render_view_host) {
+ balloon_->CloseByScript();
+}
+
+void BalloonViewHost::RenderViewCreated(RenderViewHost* render_view_host) {
+ render_view_host->Send(new ViewMsg_EnablePreferredSizeChangedMode(
+ render_view_host->routing_id()));
+}
+
+void BalloonViewHost::RendererReady(RenderViewHost* render_view_host) {
+ should_notify_on_disconnect_ = true;
+ NotificationService::current()->Notify(
+ NotificationType::NOTIFY_BALLOON_CONNECTED,
+ Source<Balloon>(balloon_), NotificationService::NoDetails());
+}
+
+void BalloonViewHost::RendererGone(RenderViewHost* render_view_host) {
+ if (!should_notify_on_disconnect_)
+ return;
+
+ should_notify_on_disconnect_ = false;
+ NotificationService::current()->Notify(
+ NotificationType::NOTIFY_BALLOON_DISCONNECTED,
+ Source<Balloon>(balloon_), NotificationService::NoDetails());
+}
+
+// RenderViewHostDelegate::View methods implemented to allow links to
+// open pages in new tabs.
+void BalloonViewHost::CreateNewWindow(int route_id) {
+ delegate_view_helper_.CreateNewWindow(
+ route_id, balloon_->profile(), site_instance_.get(),
+ DOMUIFactory::GetDOMUIType(balloon_->notification().content_url()), NULL);
+}
+
+void BalloonViewHost::ShowCreatedWindow(int route_id,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {
+ // Don't allow pop-ups from notifications.
+ if (disposition == NEW_POPUP)
+ return;
+
+ TabContents* contents = delegate_view_helper_.GetCreatedWindow(route_id);
+ if (contents) {
+ Browser* browser = BrowserList::GetLastActive();
+ browser->AddTabContents(contents, disposition, initial_pos, user_gesture);
+ }
+}
+
+void BalloonViewHost::UpdatePreferredSize(const gfx::Size& new_size) {
+ balloon_->SetContentPreferredSize(new_size);
+}
+
+void BalloonViewHost::UpdateActualSize(const gfx::Size& new_size) {
+ NSView* view = render_widget_host_view_->native_view();
+ NSRect frame = [view frame];
+ frame.size.width = new_size.width();
+ frame.size.height = new_size.height();
+
+ [view setFrame:frame];
+ [view setNeedsDisplay:YES];
+}
+
+void BalloonViewHost::Init() {
+ DCHECK(!render_view_host_) << "BalloonViewHost already initialized.";
+
+ int64 session_storage_namespace_id = balloon_->profile()->GetWebKitContext()->
+ dom_storage_context()->AllocateSessionStorageNamespaceId();
+
+ render_view_host_ = new RenderViewHost(site_instance_.get(),
+ this, MSG_ROUTING_NONE,
+ session_storage_namespace_id);
+
+ render_widget_host_view_ = new RenderWidgetHostViewMac(render_view_host_);
+ render_view_host_->set_view(render_widget_host_view_);
+ render_view_host_->CreateRenderView(GetProfile()->GetRequestContext());
+ render_view_host_->NavigateToURL(balloon_->notification().content_url());
+ initialized_ = true;
+}
diff --git a/chrome/browser/notifications/balloon_collection.cc b/chrome/browser/notifications/balloon_collection.cc
index 224e7bb..58b0f31 100644
--- a/chrome/browser/notifications/balloon_collection.cc
+++ b/chrome/browser/notifications/balloon_collection.cc
@@ -29,9 +29,15 @@ const int kInterBalloonMargin = 5;
} // namespace
// static
+#if defined(OS_MACOSX)
+BalloonCollectionImpl::Layout::Placement
+ BalloonCollectionImpl::Layout::placement_ =
+ Layout::VERTICALLY_FROM_TOP_RIGHT;
+#else
BalloonCollectionImpl::Layout::Placement
BalloonCollectionImpl::Layout::placement_ =
Layout::VERTICALLY_FROM_BOTTOM_RIGHT;
+#endif
BalloonCollectionImpl::BalloonCollectionImpl()
: space_change_listener_(NULL) {
diff --git a/chrome/browser/notifications/balloon_collection_mac.mm b/chrome/browser/notifications/balloon_collection_mac.mm
index 6a652e0..4e8e305 100644
--- a/chrome/browser/notifications/balloon_collection_mac.mm
+++ b/chrome/browser/notifications/balloon_collection_mac.mm
@@ -3,12 +3,16 @@
// found in the LICENSE file.
#include "chrome/browser/notifications/balloon_collection.h"
+#include "chrome/browser/cocoa/notifications/balloon_view_bridge.h"
#include "base/logging.h"
Balloon* BalloonCollectionImpl::MakeBalloon(const Notification& notification,
Profile* profile) {
- // TODO(johnnyg): http://crbug.com/23066. Hook up to views.
- return new Balloon(notification, profile, this);
+ Balloon* balloon = new Balloon(notification, profile, this);
+ balloon->set_view(new BalloonViewBridge());
+ gfx::Size size(layout_.min_balloon_width(), layout_.min_balloon_height());
+ balloon->set_content_size(size);
+ return balloon;
}
diff --git a/chrome/browser/notifications/notification_object_proxy.cc b/chrome/browser/notifications/notification_object_proxy.cc
index a18e0d7..3b45ddf 100644
--- a/chrome/browser/notifications/notification_object_proxy.cc
+++ b/chrome/browser/notifications/notification_object_proxy.cc
@@ -59,8 +59,13 @@ void NotificationObjectProxy::DeliverMessage(IPC::Message* message) {
// Deferred method which runs on the IO thread and sends a message to the
// proxied notification, routing it through the correct host in the browser.
void NotificationObjectProxy::Send(IPC::Message* message) {
+ // Take ownership of the message; ownership will pass to a host if possible.
+ scoped_ptr<IPC::Message> owned_message(message);
+
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_);
- if (host)
- host->Send(message);
+ if (host) {
+ // Pass ownership to the host.
+ host->Send(owned_message.release());
+ }
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index ce01841..94a563e 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -567,6 +567,14 @@
'browser/cocoa/menu_controller.mm',
'browser/cocoa/multi_key_equivalent_button.h',
'browser/cocoa/multi_key_equivalent_button.mm',
+ 'browser/cocoa/notifications/balloon_controller.mm',
+ 'browser/cocoa/notifications/balloon_controller.h',
+ 'browser/cocoa/notifications/balloon_view_bridge.h',
+ 'browser/cocoa/notifications/balloon_view_bridge.mm',
+ 'browser/cocoa/notifications/balloon_view_host_mac.h',
+ 'browser/cocoa/notifications/balloon_view_host_mac.mm',
+ 'browser/cocoa/notifications/balloon_view.h',
+ 'browser/cocoa/notifications/balloon_view.mm',
'browser/cocoa/nsmenuitem_additions.h',
'browser/cocoa/nsmenuitem_additions.mm',
'browser/cocoa/objc_method_swizzle.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 3079a6e..955cd12 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -641,6 +641,7 @@
'browser/cocoa/hyperlink_button_cell_unittest.mm',
'browser/cocoa/menu_button_unittest.mm',
'browser/cocoa/menu_controller_unittest.mm',
+ 'browser/cocoa/notifications/balloon_controller_unittest.mm',
'browser/cocoa/nsimage_cache_unittest.mm',
'browser/cocoa/nsmenuitem_additions_unittest.mm',
'browser/cocoa/objc_method_swizzle_unittest.mm',
diff --git a/chrome/common/desktop_notifications/active_notification_tracker.cc b/chrome/common/desktop_notifications/active_notification_tracker.cc
index bcb6e2b..bc25838 100644
--- a/chrome/common/desktop_notifications/active_notification_tracker.cc
+++ b/chrome/common/desktop_notifications/active_notification_tracker.cc
@@ -13,7 +13,6 @@ using WebKit::WebNotificationPermissionCallback;
bool ActiveNotificationTracker::GetId(
const WebNotification& notification, int& id) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
ReverseTable::iterator iter = reverse_notification_table_.find(notification);
if (iter == reverse_notification_table_.end())
return false;
@@ -23,7 +22,6 @@ bool ActiveNotificationTracker::GetId(
bool ActiveNotificationTracker::GetNotification(
int id, WebNotification* notification) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
WebNotification* lookup = notification_table_.Lookup(id);
if (!lookup)
return false;
@@ -34,7 +32,6 @@ bool ActiveNotificationTracker::GetNotification(
int ActiveNotificationTracker::RegisterNotification(
const WebKit::WebNotification& proxy) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
WebNotification* notification = new WebNotification(proxy);
int id = notification_table_.Add(notification);
reverse_notification_table_[proxy] = id;
@@ -42,7 +39,6 @@ int ActiveNotificationTracker::RegisterNotification(
}
void ActiveNotificationTracker::UnregisterNotification(int id) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
// We want to free the notification after removing it from the table.
scoped_ptr<WebNotification> notification(notification_table_.Lookup(id));
notification_table_.Remove(id);
@@ -61,17 +57,14 @@ void ActiveNotificationTracker::Clear() {
WebNotificationPermissionCallback* ActiveNotificationTracker::GetCallback(
int id) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
return callback_table_.Lookup(id);
}
int ActiveNotificationTracker::RegisterPermissionRequest(
WebNotificationPermissionCallback* callback) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
return callback_table_.Add(callback);
}
void ActiveNotificationTracker::OnPermissionRequestComplete(int id) {
- DCHECK(MessageLoop::current()->type() == MessageLoop::TYPE_DEFAULT);
callback_table_.Remove(id);
}
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index ecca2af..1f61c23 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -653,11 +653,8 @@ void RenderThread::EnsureWebKitInitialized() {
WebRuntimeFeatures::enableApplicationCache(
!command_line.HasSwitch(switches::kDisableApplicationCache));
-#if defined(OS_WIN) || defined(OS_LINUX)
- // Notifications are supported on Windows and Linux only.
WebRuntimeFeatures::enableNotifications(
!command_line.HasSwitch(switches::kDisableDesktopNotifications));
-#endif
WebRuntimeFeatures::enableLocalStorage(
!command_line.HasSwitch(switches::kDisableLocalStorage));