Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / infobars / extension_infobar_controller.mm
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.
4
5 #import "chrome/browser/ui/cocoa/infobars/extension_infobar_controller.h"
6
7 #include <cmath>
8
9 #include "chrome/browser/extensions/extension_infobar_delegate.h"
10 #include "chrome/browser/extensions/extension_view.h"
11 #include "chrome/browser/extensions/extension_view_host.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 "content/public/browser/web_contents.h"
20 #include "extensions/browser/image_loader.h"
21 #include "extensions/common/constants.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_icon_set.h"
24 #include "extensions/common/extension_resource.h"
25 #include "extensions/common/manifest_handlers/icons_handler.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"
31
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;
39
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;
51 @end
52
53 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to
54 // the extension's button.
55 class InfobarBridge {
56  public:
57   explicit InfobarBridge(ExtensionInfoBarController* owner)
58       : owner_(owner),
59         delegate_([owner delegate]->AsExtensionInfoBarDelegate()),
60         weak_ptr_factory_(this) {
61     LoadIcon();
62   }
63
64   // Load the Extension's icon image.
65   void LoadIcon() {
66     const extensions::Extension* extension = delegate_->extension_view_host()->
67         extension();
68     extensions::ExtensionResource icon_resource =
69         extensions::IconsInfo::GetIconResource(
70             extension,
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()));
80   }
81
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) {
86     if (!delegate_)
87       return;  // The delegate can go away while the image asynchronously loads.
88
89     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
90
91     // Fall back on the default extension icon on failure.
92     const gfx::ImageSkia* icon;
93     if (image.IsEmpty())
94       icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION);
95     else
96       icon = image.ToImageSkia();
97
98     gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW);
99
100     const int image_size = extension_misc::EXTENSION_ICON_BITTY;
101     scoped_ptr<gfx::Canvas> canvas(
102         new gfx::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,
108                          false);
109     canvas->DrawImageInt(*drop_image,
110                          image_size + kDropArrowLeftMarginPx,
111                          image_size / 2);
112     [owner_ setButtonImage:gfx::SkBitmapToNSImage(
113         canvas->ExtractImageRep().sk_bitmap())];
114   }
115
116  private:
117   // Weak. Owns us.
118   ExtensionInfoBarController* owner_;
119
120   // Weak.
121   ExtensionInfoBarDelegate* delegate_;
122
123   base::WeakPtrFactory<InfobarBridge> weak_ptr_factory_;
124
125   DISALLOW_COPY_AND_ASSIGN(InfobarBridge);
126 };
127
128
129 @implementation ExtensionInfoBarController
130
131 - (id)initWithInfoBar:(InfoBarCocoa*)infobar {
132   if ((self = [super initWithInfoBar:infobar])) {
133     dropdownButton_.reset([[MenuButton alloc] init]);
134     [dropdownButton_ setOpenMenuOnClick:YES];
135
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];
142
143     bridge_.reset(new InfobarBridge(self));
144   }
145   return self;
146 }
147
148 - (void)dealloc {
149   [[NSNotificationCenter defaultCenter] removeObserver:self];
150   [super dealloc];
151 }
152
153 - (void)addAdditionalControls {
154   [self removeButtons];
155
156   extensionView_ = [self delegate]->AsExtensionInfoBarDelegate()
157       ->extension_view_host()->view()->GetNativeView();
158
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_];
164
165   // Add the context menu button to the hierarchy.
166   [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES];
167   CGFloat buttonY =
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_];
175
176   // Because the parent view has a bottom border, account for it during
177   // positioning.
178   NSRect extensionFrame = [extensionView_ frame];
179   extensionFrame.origin.y = kBottomBorderHeightPx;
180
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
187   // never fire.
188   if (NSHeight(extensionFrame) > 0.0)
189     [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
190
191   [self adjustExtensionViewSize];
192
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]
197       addObserver:self
198          selector:@selector(extensionViewFrameChanged)
199              name:NSViewFrameDidChangeNotification
200            object:extensionView_];
201
202   [[NSNotificationCenter defaultCenter]
203       addObserver:self
204          selector:@selector(adjustExtensionViewSize)
205              name:NSViewFrameDidChangeNotification
206            object:[self view]];
207 }
208
209 - (void)infobarWillHide {
210   [[dropdownButton_ menu] cancelTracking];
211   [super infobarWillHide];
212 }
213
214 - (void)infobarWillClose {
215   [self disablePopUpMenu:[dropdownButton_ menu]];
216   [super infobarWillClose];
217 }
218
219 - (void)extensionViewFrameChanged {
220   [self adjustExtensionViewSize];
221   [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
222 }
223
224 - (CGFloat)clampedExtensionViewHeight {
225   CGFloat height = [self delegate]->AsExtensionInfoBarDelegate()->height();
226   return std::max(kToolbarMinHeightPx, std::min(height, kToolbarMaxHeightPx));
227 }
228
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];
236 }
237
238 - (void)setButtonImage:(NSImage*)image {
239   [dropdownButton_ setImage:image];
240 }
241
242 - (void)menuNeedsUpdate:(NSMenu*)menu {
243   DCHECK([self isOwned]);
244
245   if (!contextMenuController_) {
246     extensions::ExtensionViewHost* extensionViewHost =
247         [self delegate]->AsExtensionInfoBarDelegate()->extension_view_host();
248     Browser* browser = chrome::FindBrowserWithWebContents(
249         [self delegate]->AsExtensionInfoBarDelegate()->GetWebContents());
250     contextMenuController_.reset([[ExtensionActionContextMenuController alloc]
251         initWithExtension:extensionViewHost->extension()
252                   browser:browser
253           extensionAction:NULL]);
254   }
255
256   [menu removeAllItems];
257   [contextMenuController_ populateMenu:menu];
258 }
259
260 @end
261
262 // static
263 scoped_ptr<infobars::InfoBar> ExtensionInfoBarDelegate::CreateInfoBar(
264     scoped_ptr<ExtensionInfoBarDelegate> delegate) {
265   scoped_ptr<InfoBarCocoa> infobar(
266       new InfoBarCocoa(delegate.PassAs<infobars::InfoBarDelegate>()));
267   base::scoped_nsobject<ExtensionInfoBarController> controller(
268       [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get()]);
269   infobar->set_controller(controller);
270   return infobar.PassAs<infobars::InfoBar>();
271 }