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 #include "chrome/browser/ui/views/infobars/extension_infobar.h" 6 7 #include "chrome/browser/extensions/extension_context_menu_model.h" 8 #include "chrome/browser/extensions/extension_infobar_delegate.h" 9 #include "chrome/browser/extensions/extension_view_host.h" 10 #include "chrome/browser/platform_util.h" 11 #include "chrome/browser/ui/views/frame/browser_view.h" 12 #include "extensions/browser/image_loader.h" 13 #include "extensions/common/constants.h" 14 #include "extensions/common/extension.h" 15 #include "extensions/common/extension_icon_set.h" 16 #include "extensions/common/extension_resource.h" 17 #include "extensions/common/manifest_handlers/icons_handler.h" 18 #include "grit/theme_resources.h" 19 #include "ui/base/resource/resource_bundle.h" 20 #include "ui/gfx/animation/slide_animation.h" 21 #include "ui/gfx/canvas.h" 22 #include "ui/gfx/image/canvas_image_source.h" 23 #include "ui/gfx/image/image.h" 24 #include "ui/views/controls/button/menu_button.h" 25 #include "ui/views/controls/image_view.h" 26 #include "ui/views/widget/widget.h" 27 28 29 // ExtensionInfoBarDelegate ---------------------------------------------------- 30 31 // static 32 scoped_ptr<infobars::InfoBar> ExtensionInfoBarDelegate::CreateInfoBar( 33 scoped_ptr<ExtensionInfoBarDelegate> delegate) { 34 Browser* browser = delegate->browser_; 35 return scoped_ptr<infobars::InfoBar>( 36 new ExtensionInfoBar(delegate.Pass(), browser)); 37 } 38 39 40 // ExtensionInfoBar ------------------------------------------------------------ 41 42 namespace { 43 // The horizontal margin between the infobar icon and the Extension (HTML) view. 44 const int kIconHorizontalMargin = 1; 45 46 class MenuImageSource: public gfx::CanvasImageSource { 47 public: 48 MenuImageSource(const gfx::ImageSkia& icon, const gfx::ImageSkia& drop_image) 49 : gfx::CanvasImageSource(ComputeSize(drop_image), false), 50 icon_(icon), 51 drop_image_(drop_image) { 52 } 53 54 virtual ~MenuImageSource() { 55 } 56 57 // Overridden from gfx::CanvasImageSource 58 virtual void Draw(gfx::Canvas* canvas) OVERRIDE { 59 int image_size = extension_misc::EXTENSION_ICON_BITTY; 60 canvas->DrawImageInt(icon_, 0, 0, icon_.width(), icon_.height(), 0, 0, 61 image_size, image_size, false); 62 canvas->DrawImageInt(drop_image_, image_size + kDropArrowLeftMargin, 63 image_size / 2); 64 } 65 66 private: 67 gfx::Size ComputeSize(const gfx::ImageSkia& drop_image) const { 68 int image_size = extension_misc::EXTENSION_ICON_BITTY; 69 return gfx::Size(image_size + kDropArrowLeftMargin + drop_image.width(), 70 image_size); 71 } 72 73 // The margin between the extension icon and the drop-down arrow image. 74 static const int kDropArrowLeftMargin = 3; 75 76 const gfx::ImageSkia icon_; 77 const gfx::ImageSkia drop_image_; 78 79 DISALLOW_COPY_AND_ASSIGN(MenuImageSource); 80 }; 81 82 } // namespace 83 84 ExtensionInfoBar::ExtensionInfoBar( 85 scoped_ptr<ExtensionInfoBarDelegate> delegate, 86 Browser* browser) 87 : InfoBarView(delegate.PassAs<infobars::InfoBarDelegate>()), 88 browser_(browser), 89 infobar_icon_(NULL), 90 icon_as_menu_(NULL), 91 icon_as_image_(NULL), 92 weak_ptr_factory_(this) { 93 } 94 95 ExtensionInfoBar::~ExtensionInfoBar() { 96 } 97 98 void ExtensionInfoBar::Layout() { 99 InfoBarView::Layout(); 100 101 infobar_icon_->SetPosition(gfx::Point(StartX(), OffsetY(infobar_icon_))); 102 ExtensionViewViews* extension_view = 103 GetDelegate()->extension_view_host()->view(); 104 // TODO(pkasting): We'd like to simply set the extension view's desired height 105 // at creation time and position using OffsetY() like for other infobar items, 106 // but the NativeViewHost inside does not seem to be clipped by the ClipRect() 107 // call in InfoBarView::PaintChildren(), so we have to manually clamp the size 108 // here. 109 extension_view->SetSize( 110 gfx::Size(std::max(0, EndX() - StartX() - NonExtensionViewWidth()), 111 std::min(height() - kSeparatorLineHeight - arrow_height(), 112 GetDelegate()->height()))); 113 // We do SetPosition() separately after SetSize() so OffsetY() will work. 114 extension_view->SetPosition( 115 gfx::Point(infobar_icon_->bounds().right() + kIconHorizontalMargin, 116 std::max(arrow_height(), OffsetY(extension_view)))); 117 } 118 119 void ExtensionInfoBar::ViewHierarchyChanged( 120 const ViewHierarchyChangedDetails& details) { 121 if (!details.is_add || (details.child != this) || (infobar_icon_ != NULL)) { 122 InfoBarView::ViewHierarchyChanged(details); 123 return; 124 } 125 126 extensions::ExtensionViewHost* extension_view_host = 127 GetDelegate()->extension_view_host(); 128 129 if (extension_view_host->extension()->ShowConfigureContextMenus()) { 130 icon_as_menu_ = new views::MenuButton(NULL, base::string16(), this, false); 131 icon_as_menu_->SetFocusable(true); 132 infobar_icon_ = icon_as_menu_; 133 } else { 134 icon_as_image_ = new views::ImageView(); 135 infobar_icon_ = icon_as_image_; 136 } 137 138 // Wait until the icon image is loaded before showing it. 139 infobar_icon_->SetVisible(false); 140 AddChildView(infobar_icon_); 141 142 // Set the desired height of the ExtensionViewViews, so that when the 143 // AddChildView() call triggers InfoBarView::ViewHierarchyChanged(), it can 144 // read the correct height off this object in order to calculate the overall 145 // desired infobar height. 146 extension_view_host->view()->SetSize(gfx::Size(0, GetDelegate()->height())); 147 AddChildView(extension_view_host->view()); 148 149 // This must happen after adding all other children so InfoBarView can ensure 150 // the close button is the last child. 151 InfoBarView::ViewHierarchyChanged(details); 152 153 // This must happen after adding all children because it can trigger layout, 154 // which assumes that particular children (e.g. the close button) have already 155 // been added. 156 const extensions::Extension* extension = extension_view_host->extension(); 157 extension_misc::ExtensionIcons image_size = 158 extension_misc::EXTENSION_ICON_BITTY; 159 extensions::ExtensionResource icon_resource = 160 extensions::IconsInfo::GetIconResource( 161 extension, image_size, ExtensionIconSet::MATCH_EXACTLY); 162 extensions::ImageLoader* loader = 163 extensions::ImageLoader::Get(extension_view_host->browser_context()); 164 loader->LoadImageAsync( 165 extension, 166 icon_resource, 167 gfx::Size(image_size, image_size), 168 base::Bind(&ExtensionInfoBar::OnImageLoaded, 169 weak_ptr_factory_.GetWeakPtr())); 170 } 171 172 int ExtensionInfoBar::ContentMinimumWidth() const { 173 return NonExtensionViewWidth() + 174 delegate()->AsExtensionInfoBarDelegate()->extension_view_host()-> 175 view()->GetMinimumSize().width(); 176 } 177 178 void ExtensionInfoBar::OnMenuButtonClicked(views::View* source, 179 const gfx::Point& point) { 180 if (!owner()) 181 return; // We're closing; don't call anything, it might access the owner. 182 const extensions::Extension* extension = 183 GetDelegate()->extension_view_host()->extension(); 184 DCHECK(icon_as_menu_); 185 186 scoped_refptr<ExtensionContextMenuModel> options_menu_contents = 187 new ExtensionContextMenuModel(extension, browser_); 188 DCHECK_EQ(icon_as_menu_, source); 189 RunMenuAt( 190 options_menu_contents.get(), icon_as_menu_, views::MENU_ANCHOR_TOPLEFT); 191 } 192 193 void ExtensionInfoBar::OnImageLoaded(const gfx::Image& image) { 194 if (!GetDelegate()) 195 return; // The delegate can go away while we asynchronously load images. 196 197 const gfx::ImageSkia* icon = NULL; 198 // Fall back on the default extension icon on failure. 199 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 200 if (image.IsEmpty()) 201 icon = rb.GetImageNamed(IDR_EXTENSIONS_SECTION).ToImageSkia(); 202 else 203 icon = image.ToImageSkia(); 204 205 if (icon_as_menu_) { 206 const gfx::ImageSkia* drop_image = 207 rb.GetImageNamed(IDR_APP_DROPARROW).ToImageSkia(); 208 209 gfx::CanvasImageSource* source = new MenuImageSource(*icon, *drop_image); 210 gfx::ImageSkia menu_image = gfx::ImageSkia(source, source->size()); 211 icon_as_menu_->SetImage(views::Button::STATE_NORMAL, menu_image); 212 } else { 213 icon_as_image_->SetImage(*icon); 214 } 215 216 infobar_icon_->SizeToPreferredSize(); 217 infobar_icon_->SetVisible(true); 218 219 Layout(); 220 } 221 222 ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() { 223 return delegate()->AsExtensionInfoBarDelegate(); 224 } 225 226 const ExtensionInfoBarDelegate* ExtensionInfoBar::GetDelegate() const { 227 return delegate()->AsExtensionInfoBarDelegate(); 228 } 229 230 int ExtensionInfoBar::NonExtensionViewWidth() const { 231 return infobar_icon_->width() + kIconHorizontalMargin; 232 } 233