Home | History | Annotate | Download | only in location_bar
      1 // Copyright (c) 2011 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 <cmath>
      6 
      7 #import "chrome/browser/ui/cocoa/location_bar/page_action_decoration.h"
      8 
      9 #include "base/sys_string_conversions.h"
     10 #include "chrome/browser/extensions/extension_browser_event_router.h"
     11 #include "chrome/browser/extensions/extension_service.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu.h"
     14 #import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h"
     15 #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h"
     16 #include "chrome/common/extensions/extension_action.h"
     17 #include "chrome/common/extensions/extension_resource.h"
     18 #include "content/browser/tab_contents/tab_contents.h"
     19 #include "content/common/notification_service.h"
     20 #include "skia/ext/skia_utils_mac.h"
     21 
     22 namespace {
     23 
     24 // Distance to offset the bubble pointer from the bottom of the max
     25 // icon area of the decoration.  This makes the popup's upper border
     26 // 2px away from the omnibox's lower border (matches omnibox popup
     27 // upper border).
     28 const CGFloat kBubblePointYOffset = 2.0;
     29 
     30 }  // namespace
     31 
     32 PageActionDecoration::PageActionDecoration(
     33     LocationBarViewMac* owner,
     34     Profile* profile,
     35     ExtensionAction* page_action)
     36     : owner_(NULL),
     37       profile_(profile),
     38       page_action_(page_action),
     39       tracker_(this),
     40       current_tab_id_(-1),
     41       preview_enabled_(false) {
     42   DCHECK(profile);
     43   const Extension* extension = profile->GetExtensionService()->
     44       GetExtensionById(page_action->extension_id(), false);
     45   DCHECK(extension);
     46 
     47   // Load all the icons declared in the manifest. This is the contents of the
     48   // icons array, plus the default_icon property, if any.
     49   std::vector<std::string> icon_paths(*page_action->icon_paths());
     50   if (!page_action_->default_icon_path().empty())
     51     icon_paths.push_back(page_action_->default_icon_path());
     52 
     53   for (std::vector<std::string>::iterator iter = icon_paths.begin();
     54        iter != icon_paths.end(); ++iter) {
     55     tracker_.LoadImage(extension, extension->GetResource(*iter),
     56                        gfx::Size(Extension::kPageActionIconMaxSize,
     57                                  Extension::kPageActionIconMaxSize),
     58                        ImageLoadingTracker::DONT_CACHE);
     59   }
     60 
     61   registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
     62       Source<Profile>(profile_));
     63 
     64   // We set the owner last of all so that we can determine whether we are in
     65   // the process of initializing this class or not.
     66   owner_ = owner;
     67 }
     68 
     69 PageActionDecoration::~PageActionDecoration() {}
     70 
     71 // Always |kPageActionIconMaxSize| wide.  |ImageDecoration| draws the
     72 // image centered.
     73 CGFloat PageActionDecoration::GetWidthForSpace(CGFloat width) {
     74   return Extension::kPageActionIconMaxSize;
     75 }
     76 
     77 bool PageActionDecoration::AcceptsMousePress() {
     78   return true;
     79 }
     80 
     81 // Either notify listeners or show a popup depending on the Page
     82 // Action.
     83 bool PageActionDecoration::OnMousePressed(NSRect frame) {
     84   if (current_tab_id_ < 0) {
     85     NOTREACHED() << "No current tab.";
     86     // We don't want other code to try and handle this click.  Returning true
     87     // prevents this by indicating that we handled it.
     88     return true;
     89   }
     90 
     91   if (page_action_->HasPopup(current_tab_id_)) {
     92     // Anchor popup at the bottom center of the page action icon.
     93     AutocompleteTextField* field = owner_->GetAutocompleteTextField();
     94     NSPoint anchor = GetBubblePointInFrame(frame);
     95     anchor = [field convertPoint:anchor toView:nil];
     96 
     97     const GURL popup_url(page_action_->GetPopupUrl(current_tab_id_));
     98     [ExtensionPopupController showURL:popup_url
     99                             inBrowser:BrowserList::GetLastActive()
    100                            anchoredAt:anchor
    101                         arrowLocation:info_bubble::kTopRight
    102                               devMode:NO];
    103   } else {
    104     ExtensionService* service = profile_->GetExtensionService();
    105     service->browser_event_router()->PageActionExecuted(
    106         profile_, page_action_->extension_id(), page_action_->id(),
    107         current_tab_id_, current_url_.spec(),
    108         1);
    109   }
    110   return true;
    111 }
    112 
    113 void PageActionDecoration::OnImageLoaded(
    114     SkBitmap* image, const ExtensionResource& resource, int index) {
    115   // We loaded icons()->size() icons, plus one extra if the Page Action had
    116   // a default icon.
    117   int total_icons = static_cast<int>(page_action_->icon_paths()->size());
    118   if (!page_action_->default_icon_path().empty())
    119     total_icons++;
    120   DCHECK(index < total_icons);
    121 
    122   // Map the index of the loaded image back to its name. If we ever get an
    123   // index greater than the number of icons, it must be the default icon.
    124   if (image) {
    125     if (index < static_cast<int>(page_action_->icon_paths()->size()))
    126       page_action_icons_[page_action_->icon_paths()->at(index)] = *image;
    127     else
    128       page_action_icons_[page_action_->default_icon_path()] = *image;
    129   }
    130 
    131   // If we have no owner, that means this class is still being constructed and
    132   // we should not UpdatePageActions, since it leads to the PageActions being
    133   // destroyed again and new ones recreated (causing an infinite loop).
    134   if (owner_)
    135     owner_->UpdatePageActions();
    136 }
    137 
    138 void PageActionDecoration::UpdateVisibility(TabContents* contents,
    139                                             const GURL& url) {
    140   // Save this off so we can pass it back to the extension when the action gets
    141   // executed. See PageActionDecoration::OnMousePressed.
    142   current_tab_id_ = contents ? ExtensionTabUtil::GetTabId(contents) : -1;
    143   current_url_ = url;
    144 
    145   bool visible = contents &&
    146       (preview_enabled_ || page_action_->GetIsVisible(current_tab_id_));
    147   if (visible) {
    148     SetToolTip(page_action_->GetTitle(current_tab_id_));
    149 
    150     // Set the image.
    151     // It can come from three places. In descending order of priority:
    152     // - The developer can set it dynamically by path or bitmap. It will be in
    153     //   page_action_->GetIcon().
    154     // - The developer can set it dynamically by index. It will be in
    155     //   page_action_->GetIconIndex().
    156     // - It can be set in the manifest by path. It will be in page_action_->
    157     //   default_icon_path().
    158 
    159     // First look for a dynamically set bitmap.
    160     SkBitmap skia_icon = page_action_->GetIcon(current_tab_id_);
    161     if (skia_icon.isNull()) {
    162       int icon_index = page_action_->GetIconIndex(current_tab_id_);
    163       std::string icon_path = (icon_index < 0) ?
    164           page_action_->default_icon_path() :
    165           page_action_->icon_paths()->at(icon_index);
    166       if (!icon_path.empty()) {
    167         PageActionMap::iterator iter = page_action_icons_.find(icon_path);
    168         if (iter != page_action_icons_.end())
    169           skia_icon = iter->second;
    170       }
    171     }
    172     if (!skia_icon.isNull()) {
    173       SetImage(gfx::SkBitmapToNSImage(skia_icon));
    174     } else if (!GetImage()) {
    175       // During install the action can be displayed before the icons
    176       // have come in.  Rather than deal with this in multiple places,
    177       // provide a placeholder image.  This will be replaced when an
    178       // icon comes in.
    179       const NSSize default_size = NSMakeSize(Extension::kPageActionIconMaxSize,
    180                                              Extension::kPageActionIconMaxSize);
    181       SetImage([[NSImage alloc] initWithSize:default_size]);
    182     }
    183   }
    184 
    185   if (IsVisible() != visible) {
    186     SetVisible(visible);
    187     NotificationService::current()->Notify(
    188         NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED,
    189         Source<ExtensionAction>(page_action_),
    190         Details<TabContents>(contents));
    191   }
    192 }
    193 
    194 void PageActionDecoration::SetToolTip(NSString* tooltip) {
    195   tooltip_.reset([tooltip retain]);
    196 }
    197 
    198 void PageActionDecoration::SetToolTip(std::string tooltip) {
    199   SetToolTip(tooltip.empty() ? nil : base::SysUTF8ToNSString(tooltip));
    200 }
    201 
    202 NSString* PageActionDecoration::GetToolTip() {
    203   return tooltip_.get();
    204 }
    205 
    206 NSPoint PageActionDecoration::GetBubblePointInFrame(NSRect frame) {
    207   // This is similar to |ImageDecoration::GetDrawRectInFrame()|,
    208   // except that code centers the image, which can differ in size
    209   // between actions.  This centers the maximum image size, so the
    210   // point will consistently be at the same y position.  x position is
    211   // easier (the middle of the centered image is the middle of the
    212   // frame).
    213   const CGFloat delta_height =
    214       NSHeight(frame) - Extension::kPageActionIconMaxSize;
    215   const CGFloat bottom_inset = std::ceil(delta_height / 2.0);
    216 
    217   // Return a point just below the bottom of the maximal drawing area.
    218   return NSMakePoint(NSMidX(frame),
    219                      NSMaxY(frame) - bottom_inset + kBubblePointYOffset);
    220 }
    221 
    222 NSMenu* PageActionDecoration::GetMenu() {
    223   if (!profile_)
    224     return nil;
    225   ExtensionService* service = profile_->GetExtensionService();
    226   if (!service)
    227     return nil;
    228   const Extension* extension = service->GetExtensionById(
    229       page_action_->extension_id(), false);
    230   DCHECK(extension);
    231   if (!extension)
    232     return nil;
    233   menu_.reset([[ExtensionActionContextMenu alloc]
    234       initWithExtension:extension
    235                 profile:profile_
    236         extensionAction:page_action_]);
    237 
    238   return menu_.get();
    239 }
    240 
    241 void PageActionDecoration::Observe(
    242     NotificationType type,
    243     const NotificationSource& source,
    244     const NotificationDetails& details) {
    245   switch (type.value) {
    246     case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: {
    247       ExtensionPopupController* popup = [ExtensionPopupController popup];
    248       if (popup && ![popup isClosing])
    249         [popup close];
    250 
    251       break;
    252     }
    253     default:
    254       NOTREACHED() << "Unexpected notification";
    255       break;
    256   }
    257 }
    258