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_service.h" 13#include "chrome/browser/extensions/extension_tab_util.h" 14#include "chrome/browser/extensions/location_bar_controller.h" 15#include "chrome/browser/extensions/tab_helper.h" 16#include "chrome/browser/profiles/profile.h" 17#include "chrome/browser/sessions/session_id.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_window.h" 20#import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu_controller.h" 21#import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h" 22#include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h" 23#import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" 24#include "chrome/browser/ui/omnibox/location_bar_util.h" 25#include "chrome/browser/ui/webui/extensions/extension_info_ui.h" 26#include "content/public/browser/notification_service.h" 27#include "content/public/browser/web_contents.h" 28#include "extensions/common/manifest_handlers/icons_handler.h" 29#include "skia/ext/skia_utils_mac.h" 30#include "ui/gfx/canvas_skia_paint.h" 31#include "ui/gfx/image/image.h" 32 33using content::WebContents; 34using extensions::Extension; 35using extensions::LocationBarController; 36 37namespace { 38 39// Distance to offset the bubble pointer from the bottom of the max 40// icon area of the decoration. This makes the popup's upper border 41// 2px away from the omnibox's lower border (matches omnibox popup 42// upper border). 43const CGFloat kBubblePointYOffset = 2.0; 44 45} // namespace 46 47PageActionDecoration::PageActionDecoration( 48 LocationBarViewMac* owner, 49 Browser* browser, 50 ExtensionAction* page_action) 51 : owner_(NULL), 52 browser_(browser), 53 page_action_(page_action), 54 current_tab_id_(-1), 55 preview_enabled_(false) { 56 const Extension* extension = browser->profile()->GetExtensionService()-> 57 GetExtensionById(page_action->extension_id(), false); 58 DCHECK(extension); 59 60 icon_factory_.reset(new ExtensionActionIconFactory( 61 browser_->profile(), extension, page_action, this)); 62 63 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE, 64 content::Source<Profile>(browser_->profile())); 65 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_COMMAND_PAGE_ACTION_MAC, 66 content::Source<Profile>(browser_->profile())); 67 68 // We set the owner last of all so that we can determine whether we are in 69 // the process of initializing this class or not. 70 owner_ = owner; 71} 72 73PageActionDecoration::~PageActionDecoration() {} 74 75// Always |kPageActionIconMaxSize| wide. |ImageDecoration| draws the 76// image centered. 77CGFloat PageActionDecoration::GetWidthForSpace(CGFloat width) { 78 return ExtensionAction::kPageActionIconMaxSize; 79} 80 81bool PageActionDecoration::AcceptsMousePress() { 82 return true; 83} 84 85// Either notify listeners or show a popup depending on the Page 86// Action. 87bool PageActionDecoration::OnMousePressed(NSRect frame, NSPoint location) { 88 return ActivatePageAction(frame); 89} 90 91void PageActionDecoration::ActivatePageAction() { 92 ActivatePageAction(owner_->GetPageActionFrame(page_action_)); 93} 94 95bool PageActionDecoration::ActivatePageAction(NSRect frame) { 96 WebContents* web_contents = owner_->GetWebContents(); 97 if (!web_contents) { 98 // We don't want other code to try and handle this click. Returning true 99 // prevents this by indicating that we handled it. 100 return true; 101 } 102 103 LocationBarController* controller = 104 extensions::TabHelper::FromWebContents(web_contents)-> 105 location_bar_controller(); 106 107 switch (controller->OnClicked(page_action_)) { 108 case LocationBarController::ACTION_NONE: 109 break; 110 111 case LocationBarController::ACTION_SHOW_POPUP: 112 ShowPopup(frame, page_action_->GetPopupUrl(current_tab_id_)); 113 break; 114 115 case LocationBarController::ACTION_SHOW_CONTEXT_MENU: 116 // We are never passing OnClicked a right-click button, so assume that 117 // we're never going to be asked to show a context menu. 118 // TODO(kalman): if this changes, update this class to pass the real 119 // mouse button through to the LocationBarController. 120 NOTREACHED(); 121 break; 122 } 123 124 return true; 125} 126 127void PageActionDecoration::OnIconUpdated() { 128 // If we have no owner, that means this class is still being constructed. 129 WebContents* web_contents = owner_ ? owner_->GetWebContents() : NULL; 130 if (web_contents) { 131 UpdateVisibility(web_contents, current_url_); 132 owner_->RedrawDecoration(this); 133 } 134} 135 136void PageActionDecoration::UpdateVisibility(WebContents* contents, 137 const GURL& url) { 138 // Save this off so we can pass it back to the extension when the action gets 139 // executed. See PageActionDecoration::OnMousePressed. 140 current_tab_id_ = 141 contents ? extensions::ExtensionTabUtil::GetTabId(contents) : -1; 142 current_url_ = url; 143 144 bool visible = contents && 145 (preview_enabled_ || page_action_->GetIsVisible(current_tab_id_)); 146 if (visible) { 147 SetToolTip(page_action_->GetTitle(current_tab_id_)); 148 149 // Set the image. 150 gfx::Image icon = icon_factory_->GetIcon(current_tab_id_); 151 if (!icon.IsEmpty()) { 152 SetImage(icon.ToNSImage()); 153 } else if (!GetImage()) { 154 const NSSize default_size = NSMakeSize( 155 ExtensionAction::kPageActionIconMaxSize, 156 ExtensionAction::kPageActionIconMaxSize); 157 SetImage([[[NSImage alloc] initWithSize:default_size] autorelease]); 158 } 159 } 160 161 if (IsVisible() != visible) { 162 SetVisible(visible); 163 content::NotificationService::current()->Notify( 164 chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED, 165 content::Source<ExtensionAction>(page_action_), 166 content::Details<WebContents>(contents)); 167 } 168} 169 170void PageActionDecoration::SetToolTip(NSString* tooltip) { 171 tooltip_.reset([tooltip retain]); 172} 173 174void PageActionDecoration::SetToolTip(std::string tooltip) { 175 SetToolTip(tooltip.empty() ? nil : base::SysUTF8ToNSString(tooltip)); 176} 177 178NSString* PageActionDecoration::GetToolTip() { 179 return tooltip_.get(); 180} 181 182NSPoint PageActionDecoration::GetBubblePointInFrame(NSRect frame) { 183 // This is similar to |ImageDecoration::GetDrawRectInFrame()|, 184 // except that code centers the image, which can differ in size 185 // between actions. This centers the maximum image size, so the 186 // point will consistently be at the same y position. x position is 187 // easier (the middle of the centered image is the middle of the 188 // frame). 189 const CGFloat delta_height = 190 NSHeight(frame) - ExtensionAction::kPageActionIconMaxSize; 191 const CGFloat bottom_inset = std::ceil(delta_height / 2.0); 192 193 // Return a point just below the bottom of the maximal drawing area. 194 return NSMakePoint(NSMidX(frame), 195 NSMaxY(frame) - bottom_inset + kBubblePointYOffset); 196} 197 198NSMenu* PageActionDecoration::GetMenu() { 199 ExtensionService* service = browser_->profile()->GetExtensionService(); 200 if (!service) 201 return nil; 202 const Extension* extension = service->GetExtensionById( 203 page_action_->extension_id(), false); 204 DCHECK(extension); 205 if (!extension || !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 chrome::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 chrome::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