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