| // Copyright (c) 2012 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/ui/cocoa/infobars/extension_infobar_controller.h" |
| |
| #include <cmath> |
| |
| #include "chrome/browser/extensions/extension_host.h" |
| #include "chrome/browser/extensions/extension_infobar_delegate.h" |
| #include "chrome/browser/extensions/image_loader.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #import "chrome/browser/ui/cocoa/animatable_view.h" |
| #import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu_controller.h" |
| #include "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h" |
| #import "chrome/browser/ui/cocoa/menu_button.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/extensions/extension_icon_set.h" |
| #include "chrome/common/extensions/manifest_handlers/icons_handler.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_view.h" |
| #include "extensions/common/extension_resource.h" |
| #include "grit/theme_resources.h" |
| #include "skia/ext/skia_utils_mac.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/image/image.h" |
| |
| const CGFloat kBottomBorderHeightPx = 1.0; |
| const CGFloat kButtonHeightPx = 26.0; |
| const CGFloat kButtonLeftMarginPx = 2.0; |
| const CGFloat kButtonWidthPx = 34.0; |
| const CGFloat kDropArrowLeftMarginPx = 3.0; |
| const CGFloat kToolbarMinHeightPx = 36.0; |
| const CGFloat kToolbarMaxHeightPx = 72.0; |
| |
| @interface ExtensionInfoBarController(Private) |
| // Called when the extension's hosted NSView has been resized. |
| - (void)extensionViewFrameChanged; |
| // Returns the clamped height of the extension view to be within the min and max |
| // values defined above. |
| - (CGFloat)clampedExtensionViewHeight; |
| // Adjusts the width of the extension's hosted view to match the window's width |
| // and sets the proper height for it as well. |
| - (void)adjustExtensionViewSize; |
| // Sets the image to be used in the button on the left side of the infobar. |
| - (void)setButtonImage:(NSImage*)image; |
| @end |
| |
| // A helper class to bridge the asynchronous Skia bitmap loading mechanism to |
| // the extension's button. |
| class InfobarBridge : public ExtensionInfoBarDelegate::DelegateObserver { |
| public: |
| explicit InfobarBridge(ExtensionInfoBarController* owner) |
| : owner_(owner), |
| delegate_([owner delegate]->AsExtensionInfoBarDelegate()), |
| weak_ptr_factory_(this) { |
| delegate_->set_observer(this); |
| LoadIcon(); |
| } |
| |
| virtual ~InfobarBridge() { |
| if (delegate_) |
| delegate_->set_observer(NULL); |
| } |
| |
| // Load the Extension's icon image. |
| void LoadIcon() { |
| const extensions::Extension* extension = delegate_->extension_host()-> |
| extension(); |
| extensions::ExtensionResource icon_resource = |
| extensions::IconsInfo::GetIconResource( |
| extension, |
| extension_misc::EXTENSION_ICON_BITTY, |
| ExtensionIconSet::MATCH_EXACTLY); |
| extensions::ImageLoader* loader = |
| extensions::ImageLoader::Get(delegate_->extension_host()->profile()); |
| loader->LoadImageAsync(extension, icon_resource, |
| gfx::Size(extension_misc::EXTENSION_ICON_BITTY, |
| extension_misc::EXTENSION_ICON_BITTY), |
| base::Bind(&InfobarBridge::OnImageLoaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // ImageLoader callback. |
| // TODO(andybons): The infobar view implementations share a lot of the same |
| // code. Come up with a strategy to share amongst them. |
| void OnImageLoaded(const gfx::Image& image) { |
| if (!delegate_) |
| return; // The delegate can go away while the image asynchronously loads. |
| |
| ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| |
| // Fall back on the default extension icon on failure. |
| const gfx::ImageSkia* icon; |
| if (image.IsEmpty()) |
| icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION); |
| else |
| icon = image.ToImageSkia(); |
| |
| gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW); |
| |
| const int image_size = extension_misc::EXTENSION_ICON_BITTY; |
| scoped_ptr<gfx::Canvas> canvas( |
| new gfx::Canvas( |
| gfx::Size(image_size + kDropArrowLeftMarginPx + drop_image->width(), |
| image_size), 1.0f, false)); |
| canvas->DrawImageInt(*icon, |
| 0, 0, icon->width(), icon->height(), |
| 0, 0, image_size, image_size, |
| false); |
| canvas->DrawImageInt(*drop_image, |
| image_size + kDropArrowLeftMarginPx, |
| image_size / 2); |
| [owner_ setButtonImage:gfx::SkBitmapToNSImage( |
| canvas->ExtractImageRep().sk_bitmap())]; |
| } |
| |
| // Overridden from ExtensionInfoBarDelegate::DelegateObserver: |
| virtual void OnDelegateDeleted() OVERRIDE { |
| delegate_ = NULL; |
| } |
| |
| private: |
| // Weak. Owns us. |
| ExtensionInfoBarController* owner_; |
| |
| // Weak. |
| ExtensionInfoBarDelegate* delegate_; |
| |
| base::WeakPtrFactory<InfobarBridge> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InfobarBridge); |
| }; |
| |
| |
| @implementation ExtensionInfoBarController |
| |
| - (id)initWithInfoBar:(InfoBarCocoa*)infobar |
| window:(NSWindow*)window { |
| if ((self = [super initWithInfoBar:infobar])) { |
| window_ = window; |
| dropdownButton_.reset([[MenuButton alloc] init]); |
| [dropdownButton_ setOpenMenuOnClick:YES]; |
| |
| extensions::ExtensionHost* extensionHost = |
| [self delegate]->AsExtensionInfoBarDelegate()->extension_host(); |
| Browser* browser = chrome::FindBrowserWithWebContents( |
| [self infobar]->OwnerCocoa()->web_contents()); |
| contextMenuController_.reset([[ExtensionActionContextMenuController alloc] |
| initWithExtension:extensionHost->extension() |
| browser:browser |
| extensionAction:NULL]); |
| |
| base::scoped_nsobject<NSMenu> contextMenu( |
| [[NSMenu alloc] initWithTitle:@""]); |
| [contextMenu setDelegate:self]; |
| // See menu_button.h for documentation on why this is needed. |
| [contextMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""]; |
| [dropdownButton_ setAttachedMenu:contextMenu]; |
| |
| bridge_.reset(new InfobarBridge(self)); |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| [super dealloc]; |
| } |
| |
| - (void)addAdditionalControls { |
| [self removeButtons]; |
| |
| extensionView_ = [self delegate]->AsExtensionInfoBarDelegate() |
| ->extension_host()->view()->native_view(); |
| |
| // Add the extension's RenderWidgetHostView to the view hierarchy of the |
| // InfoBar and make sure to place it below the Close button. |
| [infoBarView_ addSubview:extensionView_ |
| positioned:NSWindowBelow |
| relativeTo:(NSView*)closeButton_]; |
| |
| // Add the context menu button to the hierarchy. |
| [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES]; |
| CGFloat buttonY = |
| std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) + |
| kBottomBorderHeightPx; |
| NSRect buttonFrame = NSMakeRect( |
| kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx); |
| [dropdownButton_ setFrame:buttonFrame]; |
| [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin]; |
| [infoBarView_ addSubview:dropdownButton_]; |
| |
| // Because the parent view has a bottom border, account for it during |
| // positioning. |
| NSRect extensionFrame = [extensionView_ frame]; |
| extensionFrame.origin.y = kBottomBorderHeightPx; |
| |
| [extensionView_ setFrame:extensionFrame]; |
| // The extension's native view will only have a height that is non-zero if it |
| // already has been loaded and rendered, which is the case when you switch |
| // back to a tab with an extension infobar within it. The reason this is |
| // needed is because the extension view's frame will not have changed in the |
| // above case, so the NSViewFrameDidChangeNotification registered below will |
| // never fire. |
| if (NSHeight(extensionFrame) > 0.0) |
| [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]); |
| |
| [self adjustExtensionViewSize]; |
| |
| // These two notification handlers are here to ensure the width of the |
| // native extension view is the same as the browser window's width and that |
| // the parent infobar view matches the height of the extension's native view. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(extensionViewFrameChanged) |
| name:NSViewFrameDidChangeNotification |
| object:extensionView_]; |
| |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(adjustExtensionViewSize) |
| name:NSWindowDidResizeNotification |
| object:window_]; |
| } |
| |
| - (void)infobarWillClose { |
| [self disablePopUpMenu:[dropdownButton_ menu]]; |
| [super infobarWillClose]; |
| } |
| |
| - (void)extensionViewFrameChanged { |
| [self adjustExtensionViewSize]; |
| [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]); |
| } |
| |
| - (CGFloat)clampedExtensionViewHeight { |
| CGFloat height = [self delegate]->AsExtensionInfoBarDelegate()->height(); |
| return std::max(kToolbarMinHeightPx, std::min(height, kToolbarMaxHeightPx)); |
| } |
| |
| - (void)adjustExtensionViewSize { |
| [extensionView_ setPostsFrameChangedNotifications:NO]; |
| NSSize extensionViewSize = [extensionView_ frame].size; |
| extensionViewSize.width = NSWidth([window_ frame]); |
| extensionViewSize.height = [self clampedExtensionViewHeight]; |
| [extensionView_ setFrameSize:extensionViewSize]; |
| [extensionView_ setPostsFrameChangedNotifications:YES]; |
| } |
| |
| - (void)setButtonImage:(NSImage*)image { |
| [dropdownButton_ setImage:image]; |
| } |
| |
| - (void)menuNeedsUpdate:(NSMenu*)menu { |
| [menu removeAllItems]; |
| [contextMenuController_ populateMenu:menu]; |
| } |
| |
| @end |
| |
| InfoBar* ExtensionInfoBarDelegate::CreateInfoBar(InfoBarService* owner) { |
| scoped_ptr<InfoBarCocoa> infobar(new InfoBarCocoa(owner, this)); |
| NSWindow* window = |
| [(NSView*)owner->web_contents()->GetView()->GetContentNativeView() |
| window]; |
| base::scoped_nsobject<ExtensionInfoBarController> controller( |
| [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get() |
| window:window]); |
| infobar->set_controller(controller); |
| return infobar.release(); |
| } |