diff options
author | johnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-09 23:16:56 +0000 |
---|---|---|
committer | johnnyg@chromium.org <johnnyg@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-09 23:16:56 +0000 |
commit | 6a760c99f8a9590f8643a832d50980c60c5c3931 (patch) | |
tree | 24cc61cdac8f30b937fe6c39ff342a142835c4c1 | |
parent | 751fae0d3da720758f736b515012e72ce0a1e7e7 (diff) | |
download | chromium_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
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)); |