| OLD | NEW |
| (Empty) |
| 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 "chrome/browser/extensions/extension_infobar_delegate.h" | |
| 8 #include "chrome/browser/extensions/extension_context_menu_model.h" | |
| 9 #include "chrome/browser/extensions/extension_view.h" | |
| 10 #include "chrome/browser/extensions/extension_view_host.h" | |
| 11 #include "chrome/browser/infobars/infobar_service.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | |
| 13 #include "chrome/browser/ui/browser_finder.h" | |
| 14 #import "chrome/browser/ui/cocoa/animatable_view.h" | |
| 15 #include "chrome/browser/ui/cocoa/infobars/infobar_cocoa.h" | |
| 16 #import "chrome/browser/ui/cocoa/menu_button.h" | |
| 17 #include "content/public/browser/web_contents.h" | |
| 18 #include "extensions/browser/image_loader.h" | |
| 19 #include "extensions/common/constants.h" | |
| 20 #include "extensions/common/extension.h" | |
| 21 #include "extensions/common/extension_icon_set.h" | |
| 22 #include "extensions/common/extension_resource.h" | |
| 23 #include "extensions/common/manifest_handlers/icons_handler.h" | |
| 24 #include "grit/theme_resources.h" | |
| 25 #include "skia/ext/skia_utils_mac.h" | |
| 26 #include "ui/base/resource/resource_bundle.h" | |
| 27 #include "ui/gfx/canvas.h" | |
| 28 #import "ui/base/cocoa/menu_controller.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()->GetNativeView(); | |
| 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 contextMenuModel_ = make_scoped_refptr(new ExtensionContextMenuModel( | |
| 250 extensionViewHost->extension(), browser)); | |
| 251 contextMenuController_.reset( | |
| 252 [[MenuController alloc] initWithModel:contextMenuModel_.get() | |
| 253 useWithPopUpButtonCell:NO]); | |
| 254 } | |
| 255 | |
| 256 [menu removeAllItems]; | |
| 257 [contextMenuController_ menuNeedsUpdate: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(new InfoBarCocoa(delegate.Pass())); | |
| 266 base::scoped_nsobject<ExtensionInfoBarController> controller( | |
| 267 [[ExtensionInfoBarController alloc] initWithInfoBar:infobar.get()]); | |
| 268 infobar->set_controller(controller); | |
| 269 return infobar.Pass(); | |
| 270 } | |
| OLD | NEW |