• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2010 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/extensions/extension_infobar_controller.h"
6
7#include <cmath>
8
9#include "chrome/browser/extensions/extension_host.h"
10#include "chrome/browser/extensions/extension_infobar_delegate.h"
11#include "chrome/browser/extensions/image_loading_tracker.h"
12#import "chrome/browser/ui/cocoa/animatable_view.h"
13#import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu.h"
14#import "chrome/browser/ui/cocoa/menu_button.h"
15#include "chrome/browser/ui/cocoa/infobars/infobar.h"
16#include "chrome/common/extensions/extension.h"
17#include "chrome/common/extensions/extension_icon_set.h"
18#include "chrome/common/extensions/extension_resource.h"
19#include "content/browser/tab_contents/tab_contents.h"
20#include "grit/theme_resources.h"
21#include "skia/ext/skia_utils_mac.h"
22#include "ui/base/resource/resource_bundle.h"
23#include "ui/gfx/canvas_skia.h"
24
25namespace {
26const CGFloat kAnimationDuration = 0.12;
27const CGFloat kBottomBorderHeightPx = 1.0;
28const CGFloat kButtonHeightPx = 26.0;
29const CGFloat kButtonLeftMarginPx = 2.0;
30const CGFloat kButtonWidthPx = 34.0;
31const CGFloat kDropArrowLeftMarginPx = 3.0;
32const CGFloat kToolbarMinHeightPx = 36.0;
33const CGFloat kToolbarMaxHeightPx = 72.0;
34}  // namespace
35
36@interface ExtensionInfoBarController(Private)
37// Called when the extension's hosted NSView has been resized.
38- (void)extensionViewFrameChanged;
39// Returns the clamped height of the extension view to be within the min and max
40// values defined above.
41- (CGFloat)clampedExtensionViewHeight;
42// Adjusts the width of the extension's hosted view to match the window's width
43// and sets the proper height for it as well.
44- (void)adjustExtensionViewSize;
45// Sets the image to be used in the button on the left side of the infobar.
46- (void)setButtonImage:(NSImage*)image;
47@end
48
49// A helper class to bridge the asynchronous Skia bitmap loading mechanism to
50// the extension's button.
51class InfobarBridge : public ExtensionInfoBarDelegate::DelegateObserver,
52                      public ImageLoadingTracker::Observer {
53 public:
54  explicit InfobarBridge(ExtensionInfoBarController* owner)
55      : owner_(owner),
56        delegate_([owner delegate]->AsExtensionInfoBarDelegate()),
57        tracker_(this) {
58    delegate_->set_observer(this);
59    LoadIcon();
60  }
61
62  virtual ~InfobarBridge() {
63    if (delegate_)
64      delegate_->set_observer(NULL);
65  }
66
67  // Load the Extension's icon image.
68  void LoadIcon() {
69    const Extension* extension = delegate_->extension_host()->extension();
70    ExtensionResource icon_resource = extension->GetIconResource(
71        Extension::EXTENSION_ICON_BITTY, ExtensionIconSet::MATCH_EXACTLY);
72    if (!icon_resource.relative_path().empty()) {
73      tracker_.LoadImage(extension, icon_resource,
74                         gfx::Size(Extension::EXTENSION_ICON_BITTY,
75                                   Extension::EXTENSION_ICON_BITTY),
76                         ImageLoadingTracker::DONT_CACHE);
77    } else {
78      OnImageLoaded(NULL, icon_resource, 0);
79    }
80  }
81
82  // ImageLoadingTracker::Observer implementation.
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  virtual void OnImageLoaded(
86      SkBitmap* image, const ExtensionResource& resource, int index) {
87    if (!delegate_)
88      return;  // The delegate can go away while the image asynchronously loads.
89
90    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
91
92    // Fall back on the default extension icon on failure.
93    SkBitmap* icon;
94    if (!image || image->empty())
95      icon = rb.GetBitmapNamed(IDR_EXTENSIONS_SECTION);
96    else
97      icon = image;
98
99    SkBitmap* drop_image = rb.GetBitmapNamed(IDR_APP_DROPARROW);
100
101    const int image_size = Extension::EXTENSION_ICON_BITTY;
102    scoped_ptr<gfx::CanvasSkia> canvas(
103        new gfx::CanvasSkia(
104            image_size + kDropArrowLeftMarginPx + drop_image->width(),
105            image_size, false));
106    canvas->DrawBitmapInt(*icon,
107                          0, 0, icon->width(), icon->height(),
108                          0, 0, image_size, image_size,
109                          false);
110    canvas->DrawBitmapInt(*drop_image,
111                          image_size + kDropArrowLeftMarginPx,
112                          image_size / 2);
113    [owner_ setButtonImage:gfx::SkBitmapToNSImage(canvas->ExtractBitmap())];
114  }
115
116  // Overridden from ExtensionInfoBarDelegate::DelegateObserver:
117  virtual void OnDelegateDeleted() {
118    delegate_ = NULL;
119  }
120
121 private:
122  // Weak. Owns us.
123  ExtensionInfoBarController* owner_;
124
125  // Weak.
126  ExtensionInfoBarDelegate* delegate_;
127
128  // Loads the extensions's icon on the file thread.
129  ImageLoadingTracker tracker_;
130
131  DISALLOW_COPY_AND_ASSIGN(InfobarBridge);
132};
133
134
135@implementation ExtensionInfoBarController
136
137- (id)initWithDelegate:(InfoBarDelegate*)delegate
138                window:(NSWindow*)window {
139  if ((self = [super initWithDelegate:delegate])) {
140    window_ = window;
141    dropdownButton_.reset([[MenuButton alloc] init]);
142    [dropdownButton_ setOpenMenuOnClick:YES];
143
144    ExtensionHost* extensionHost = delegate_->AsExtensionInfoBarDelegate()->
145        extension_host();
146    contextMenu_.reset([[ExtensionActionContextMenu alloc]
147        initWithExtension:extensionHost->extension()
148                  profile:extensionHost->profile()
149          extensionAction:NULL]);
150    // See menu_button.h for documentation on why this is needed.
151    NSMenuItem* dummyItem =
152        [[[NSMenuItem alloc] initWithTitle:@""
153                                    action:nil
154                             keyEquivalent:@""] autorelease];
155    [contextMenu_ insertItem:dummyItem atIndex:0];
156    [dropdownButton_ setAttachedMenu:contextMenu_.get()];
157
158    bridge_.reset(new InfobarBridge(self));
159  }
160  return self;
161}
162
163- (void)dealloc {
164  [[NSNotificationCenter defaultCenter] removeObserver:self];
165  [super dealloc];
166}
167
168- (void)addAdditionalControls {
169  [self removeButtons];
170
171  extensionView_ = delegate_->AsExtensionInfoBarDelegate()->extension_host()->
172      view()->native_view();
173
174  // Add the extension's RenderWidgetHostViewMac to the view hierarchy of the
175  // InfoBar and make sure to place it below the Close button.
176  [infoBarView_ addSubview:extensionView_
177                positioned:NSWindowBelow
178                relativeTo:(NSView*)closeButton_];
179
180  // Add the context menu button to the hierarchy.
181  [dropdownButton_ setShowsBorderOnlyWhileMouseInside:YES];
182  CGFloat buttonY =
183      std::floor(NSMidY([infoBarView_ frame]) - (kButtonHeightPx / 2.0)) +
184          kBottomBorderHeightPx;
185  NSRect buttonFrame = NSMakeRect(
186      kButtonLeftMarginPx, buttonY, kButtonWidthPx, kButtonHeightPx);
187  [dropdownButton_ setFrame:buttonFrame];
188  [dropdownButton_ setAutoresizingMask:NSViewMinYMargin | NSViewMaxYMargin];
189  [infoBarView_ addSubview:dropdownButton_];
190
191  // Because the parent view has a bottom border, account for it during
192  // positioning.
193  NSRect extensionFrame = [extensionView_ frame];
194  extensionFrame.origin.y = kBottomBorderHeightPx;
195
196  [extensionView_ setFrame:extensionFrame];
197  // The extension's native view will only have a height that is non-zero if it
198  // already has been loaded and rendered, which is the case when you switch
199  // back to a tab with an extension infobar within it. The reason this is
200  // needed is because the extension view's frame will not have changed in the
201  // above case, so the NSViewFrameDidChangeNotification registered below will
202  // never fire.
203  if (NSHeight(extensionFrame) > 0.0) {
204    NSSize infoBarSize = [[self view] frame].size;
205    infoBarSize.height = [self clampedExtensionViewHeight] +
206        kBottomBorderHeightPx;
207    [[self view] setFrameSize:infoBarSize];
208    [infoBarView_ setFrameSize:infoBarSize];
209  }
210
211  [self adjustExtensionViewSize];
212
213  // These two notification handlers are here to ensure the width of the
214  // native extension view is the same as the browser window's width and that
215  // the parent infobar view matches the height of the extension's native view.
216  [[NSNotificationCenter defaultCenter]
217      addObserver:self
218         selector:@selector(extensionViewFrameChanged)
219             name:NSViewFrameDidChangeNotification
220           object:extensionView_];
221
222  [[NSNotificationCenter defaultCenter]
223      addObserver:self
224         selector:@selector(adjustWidthToFitWindow)
225             name:NSWindowDidResizeNotification
226           object:window_];
227}
228
229- (void)extensionViewFrameChanged {
230  [self adjustExtensionViewSize];
231
232  AnimatableView* view = [self animatableView];
233  NSRect infoBarFrame = [view frame];
234  CGFloat newHeight = [self clampedExtensionViewHeight] + kBottomBorderHeightPx;
235  [infoBarView_ setPostsFrameChangedNotifications:NO];
236  infoBarFrame.size.height = newHeight;
237  [infoBarView_ setFrame:infoBarFrame];
238  [infoBarView_ setPostsFrameChangedNotifications:YES];
239  [view animateToNewHeight:newHeight duration:kAnimationDuration];
240}
241
242- (CGFloat)clampedExtensionViewHeight {
243  return std::max(kToolbarMinHeightPx,
244      std::min(NSHeight([extensionView_ frame]), kToolbarMaxHeightPx));
245}
246
247- (void)adjustExtensionViewSize {
248  [extensionView_ setPostsFrameChangedNotifications:NO];
249  NSSize extensionViewSize = [extensionView_ frame].size;
250  extensionViewSize.width = NSWidth([window_ frame]);
251  extensionViewSize.height = [self clampedExtensionViewHeight];
252  [extensionView_ setFrameSize:extensionViewSize];
253  [extensionView_ setPostsFrameChangedNotifications:YES];
254}
255
256- (void)setButtonImage:(NSImage*)image {
257  [dropdownButton_ setImage:image];
258}
259
260@end
261
262InfoBar* ExtensionInfoBarDelegate::CreateInfoBar() {
263  NSWindow* window = [(NSView*)tab_contents_->GetContentNativeView() window];
264  ExtensionInfoBarController* controller =
265      [[ExtensionInfoBarController alloc] initWithDelegate:this
266                                                    window:window];
267  return new InfoBar(controller);
268}
269