1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/infobars/extension_infobar_controller.h"
9 #include "chrome/browser/extensions/extension_infobar_delegate.h"
10 #include "chrome/browser/extensions/extension_view_host.h"
11 #include "chrome/browser/extensions/image_loader.h"
12 #include "chrome/browser/infobars/infobar_service.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/ui/browser_finder.h"
15 #import "chrome/browser/ui/cocoa/animatable_view.h"
16 #import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu_controller.h"
17 #include "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h"
18 #import "chrome/browser/ui/cocoa/menu_button.h"
19 #include "chrome/common/extensions/extension_constants.h"
20 #include "chrome/common/extensions/extension_icon_set.h"
21 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_view.h"
24 #include "extensions/common/extension.h"
25 #include "extensions/common/extension_resource.h"
26 #include "grit/theme_resources.h"
27 #include "skia/ext/skia_utils_mac.h"
28 #include "ui/base/resource/resource_bundle.h"
29 #include "ui/gfx/canvas.h"
30 #include "ui/gfx/image/image.h"
32 const CGFloat kBottomBorderHeightPx = 1.0;
33 const CGFloat kButtonHeightPx = 26.0;
34 const CGFloat kButtonLeftMarginPx = 2.0;
35 const CGFloat kButtonWidthPx = 34.0;
36 const CGFloat kDropArrowLeftMarginPx = 3.0;
37 const CGFloat kToolbarMinHeightPx = 36.0;
38 const CGFloat kToolbarMaxHeightPx = 72.0;
40 @interface ExtensionInfoBarController(Private)
41 // Called when the extension's hosted NSView has been resized.
42 - (void)extensionViewFrameChanged;
43 // Returns the clamped height of the extension view to be within the min and max
44 // values defined above.
45 - (CGFloat)clampedExtensionViewHeight;
46 // Adjusts the width of the extension's hosted view to match the window's width
47 // and sets the proper height for it as well.
48 - (void)adjustExtensionViewSize;
49 // Sets the image to be used in the button on the left side of the infobar.
50 - (void)setButtonImage:(NSImage*)image;
53 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to
54 // the extension's button.
57 explicit InfobarBridge(ExtensionInfoBarController* owner)
59 delegate_([owner delegate]->AsExtensionInfoBarDelegate()),
60 weak_ptr_factory_(this) {
64 // Load the Extension's icon image.
66 const extensions::Extension* extension = delegate_->extension_view_host()->
68 extensions::ExtensionResource icon_resource =
69 extensions::IconsInfo::GetIconResource(
71 extension_misc::EXTENSION_ICON_BITTY,
72 ExtensionIconSet::MATCH_EXACTLY);
73 extensions::ImageLoader* loader = extensions::ImageLoader::Get(
74 delegate_->extension_view_host()->browser_context());
75 loader->LoadImageAsync(extension, icon_resource,
76 gfx::Size(extension_misc::EXTENSION_ICON_BITTY,
77 extension_misc::EXTENSION_ICON_BITTY),
78 base::Bind(&InfobarBridge::OnImageLoaded,
79 weak_ptr_factory_.GetWeakPtr()));
82 // ImageLoader callback.
83 // TODO(andybons): The infobar view implementations share a lot of the same
84 // code. Come up with a strategy to share amongst them.
85 void OnImageLoaded(const gfx::Image& image) {
87 return; // The delegate can go away while the image asynchronously loads.
89 ResourceBundle& rb = ResourceBundle::GetSharedInstance();
91 // Fall back on the default extension icon on failure.
92 const gfx::ImageSkia* icon;
94 icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION);
96 icon = image.ToImageSkia();
98 gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW);
100 const int image_size = extension_misc::EXTENSION_ICON_BITTY;
101 scoped_ptr<gfx::Canvas> canvas(
103 gfx::Size(image_size + kDropArrowLeftMarginPx + drop_image->width(),
104 image_size), 1.0f, false));
105 canvas->DrawImageInt(*icon,
106 0, 0, icon->width(), icon->height(),
107 0, 0, image_size, image_size,
109 canvas->DrawImageInt(*drop_image,
110 image_size + kDropArrowLeftMarginPx,
112 [owner_ setButtonImage:gfx::SkBitmapToNSImage(
113 canvas->ExtractImageRep().sk_bitmap())];
118 ExtensionInfoBarController* owner_;
121 ExtensionInfoBarDelegate* delegate_;
123 base::WeakPtrFactory<InfobarBridge> weak_ptr_factory_;
125 DISALLOW_COPY_AND_ASSIGN(InfobarBridge);
129 @implementation ExtensionInfoBarController
131 - (id)initWithInfoBar:(InfoBarCocoa*)infobar {
132 if ((self = [super initWithInfoBar:infobar])) {
133 dropdownButton_.reset([[MenuButton alloc] init]);
134 [dropdownButton_ setOpenMenuOnClick:YES];
136 base::scoped_nsobject<NSMenu> contextMenu(
137 [[NSMenu alloc] initWithTitle:@""]);
138 [contextMenu setDelegate:self];
139 // See menu_button.h for documentation on why this is needed.
140 [contextMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
141 [dropdownButton_ setAttachedMenu:contextMenu];
143 bridge_.reset(new InfobarBridge(self));
149 [[NSNotificationCenter defaultCenter] removeObserver:self];
153 - (void)addAdditionalControls {
154 [self removeButtons];
156 extensionView_ = [self delegate]->AsExtensionInfoBarDelegate()
157 ->extension_view_host()->view()->native_view();
159 // Add the extension's RenderWidgetHostView to the view hierarchy of the
160 // InfoBar and make sure to place it below the Close button.
161 [infoBarView_ addSubview:extensionView_
162 positioned:NSWindowBelow
163 relativeTo:(NSView*)closeButton_];
165 // Add the context menu button to the hierarchy.
166 [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES];
168 std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) +
169 kBottomBorderHeightPx;
170 NSRect buttonFrame = NSMakeRect(
171 kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx);
172 [dropdownButton_ setFrame:buttonFrame];
173 [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
174 [infoBarView_ addSubview:dropdownButton_];
176 // Because the parent view has a bottom border, account for it during
178 NSRect extensionFrame = [extensionView_ frame];
179 extensionFrame.origin.y = kBottomBorderHeightPx;
181 [extensionView_ setFrame:extensionFrame];
182 // The extension's native view will only have a height that is non-zero if it
183 // already has been loaded and rendered, which is the case when you switch
184 // back to a tab with an extension infobar within it. The reason this is
185 // needed is because the extension view's frame will not have changed in the
186 // above case, so the NSViewFrameDidChangeNotification registered below will
188 if (NSHeight(extensionFrame) > 0.0)
189 [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
191 [self adjustExtensionViewSize];
193 // These two notification handlers are here to ensure the width of the
194 // native extension view is the same as the browser window's width and that
195 // the parent infobar view matches the height of the extension's native view.
196 [[NSNotificationCenter defaultCenter]
198 selector:@selector(extensionViewFrameChanged)
199 name:NSViewFrameDidChangeNotification
200 object:extensionView_];
202 [[NSNotificationCenter defaultCenter]
204 selector:@selector(adjustExtensionViewSize)
205 name:NSViewFrameDidChangeNotification
209 - (void)infobarWillHide {
210 [[dropdownButton_ menu] cancelTracking];
211 [super infobarWillHide];
214 - (void)infobarWillClose {
215 [self disablePopUpMenu:[dropdownButton_ menu]];
216 [super infobarWillClose];
219 - (void)extensionViewFrameChanged {
220 [self adjustExtensionViewSize];
221 [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
224 - (CGFloat)clampedExtensionViewHeight {
225 CGFloat height = [self delegate]->AsExtensionInfoBarDelegate()->height();
226 return std::max(kToolbarMinHeightPx, std::min(height, kToolbarMaxHeightPx));
229 - (void)adjustExtensionViewSize {
230 [extensionView_ setPostsFrameChangedNotifications:NO];
231 NSSize extensionViewSize = [extensionView_ frame].size;
232 extensionViewSize.width = NSWidth([[self view] frame]);
233 extensionViewSize.height = [self clampedExtensionViewHeight];
234 [extensionView_ setFrameSize:extensionViewSize];
235 [extensionView_ setPostsFrameChangedNotifications:YES];
238 - (void)setButtonImage:(NSImage*)image {
239 [dropdownButton_ setImage:image];
242 - (void)menuNeedsUpdate:(NSMenu*)menu {
243 DCHECK([self isOwned]);
245 if (!contextMenuController_) {
246 extensions::ExtensionViewHost* extensionViewHost =
247 [self delegate]->AsExtensionInfoBarDelegate()->extension_view_host();
248 Browser* browser = chrome::FindBrowserWithWebContents(
249 [self infobar]->OwnerCocoa()->web_contents());
250 contextMenuController_.reset([[ExtensionActionContextMenuController alloc]
251 initWithExtension:extensionViewHost->extension()
253 extensionAction:NULL]);
256 [menu removeAllItems];
257 [contextMenuController_ populateMenu:menu];
263 scoped_ptr<InfoBar> ExtensionInfoBarDelegate::CreateInfoBar(
264 scoped_ptr<ExtensionInfoBarDelegate> delegate) {
265 scoped_ptr<InfoBarCocoa> infobar(
266 new InfoBarCocoa(delegate.PassAs<InfoBarDelegate>()));
267 base::scoped_nsobject<ExtensionInfoBarController> controller(
268 [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get()]);
269 infobar->set_controller(controller);
270 return infobar.PassAs<InfoBar>();