page_action_decoration.mm revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 <cmath> 6 7#import "chrome/browser/ui/cocoa/location_bar/page_action_decoration.h" 8 9#include "base/strings/sys_string_conversions.h" 10#include "chrome/browser/chrome_notification_types.h" 11#include "chrome/browser/extensions/extension_action.h" 12#include "chrome/browser/extensions/extension_tab_util.h" 13#include "chrome/browser/extensions/location_bar_controller.h" 14#include "chrome/browser/extensions/tab_helper.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/sessions/session_id.h" 17#include "chrome/browser/ui/browser.h" 18#include "chrome/browser/ui/browser_window.h" 19#import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu_controller.h" 20#import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h" 21#include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h" 22#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" 23#include "chrome/browser/ui/webui/extensions/extension_info_ui.h" 24#include "content/public/browser/notification_service.h" 25#include "content/public/browser/web_contents.h" 26#include "extensions/browser/extension_registry.h" 27#include "extensions/common/manifest_handlers/icons_handler.h" 28#include "skia/ext/skia_utils_mac.h" 29#include "ui/gfx/canvas_skia_paint.h" 30#include "ui/gfx/image/image.h" 31 32using content::WebContents; 33using extensions::Extension; 34using extensions::LocationBarController; 35 36namespace { 37 38// Distance to offset the bubble pointer from the bottom of the max 39// icon area of the decoration. This makes the popup's upper border 40// 2px away from the omnibox's lower border (matches omnibox popup 41// upper border). 42const CGFloat kBubblePointYOffset = 2.0; 43 44} // namespace 45 46PageActionDecoration::PageActionDecoration( 47 LocationBarViewMac* owner, 48 Browser* browser, 49 ExtensionAction* page_action) 50 : owner_(NULL), 51 browser_(browser), 52 page_action_(page_action), 53 current_tab_id_(-1), 54 preview_enabled_(false) { 55 const Extension* extension = extensions::ExtensionRegistry::Get( 56 browser->profile())->enabled_extensions().GetByID( 57 page_action->extension_id()); 58 DCHECK(extension); 59 60 icon_factory_.reset(new ExtensionActionIconFactory( 61 browser_->profile(), extension, page_action, this)); 62 63 registrar_.Add(this, 64 extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 65 content::Source<Profile>(browser_->profile())); 66 registrar_.Add(this, 67 extensions::NOTIFICATION_EXTENSION_COMMAND_PAGE_ACTION_MAC, 68 content::Source<Profile>(browser_->profile())); 69 70 // We set the owner last of all so that we can determine whether we are in 71 // the process of initializing this class or not. 72 owner_ = owner; 73} 74 75PageActionDecoration::~PageActionDecoration() {} 76 77// Always |kPageActionIconMaxSize| wide. |ImageDecoration| draws the 78// image centered. 79CGFloat PageActionDecoration::GetWidthForSpace(CGFloat width) { 80 return ExtensionAction::kPageActionIconMaxSize; 81} 82 83bool PageActionDecoration::AcceptsMousePress() { 84 return true; 85} 86 87// Either notify listeners or show a popup depending on the Page 88// Action. 89bool PageActionDecoration::OnMousePressed(NSRect frame, NSPoint location) { 90 return ActivatePageAction(frame); 91} 92 93void PageActionDecoration::ActivatePageAction() { 94 ActivatePageAction(owner_->GetPageActionFrame(page_action_)); 95} 96 97bool PageActionDecoration::ActivatePageAction(NSRect frame) { 98 WebContents* web_contents = owner_->GetWebContents(); 99 if (!web_contents) { 100 // We don't want other code to try and handle this click. Returning true 101 // prevents this by indicating that we handled it. 102 return true; 103 } 104 105 LocationBarController* controller = 106 extensions::TabHelper::FromWebContents(web_contents)-> 107 location_bar_controller(); 108 109 switch (controller->OnClicked(page_action_)) { 110 case LocationBarController::ACTION_NONE: 111 break; 112 113 case LocationBarController::ACTION_SHOW_POPUP: 114 ShowPopup(frame, page_action_->GetPopupUrl(current_tab_id_)); 115 break; 116 117 case LocationBarController::ACTION_SHOW_CONTEXT_MENU: 118 // We are never passing OnClicked a right-click button, so assume that 119 // we're never going to be asked to show a context menu. 120 // TODO(kalman): if this changes, update this class to pass the real 121 // mouse button through to the LocationBarController. 122 NOTREACHED(); 123 break; 124 } 125 126 return true; 127} 128 129void PageActionDecoration::OnIconUpdated() { 130 // If we have no owner, that means this class is still being constructed. 131 WebContents* web_contents = owner_ ? owner_->GetWebContents() : NULL; 132 if (web_contents) { 133 UpdateVisibility(web_contents, current_url_); 134 owner_->RedrawDecoration(this); 135 } 136} 137 138void PageActionDecoration::UpdateVisibility(WebContents* 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_ = 143 contents ? extensions::ExtensionTabUtil::GetTabId(contents) : -1; 144 current_url_ = url; 145 146 bool visible = contents && 147 (preview_enabled_ || page_action_->GetIsVisible(current_tab_id_)); 148 if (visible) { 149 SetToolTip(page_action_->GetTitle(current_tab_id_)); 150 151 // Set the image. 152 gfx::Image icon = icon_factory_->GetIcon(current_tab_id_); 153 if (!icon.IsEmpty()) { 154 SetImage(icon.ToNSImage()); 155 } else if (!GetImage()) { 156 const NSSize default_size = NSMakeSize( 157 ExtensionAction::kPageActionIconMaxSize, 158 ExtensionAction::kPageActionIconMaxSize); 159 SetImage([[[NSImage alloc] initWithSize:default_size] autorelease]); 160 } 161 } 162 163 if (IsVisible() != visible) { 164 SetVisible(visible); 165 content::NotificationService::current()->Notify( 166 extensions::NOTIFICATION_EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED, 167 content::Source<ExtensionAction>(page_action_), 168 content::Details<WebContents>(contents)); 169 } 170} 171 172void PageActionDecoration::SetToolTip(NSString* tooltip) { 173 tooltip_.reset([tooltip retain]); 174} 175 176void PageActionDecoration::SetToolTip(std::string tooltip) { 177 SetToolTip(tooltip.empty() ? nil : base::SysUTF8ToNSString(tooltip)); 178} 179 180NSString* PageActionDecoration::GetToolTip() { 181 return tooltip_.get(); 182} 183 184NSPoint PageActionDecoration::GetBubblePointInFrame(NSRect frame) { 185 // This is similar to |ImageDecoration::GetDrawRectInFrame()|, 186 // except that code centers the image, which can differ in size 187 // between actions. This centers the maximum image size, so the 188 // point will consistently be at the same y position. x position is 189 // easier (the middle of the centered image is the middle of the 190 // frame). 191 const CGFloat delta_height = 192 NSHeight(frame) - ExtensionAction::kPageActionIconMaxSize; 193 const CGFloat bottom_inset = std::ceil(delta_height / 2.0); 194 195 // Return a point just below the bottom of the maximal drawing area. 196 return NSMakePoint(NSMidX(frame), 197 NSMaxY(frame) - bottom_inset + kBubblePointYOffset); 198} 199 200NSMenu* PageActionDecoration::GetMenu() { 201 const Extension* extension = extensions::ExtensionRegistry::Get( 202 browser_->profile())->enabled_extensions().GetByID( 203 page_action_->extension_id()); 204 DCHECK(extension); 205 if (!extension->ShowConfigureContextMenus()) 206 return nil; 207 208 contextMenuController_.reset([[ExtensionActionContextMenuController alloc] 209 initWithExtension:extension 210 browser:browser_ 211 extensionAction:page_action_]); 212 213 base::scoped_nsobject<NSMenu> contextMenu([[NSMenu alloc] initWithTitle:@""]); 214 [contextMenuController_ populateMenu:contextMenu]; 215 return contextMenu.autorelease(); 216} 217 218void PageActionDecoration::ShowPopup(const NSRect& frame, 219 const GURL& popup_url) { 220 // Anchor popup at the bottom center of the page action icon. 221 AutocompleteTextField* field = owner_->GetAutocompleteTextField(); 222 NSPoint anchor = GetBubblePointInFrame(frame); 223 anchor = [field convertPoint:anchor toView:nil]; 224 225 [ExtensionPopupController showURL:popup_url 226 inBrowser:chrome::GetLastActiveBrowser() 227 anchoredAt:anchor 228 arrowLocation:info_bubble::kTopRight 229 devMode:NO]; 230} 231 232void PageActionDecoration::Observe( 233 int type, 234 const content::NotificationSource& source, 235 const content::NotificationDetails& details) { 236 switch (type) { 237 case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: { 238 ExtensionPopupController* popup = [ExtensionPopupController popup]; 239 if (popup && ![popup isClosing]) 240 [popup close]; 241 242 break; 243 } 244 case extensions::NOTIFICATION_EXTENSION_COMMAND_PAGE_ACTION_MAC: { 245 std::pair<const std::string, gfx::NativeWindow>* payload = 246 content::Details<std::pair<const std::string, gfx::NativeWindow> >( 247 details).ptr(); 248 std::string extension_id = payload->first; 249 gfx::NativeWindow window = payload->second; 250 if (window != browser_->window()->GetNativeWindow()) 251 break; 252 if (extension_id != page_action_->extension_id()) 253 break; 254 if (IsVisible()) 255 ActivatePageAction(); 256 break; 257 } 258 259 default: 260 NOTREACHED() << "Unexpected notification"; 261 break; 262 } 263} 264