Upstream version 7.36.149.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_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 "content/public/browser/web_contents.h"
20 #include "extensions/common/constants.h"
21 #include "extensions/common/extension.h"
22 #include "extensions/common/extension_icon_set.h"
23 #include "extensions/common/extension_resource.h"
24 #include "extensions/common/manifest_handlers/icons_handler.h"
25 #include "grit/theme_resources.h"
26 #include "skia/ext/skia_utils_mac.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/canvas.h"
29 #include "ui/gfx/image/image.h"
30
31 const CGFloat kBottomBorderHeightPx = 1.0;
32 const CGFloat kButtonHeightPx = 26.0;
33 const CGFloat kButtonLeftMarginPx = 2.0;
34 const CGFloat kButtonWidthPx = 34.0;
35 const CGFloat kDropArrowLeftMarginPx = 3.0;
36 const CGFloat kToolbarMinHeightPx = 36.0;
37 const CGFloat kToolbarMaxHeightPx = 72.0;
38
39 @interface ExtensionInfoBarController(Private)
40 // Called when the extension's hosted NSView has been resized.
41 - (void)extensionViewFrameChanged;
42 // Returns the clamped height of the extension view to be within the min and max
43 // values defined above.
44 - (CGFloat)clampedExtensionViewHeight;
45 // Adjusts the width of the extension's hosted view to match the window's width
46 // and sets the proper height for it as well.
47 - (void)adjustExtensionViewSize;
48 // Sets the image to be used in the button on the left side of the infobar.
49 - (void)setButtonImage:(NSImage*)image;
50 @end
51
52 // A helper class to bridge the asynchronous Skia bitmap loading mechanism to
53 // the extension's button.
54 class InfobarBridge {
55  public:
56   explicit InfobarBridge(ExtensionInfoBarController* owner)
57       : owner_(owner),
58         delegate_([owner delegate]->AsExtensionInfoBarDelegate()),
59         weak_ptr_factory_(this) {
60     LoadIcon();
61   }
62
63   // Load the Extension's icon image.
64   void LoadIcon() {
65     const extensions::Extension* extension = delegate_->extension_view_host()->
66         extension();
67     extensions::ExtensionResource icon_resource =
68         extensions::IconsInfo::GetIconResource(
69             extension,
70             extension_misc::EXTENSION_ICON_BITTY,
71             ExtensionIconSet::MATCH_EXACTLY);
72     extensions::ImageLoader* loader = extensions::ImageLoader::Get(
73         delegate_->extension_view_host()->browser_context());
74     loader->LoadImageAsync(extension, icon_resource,
75                            gfx::Size(extension_misc::EXTENSION_ICON_BITTY,
76                                      extension_misc::EXTENSION_ICON_BITTY),
77                            base::Bind(&InfobarBridge::OnImageLoaded,
78                                       weak_ptr_factory_.GetWeakPtr()));
79   }
80
81   // ImageLoader callback.
82   // TODO(andybons): The infobar view implementations share a lot of the same
83   // code. Come up with a strategy to share amongst them.
84   void OnImageLoaded(const gfx::Image& image) {
85     if (!delegate_)
86       return;  // The delegate can go away while the image asynchronously loads.
87
88     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
89
90     // Fall back on the default extension icon on failure.
91     const gfx::ImageSkia* icon;
92     if (image.IsEmpty())
93       icon = rb.GetImageSkiaNamed(IDR_EXTENSIONS_SECTION);
94     else
95       icon = image.ToImageSkia();
96
97     gfx::ImageSkia* drop_image = rb.GetImageSkiaNamed(IDR_APP_DROPARROW);
98
99     const int image_size = extension_misc::EXTENSION_ICON_BITTY;
100     scoped_ptr<gfx::Canvas> canvas(
101         new gfx::Canvas(
102             gfx::Size(image_size + kDropArrowLeftMarginPx + drop_image->width(),
103                       image_size), 1.0f, false));
104     canvas->DrawImageInt(*icon,
105                          0, 0, icon->width(), icon->height(),
106                          0, 0, image_size, image_size,
107                          false);
108     canvas->DrawImageInt(*drop_image,
109                          image_size + kDropArrowLeftMarginPx,
110                          image_size / 2);
111     [owner_ setButtonImage:gfx::SkBitmapToNSImage(
112         canvas->ExtractImageRep().sk_bitmap())];
113   }
114
115  private:
116   // Weak. Owns us.
117   ExtensionInfoBarController* owner_;
118
119   // Weak.
120   ExtensionInfoBarDelegate* delegate_;
121
122   base::WeakPtrFactory<InfobarBridge> weak_ptr_factory_;
123
124   DISALLOW_COPY_AND_ASSIGN(InfobarBridge);
125 };
126
127
128 @implementation ExtensionInfoBarController
129
130 - (id)initWithInfoBar:(InfoBarCocoa*)infobar {
131   if ((self = [super initWithInfoBar:infobar])) {
132     dropdownButton_.reset([[MenuButton alloc] init]);
133     [dropdownButton_ setOpenMenuOnClick:YES];
134
135     base::scoped_nsobject<NSMenu> contextMenu(
136         [[NSMenu alloc] initWithTitle:@""]);
137     [contextMenu setDelegate:self];
138     // See menu_button.h for documentation on why this is needed.
139     [contextMenu addItemWithTitle:@"" action:NULL keyEquivalent:@""];
140     [dropdownButton_ setAttachedMenu:contextMenu];
141
142     bridge_.reset(new InfobarBridge(self));
143   }
144   return self;
145 }
146
147 - (void)dealloc {
148   [[NSNotificationCenter defaultCenter] removeObserver:self];
149   [super dealloc];
150 }
151
152 - (void)addAdditionalControls {
153   [self removeButtons];
154
155   extensionView_ = [self delegate]->AsExtensionInfoBarDelegate()
156       ->extension_view_host()->view()->native_view();
157
158   // Add the extension's RenderWidgetHostView to the view hierarchy of the
159   // InfoBar and make sure to place it below the Close button.
160   [infoBarView_ addSubview:extensionView_
161                 positioned:NSWindowBelow
162                 relativeTo:(NSView*)closeButton_];
163
164   // Add the context menu button to the hierarchy.
165   [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES];
166   CGFloat buttonY =
167       std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) +
168           kBottomBorderHeightPx;
169   NSRect buttonFrame = NSMakeRect(
170       kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx);
171   [dropdownButton_ setFrame:buttonFrame];
172   [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
173   [infoBarView_ addSubview:dropdownButton_];
174
175   // Because the parent view has a bottom border, account for it during
176   // positioning.
177   NSRect extensionFrame = [extensionView_ frame];
178   extensionFrame.origin.y = kBottomBorderHeightPx;
179
180   [extensionView_ setFrame:extensionFrame];
181   // The extension's native view will only have a height that is non-zero if it
182   // already has been loaded and rendered, which is the case when you switch
183   // back to a tab with an extension infobar within it. The reason this is
184   // needed is because the extension view's frame will not have changed in the
185   // above case, so the NSViewFrameDidChangeNotification registered below will
186   // never fire.
187   if (NSHeight(extensionFrame) > 0.0)
188     [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
189
190   [self adjustExtensionViewSize];
191
192   // These two notification handlers are here to ensure the width of the
193   // native extension view is the same as the browser window's width and that
194   // the parent infobar view matches the height of the extension's native view.
195   [[NSNotificationCenter defaultCenter]
196       addObserver:self
197          selector:@selector(extensionViewFrameChanged)
198              name:NSViewFrameDidChangeNotification
199            object:extensionView_];
200
201   [[NSNotificationCenter defaultCenter]
202       addObserver:self
203          selector:@selector(adjustExtensionViewSize)
204              name:NSViewFrameDidChangeNotification
205            object:[self view]];
206 }
207
208 - (void)infobarWillHide {
209   [[dropdownButton_ menu] cancelTracking];
210   [super infobarWillHide];
211 }
212
213 - (void)infobarWillClose {
214   [self disablePopUpMenu:[dropdownButton_ menu]];
215   [super infobarWillClose];
216 }
217
218 - (void)extensionViewFrameChanged {
219   [self adjustExtensionViewSize];
220   [self infobar]->SetBarTargetHeight([self clampedExtensionViewHeight]);
221 }
222
223 - (CGFloat)clampedExtensionViewHeight {
224   CGFloat height = [self delegate]->AsExtensionInfoBarDelegate()->height();
225   return std::max(kToolbarMinHeightPx, std::min(height, kToolbarMaxHeightPx));
226 }
227
228 - (void)adjustExtensionViewSize {
229   [extensionView_ setPostsFrameChangedNotifications:NO];
230   NSSize extensionViewSize = [extensionView_ frame].size;
231   extensionViewSize.width = NSWidth([[self view] frame]);
232   extensionViewSize.height = [self clampedExtensionViewHeight];
233   [extensionView_ setFrameSize:extensionViewSize];
234   [extensionView_ setPostsFrameChangedNotifications:YES];
235 }
236
237 - (void)setButtonImage:(NSImage*)image {
238   [dropdownButton_ setImage:image];
239 }
240
241 - (void)menuNeedsUpdate:(NSMenu*)menu {
242   DCHECK([self isOwned]);
243
244   if (!contextMenuController_) {
245     extensions::ExtensionViewHost* extensionViewHost =
246         [self delegate]->AsExtensionInfoBarDelegate()->extension_view_host();
247     Browser* browser = chrome::FindBrowserWithWebContents(
248         [self delegate]->AsExtensionInfoBarDelegate()->GetWebContents());
249     contextMenuController_.reset([[ExtensionActionContextMenuController alloc]
250         initWithExtension:extensionViewHost->extension()
251                   browser:browser
252           extensionAction:NULL]);
253   }
254
255   [menu removeAllItems];
256   [contextMenuController_ populateMenu:menu];
257 }
258
259 @end
260
261 // static
262 scoped_ptr<infobars::InfoBar> ExtensionInfoBarDelegate::CreateInfoBar(
263     scoped_ptr<ExtensionInfoBarDelegate> delegate) {
264   scoped_ptr<InfoBarCocoa> infobar(
265       new InfoBarCocoa(delegate.PassAs<infobars::InfoBarDelegate>()));
266   base::scoped_nsobject<ExtensionInfoBarController> controller(
267       [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get()]);
268   infobar->set_controller(controller);
269   return infobar.PassAs<infobars::InfoBar>();
270 }