diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-30 02:45:36 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-30 02:45:36 +0000 |
commit | d27c133619e31d7e237a7e799ae3a00a03652285 (patch) | |
tree | c8ba3ecae798ca9efd8adf5da560416678b07c1c /chrome/browser | |
parent | 29177ad26f80c0bc590211406fca7e9498d954ef (diff) | |
download | chromium_src-d27c133619e31d7e237a7e799ae3a00a03652285.zip chromium_src-d27c133619e31d7e237a7e799ae3a00a03652285.tar.gz chromium_src-d27c133619e31d7e237a7e799ae3a00a03652285.tar.bz2 |
Adds popups to browser actions, completing the feature.
Adds functionality to choose between two different InfoBubbleView types: white background and gradient background.
TBR=andybons@chromium.org
Review URL: http://codereview.chromium.org/453003
git-svn-id: svn://svn.chromium.org/chrome/branches/249/src@33261 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
10 files changed, 407 insertions, 18 deletions
diff --git a/chrome/browser/cocoa/extension_view_mac.mm b/chrome/browser/cocoa/extension_view_mac.mm index 171cc30..b1f9d0e 100644 --- a/chrome/browser/cocoa/extension_view_mac.mm +++ b/chrome/browser/cocoa/extension_view_mac.mm @@ -53,6 +53,7 @@ void ExtensionViewMac::UpdatePreferredSize(const gfx::Size& new_size) { NSView* view = native_view(); NSRect frame = [view frame]; frame.size.width = new_size.width(); + frame.size.height = new_size.height(); // RenderWidgetHostViewCocoa overrides setFrame but not setFrameSize. [view setFrame:frame]; diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.h b/chrome/browser/cocoa/extensions/browser_actions_controller.h index eb026c1..08734bb 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.h +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.h @@ -13,6 +13,7 @@ class Browser; @class BrowserActionButton; class Extension; +@class ExtensionPopupController; class ExtensionsServiceObserverBridge; class Profile; @@ -42,6 +43,9 @@ extern NSString* const kBrowserActionsChangedNotification; // The order of the BrowserActionButton objects within the dictionary. scoped_nsobject<NSMutableArray> buttonOrder_; + + // The controller for the popup displayed if a browser action has one. Weak. + ExtensionPopupController* popupController_; } // Initializes the controller given the current browser and container view that @@ -56,6 +60,10 @@ extern NSString* const kBrowserActionsChangedNotification; // Hides the browser action's popup menu (if one is present and visible). - (void)hidePopup; +// Returns the controller used to display the popup being shown. If no popup is +// currently open, then nil is returned. +- (ExtensionPopupController*)popup; + // Marks the container view for redraw. Called by the extension service // notification bridge. - (void)browserActionVisibilityHasChanged; diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm index 80876a8..5414c27 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm @@ -9,6 +9,7 @@ #include "app/gfx/canvas_paint.h" #include "base/sys_string_conversions.h" #include "chrome/browser/browser.h" +#include "chrome/browser/cocoa/extensions/extension_popup_controller.h" #include "chrome/browser/cocoa/toolbar_button_cell.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extensions_service.h" @@ -278,8 +279,8 @@ class ExtensionsServiceObserverBridge : public NotificationObserver { break; } case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: - //if (Details<ExtensionHost>(popup_->host()) != details) - // return; + if (Details<ExtensionHost>([[owner_ popup] host]) != details) + return; [owner_ hidePopup]; break; default: @@ -318,7 +319,12 @@ class ExtensionsServiceObserverBridge : public NotificationObserver { } - (void)hidePopup { - NOTIMPLEMENTED(); + [popupController_ close]; + popupController_ = nil; +} + +- (ExtensionPopupController*)popup { + return popupController_; } - (void)browserActionVisibilityHasChanged { @@ -409,8 +415,28 @@ class ExtensionsServiceObserverBridge : public NotificationObserver { - (void)browserActionClicked:(BrowserActionButton*)sender { ExtensionAction* action = [sender extension]->browser_action(); if (action->has_popup()) { - // Popups are not implemented for Mac yet. - NOTIMPLEMENTED(); + NSString* extensionId = base::SysUTF8ToNSString([sender extension]->id()); + // If the extension ID is not valid UTF-8, then the NSString will be nil + // and an exception will be thrown when calling objectForKey below, hosing + // the browser. Check it. + DCHECK(extensionId); + if (!extensionId) + return; + BrowserActionButton* actionButton = [buttons_ objectForKey:extensionId]; + NSRect relativeButtonBounds = [[[actionButton window] contentView] + convertRect:[actionButton bounds] + fromView:actionButton]; + NSPoint arrowPoint = [[actionButton window] convertBaseToScreen:NSMakePoint( + NSMinX(relativeButtonBounds), + NSMinY(relativeButtonBounds))]; + // Adjust the anchor point to be at the center of the browser action button. + arrowPoint.x += kBrowserActionWidth / 2; + + popupController_ = + [[ExtensionPopupController showURL:action->popup_url() + inBrowser:browser_ + anchoredAt:arrowPoint + arrowLocation:kTopRight] retain]; } else { ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( profile_, action->extension_id(), browser_); diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller.h b/chrome/browser/cocoa/extensions/extension_popup_controller.h new file mode 100644 index 0000000..f6858bd --- /dev/null +++ b/chrome/browser/cocoa/extensions/extension_popup_controller.h @@ -0,0 +1,58 @@ +// 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_EXTENSIONS_EXTENSION_POPUP_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_POPUP_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +#import "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/cocoa/info_bubble_view.h" +#include "googleurl/src/gurl.h" + +class Browser; +class ExtensionHost; +@class InfoBubbleWindow; + +// This controller manages a single browser action popup that can appear once a +// user has clicked on a browser action button. It instantiates the extension +// popup view showing the content and resizes the window to accomodate any size +// changes as they occur. +// There can only be one browser action popup open at a time. +@interface ExtensionPopupController : NSWindowController { + @private + // The native extension view retrieved from the extension host. Weak. + NSView* extensionView_; + + // The popup's parent window. Weak. + NSWindow* parentWindow_; + + // Where the window is anchored. Right now it's the bottom center of the + // browser action button. + NSPoint anchor_; + + // The extension host object. + scoped_ptr<ExtensionHost> host_; +} + +// Returns a pointer to the extension host object associated with this +// browser action. +- (ExtensionHost*)host; + +// Starts the process of showing the given popup URL. Instantiates an +// ExtensionPopupController with the parent window retrieved from |browser|, a +// host for the popup created by the extension process manager specific to the +// browser profile and the remaining arguments |anchoredAt| and |arrowLocation|. +// |anchoredAt| is expected to be in the screen's coordinates at the bottom +// center of the browser action button. +// The actual display of the popup is delayed until the page contents finish +// loading in order to minimize UI flashing and resizing. ++ (ExtensionPopupController*)showURL:(GURL)url + inBrowser:(Browser*)browser + anchoredAt:(NSPoint)anchoredAt + arrowLocation:(BubbleArrowLocation)arrowLocation; +@end + +#endif // CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_POPUP_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller.mm b/chrome/browser/cocoa/extensions/extension_popup_controller.mm new file mode 100644 index 0000000..48a8a22 --- /dev/null +++ b/chrome/browser/cocoa/extensions/extension_popup_controller.mm @@ -0,0 +1,178 @@ +// 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 "chrome/browser/cocoa/extensions/extension_popup_controller.h" + +#include <algorithm> + +#include "chrome/browser/browser.h" +#import "chrome/browser/cocoa/browser_window_cocoa.h" +#import "chrome/browser/cocoa/extension_view_mac.h" +#import "chrome/browser/cocoa/info_bubble_window.h" +#include "chrome/browser/profile.h" + +// The minimum/maximum dimensions of the popup. +// The minimum is just a little larger than the size of the button itself. +// The maximum is an arbitrary number that should be smaller than most screens. +namespace { +const CGFloat kMinWidth = 25; +const CGFloat kMinHeight = 25; +const CGFloat kMaxWidth = 800; +const CGFloat kMaxHeight = 600; +} // namespace + +@interface ExtensionPopupController(Private) +// Callers should be using the public static method for initialization. +// NOTE: This takes ownership of |host|. +- (id)initWithHost:(ExtensionHost*)host + parentWindow:(NSWindow*)parentWindow + anchoredAt:(NSPoint)anchoredAt + arrowLocation:(BubbleArrowLocation)arrowLocation; + +// Called when the extension's hosted NSView has been resized. +- (void)extensionViewFrameChanged; +@end + +@implementation ExtensionPopupController + +- (id)initWithHost:(ExtensionHost*)host + parentWindow:(NSWindow*)parentWindow + anchoredAt:(NSPoint)anchoredAt + arrowLocation:(BubbleArrowLocation)arrowLocation { + parentWindow_ = parentWindow; + anchor_ = anchoredAt; + host_.reset(host); + + scoped_nsobject<InfoBubbleView> view([[InfoBubbleView alloc] init]); + if (!view.get()) + return nil; + [view setArrowLocation:arrowLocation]; + [view setBubbleType:kWhiteInfoBubble]; + + host->view()->set_is_toolstrip(NO); + + extensionView_ = host->view()->native_view(); + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(extensionViewFrameChanged) + name:NSViewFrameDidChangeNotification + object:extensionView_]; + + [view addSubview:extensionView_]; + scoped_nsobject<InfoBubbleWindow> window( + [[InfoBubbleWindow alloc] + initWithContentRect:NSZeroRect + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:YES]); + if (!window.get()) + return nil; + + [window setDelegate:self]; + [window setContentView:view]; + self = [super initWithWindow:window]; + + return self; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [super dealloc]; +} + +- (void)windowWillClose:(NSNotification *)notification { + [self autorelease]; +} + +- (ExtensionHost*)host { + return host_.get(); +} + +- (void)windowDidResignKey:(NSNotification *)notification { + DCHECK_EQ([notification object], [self window]); + [self close]; +} + +- (void)close { + [parentWindow_ removeChildWindow:[self window]]; + [super close]; +} + ++ (ExtensionPopupController*)showURL:(GURL)url + inBrowser:(Browser*)browser + anchoredAt:(NSPoint)anchoredAt + arrowLocation:(BubbleArrowLocation)arrowLocation { + DCHECK(browser); + if (!browser) + return nil; + + ExtensionProcessManager* manager = + browser->profile()->GetExtensionProcessManager(); + DCHECK(manager); + if (!manager) + return nil; + + ExtensionHost* host = manager->CreatePopup(url, browser); + DCHECK(host); + if (!host) + return nil; + + // Takes ownership of |host|. Also will autorelease itself when the popup is + // closed, so no need to do that here. + ExtensionPopupController* popup = [[ExtensionPopupController alloc] + initWithHost:host + parentWindow:browser->window()->GetNativeHandle() + anchoredAt:anchoredAt + arrowLocation:arrowLocation]; + + return popup; +} + +- (void)extensionViewFrameChanged { + // Constrain the size of the view. + [extensionView_ setFrameSize:NSMakeSize( + std::max(kMinWidth, std::min(kMaxWidth, NSWidth([extensionView_ frame]))), + std::max(kMinHeight, + std::min(kMaxHeight, NSHeight([extensionView_ frame]))))]; + + // Pad the window by half of the rounded corner radius to prevent the + // extension's view from bleeding out over the corners. + CGFloat inset = kBubbleCornerRadius / 2.0; + [extensionView_ setFrameOrigin:NSMakePoint(inset, inset)]; + + NSRect frame = [extensionView_ frame]; + frame.size.height += kBubbleArrowHeight + kBubbleCornerRadius; + frame.size.width += kBubbleCornerRadius; + // Adjust the origin according to the height and width so that the arrow is + // positioned correctly at the middle and slightly down from the button. + NSPoint windowOrigin = anchor_; + windowOrigin.x -= NSWidth(frame) - kBubbleArrowXOffset - + (kBubbleArrowWidth / 2.0); + windowOrigin.y -= NSHeight(frame) - (kBubbleArrowHeight / 2.0); + frame.origin = windowOrigin; + [[self window] setFrame:frame display:YES]; + + // A NSViewFrameDidChangeNotification won't be sent until the extension view + // content is loaded. The window is hidden on init, so show it the first time + // the notification is fired (and consequently the view contents have loaded). + // + // TODO(andybons): It seems that if the frame changes again before the + // animation of the window show is completed, the window size gets super + // janky. Fix this. + if (![[self window] isVisible]) { + [self showWindow:self]; + } +} + +// We want this to be a child of a browser window. addChildWindow: (called from +// this function) will bring the window on-screen; unfortunately, +// [NSWindowController showWindow:] will also bring it on-screen (but will cause +// unexpected changes to the window's position). We cannot have an +// addChildWindow: and a subsequent showWindow:. Thus, we have our own version. +- (void)showWindow:(id)sender { + [parentWindow_ addChildWindow:[self window] ordered:NSWindowAbove]; + [[self window] makeKeyAndOrderFront:self]; +} + +@end diff --git a/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm b/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm new file mode 100644 index 0000000..08fbbaa --- /dev/null +++ b/chrome/browser/cocoa/extensions/extension_popup_controller_unittest.mm @@ -0,0 +1,90 @@ +// 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/scoped_nsobject.h" +#include "chrome/browser/cocoa/browser_test_helper.h" +#include "chrome/browser/cocoa/cocoa_test_helper.h" +#include "chrome/browser/cocoa/extensions/extension_popup_controller.h" +#include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/test/testing_profile.h" + +namespace { + +class ExtensionTestingProfile : public TestingProfile { + public: + ExtensionTestingProfile() {} + + FilePath GetExtensionsInstallDir() { + return GetPath().AppendASCII(ExtensionsService::kInstallDirectoryName); + } + + void InitExtensionProfile() { + DCHECK(!GetExtensionProcessManager()); + DCHECK(!GetExtensionsService()); + + manager_.reset(new ExtensionProcessManager(this)); + service_ = new ExtensionsService(this, + CommandLine::ForCurrentProcess(), + GetPrefs(), + GetExtensionsInstallDir(), + false); + service_->set_extensions_enabled(true); + service_->set_show_extensions_prompts(false); + service_->ClearProvidersForTesting(); + service_->Init(); + } + + void ShutdownExtensionProfile() { + manager_.reset(); + service_ = NULL; + } + + virtual ExtensionProcessManager* GetExtensionProcessManager() { + return manager_.get(); + } + + virtual ExtensionsService* GetExtensionsService() { + return service_.get(); + } + + private: + scoped_ptr<ExtensionProcessManager> manager_; + scoped_refptr<ExtensionsService> service_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionTestingProfile); +}; + +class ExtensionPopupControllerTest : public CocoaTest { + public: + virtual void SetUp() { + CocoaTest::SetUp(); + profile_.reset(new ExtensionTestingProfile()); + profile_->InitExtensionProfile(); + browser_.reset(new Browser(Browser::TYPE_NORMAL, profile_.get())); + popup_controller_.reset( + [ExtensionPopupController showURL:GURL("http://google.com") + inBrowser:browser_.get() + anchoredAt:NSZeroPoint + arrowLocation:kTopRight]); + } + virtual void TearDown() { + profile_->ShutdownExtensionProfile(); + CocoaTest::TearDown(); + } + + protected: + scoped_nsobject<ExtensionPopupController> popup_controller_; + scoped_ptr<Browser> browser_; + scoped_ptr<ExtensionTestingProfile> profile_; +}; + +TEST_F(ExtensionPopupControllerTest, DISABLED_Basics) { + // TODO(andybons): Better mechanisms for mocking out the extensions service + // and extensions for easy testing need to be implemented. + // http://crbug.com/28316 + EXPECT_TRUE(popup_controller_.get()); +} + +} // namespace diff --git a/chrome/browser/cocoa/info_bubble_view.h b/chrome/browser/cocoa/info_bubble_view.h index fc95bc9..d5cfa4e 100644 --- a/chrome/browser/cocoa/info_bubble_view.h +++ b/chrome/browser/cocoa/info_bubble_view.h @@ -2,20 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef CHROME_BROWSER_COCOA_INFO_BUBBLE_VIEW_H_ +#define CHROME_BROWSER_COCOA_INFO_BUBBLE_VIEW_H_ + #import <Cocoa/Cocoa.h> +extern const CGFloat kBubbleArrowHeight; +extern const CGFloat kBubbleArrowWidth; +extern const CGFloat kBubbleArrowXOffset; +extern const CGFloat kBubbleCornerRadius; + enum BubbleArrowLocation { kTopLeft, kTopRight, }; +enum InfoBubbleType { + kGradientInfoBubble, + kWhiteInfoBubble +}; + // Content view for a bubble with an arrow showing arbitrary content. // This is where nonrectangular drawing happens. @interface InfoBubbleView : NSView { @private BubbleArrowLocation arrowLocation_; + + // The type simply is used to determine what sort of background it should + // draw. + InfoBubbleType bubbleType_; } @property (assign, nonatomic) BubbleArrowLocation arrowLocation; +@property (assign, nonatomic) InfoBubbleType bubbleType; @end + +#endif // CHROME_BROWSER_COCOA_INFO_BUBBLE_VIEW_H_ diff --git a/chrome/browser/cocoa/info_bubble_view.mm b/chrome/browser/cocoa/info_bubble_view.mm index 90508b8..4db4ab8 100644 --- a/chrome/browser/cocoa/info_bubble_view.mm +++ b/chrome/browser/cocoa/info_bubble_view.mm @@ -7,21 +7,21 @@ #include "base/logging.h" #import "third_party/GTM/AppKit/GTMTheme.h" -namespace { -// TODO(andybons): confirm constants with UI dudes -const CGFloat kBubbleCornerRadius = 8.0; -const CGFloat kBubbleArrowXOffset = 10.0; -const CGFloat kBubbleArrowWidth = 15.0; -const CGFloat kBubbleArrowHeight = 8.0; -} +// TODO(andybons): confirm constants with UI dudes. +extern const CGFloat kBubbleArrowHeight = 8.0; +extern const CGFloat kBubbleArrowWidth = 15.0; +extern const CGFloat kBubbleArrowXOffset = 10.0; +extern const CGFloat kBubbleCornerRadius = 8.0; @implementation InfoBubbleView @synthesize arrowLocation = arrowLocation_; +@synthesize bubbleType = bubbleType_; - (id)initWithFrame:(NSRect)frameRect { if ((self = [super initWithFrame:frameRect])) { arrowLocation_ = kTopLeft; + bubbleType_ = kGradientInfoBubble; } return self; @@ -61,11 +61,16 @@ const CGFloat kBubbleArrowHeight = 8.0; arrowStart.y)]; [bezier closePath]; - // Then fill the inside. - GTMTheme *theme = [GTMTheme defaultTheme]; - NSGradient *gradient = [theme gradientForStyle:GTMThemeStyleToolBar - state:NO]; - [gradient drawInBezierPath:bezier angle:0.0]; + // Then fill the inside depending on the type of bubble. + if (bubbleType_ == kGradientInfoBubble) { + GTMTheme *theme = [GTMTheme defaultTheme]; + NSGradient *gradient = [theme gradientForStyle:GTMThemeStyleToolBar + state:NO]; + [gradient drawInBezierPath:bezier angle:0.0]; + } else if (bubbleType_ == kWhiteInfoBubble) { + [[NSColor whiteColor] set]; + [bezier fill]; + } } @end diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc index 63021f6..eca6fd1 100644 --- a/chrome/browser/extensions/extension_host.cc +++ b/chrome/browser/extensions/extension_host.cc @@ -608,7 +608,8 @@ Browser* ExtensionHost::GetBrowser() { void ExtensionHost::SetRenderViewType(ViewType::Type type) { DCHECK(type == ViewType::EXTENSION_MOLE || - type == ViewType::EXTENSION_TOOLSTRIP); + type == ViewType::EXTENSION_TOOLSTRIP || + type == ViewType::EXTENSION_POPUP); extension_host_type_ = type; render_view_host()->ViewTypeChanged(extension_host_type_); } diff --git a/chrome/browser/views/extensions/extension_view.cc b/chrome/browser/views/extensions/extension_view.cc index 7945307..9bf87f5 100644 --- a/chrome/browser/views/extensions/extension_view.cc +++ b/chrome/browser/views/extensions/extension_view.cc @@ -13,6 +13,8 @@ #include "chrome/browser/renderer_host/render_widget_host_view_win.h" #elif defined(OS_LINUX) #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" +#elif defined(OS_MACOSX) +#include "chrome/browser/renderer_host/render_widget_host_view_mac.h" #endif ExtensionView::ExtensionView(ExtensionHost* host, Browser* browser) |