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 "chrome/browser/ui/views/location_bar/page_action_image_view.h" 6 7 #include "base/utf_string_conversions.h" 8 #include "chrome/browser/extensions/extension_browser_event_router.h" 9 #include "chrome/browser/extensions/extension_service.h" 10 #include "chrome/browser/platform_util.h" 11 #include "chrome/browser/profiles/profile.h" 12 #include "chrome/browser/ui/browser_list.h" 13 #include "chrome/browser/ui/views/frame/browser_view.h" 14 #include "chrome/browser/ui/views/location_bar/location_bar_view.h" 15 #include "chrome/common/extensions/extension_action.h" 16 #include "chrome/common/extensions/extension_resource.h" 17 #include "ui/base/accessibility/accessible_view_state.h" 18 #include "views/controls/menu/menu_2.h" 19 20 PageActionImageView::PageActionImageView(LocationBarView* owner, 21 Profile* profile, 22 ExtensionAction* page_action) 23 : owner_(owner), 24 profile_(profile), 25 page_action_(page_action), 26 ALLOW_THIS_IN_INITIALIZER_LIST(tracker_(this)), 27 current_tab_id_(-1), 28 preview_enabled_(false), 29 popup_(NULL) { 30 const Extension* extension = profile->GetExtensionService()-> 31 GetExtensionById(page_action->extension_id(), false); 32 DCHECK(extension); 33 34 // Load all the icons declared in the manifest. This is the contents of the 35 // icons array, plus the default_icon property, if any. 36 std::vector<std::string> icon_paths(*page_action->icon_paths()); 37 if (!page_action_->default_icon_path().empty()) 38 icon_paths.push_back(page_action_->default_icon_path()); 39 40 for (std::vector<std::string>::iterator iter = icon_paths.begin(); 41 iter != icon_paths.end(); ++iter) { 42 tracker_.LoadImage(extension, extension->GetResource(*iter), 43 gfx::Size(Extension::kPageActionIconMaxSize, 44 Extension::kPageActionIconMaxSize), 45 ImageLoadingTracker::DONT_CACHE); 46 } 47 48 set_accessibility_focusable(true); 49 } 50 51 PageActionImageView::~PageActionImageView() { 52 if (popup_) 53 HidePopup(); 54 } 55 56 void PageActionImageView::ExecuteAction(int button, 57 bool inspect_with_devtools) { 58 if (current_tab_id_ < 0) { 59 NOTREACHED() << "No current tab."; 60 return; 61 } 62 63 if (page_action_->HasPopup(current_tab_id_)) { 64 // In tests, GetLastActive could return NULL, so we need to have 65 // a fallback. 66 // TODO(erikkay): Find a better way to get the Browser that this 67 // button is in. 68 Browser* browser = BrowserList::GetLastActiveWithProfile(profile_); 69 if (!browser) 70 browser = BrowserList::FindBrowserWithProfile(profile_); 71 DCHECK(browser); 72 73 bool popup_showing = popup_ != NULL; 74 75 // Always hide the current popup. Only one popup at a time. 76 HidePopup(); 77 78 // If we were already showing, then treat this click as a dismiss. 79 if (popup_showing) 80 return; 81 82 gfx::Rect screen_bounds(GetImageBounds()); 83 gfx::Point origin(screen_bounds.origin()); 84 View::ConvertPointToScreen(this, &origin); 85 screen_bounds.set_origin(origin); 86 87 BubbleBorder::ArrowLocation arrow_location = base::i18n::IsRTL() ? 88 BubbleBorder::TOP_LEFT : BubbleBorder::TOP_RIGHT; 89 90 popup_ = ExtensionPopup::Show( 91 page_action_->GetPopupUrl(current_tab_id_), 92 browser, 93 screen_bounds, 94 arrow_location, 95 inspect_with_devtools, 96 this); // ExtensionPopup::Observer 97 } else { 98 ExtensionService* service = profile_->GetExtensionService(); 99 service->browser_event_router()->PageActionExecuted( 100 profile_, page_action_->extension_id(), page_action_->id(), 101 current_tab_id_, current_url_.spec(), button); 102 } 103 } 104 105 void PageActionImageView::GetAccessibleState(ui::AccessibleViewState* state) { 106 state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON; 107 } 108 109 bool PageActionImageView::OnMousePressed(const views::MouseEvent& event) { 110 // We want to show the bubble on mouse release; that is the standard behavior 111 // for buttons. (Also, triggering on mouse press causes bugs like 112 // http://crbug.com/33155.) 113 return true; 114 } 115 116 void PageActionImageView::OnMouseReleased(const views::MouseEvent& event) { 117 if (!HitTest(event.location())) 118 return; 119 120 int button = -1; 121 if (event.IsLeftMouseButton()) { 122 button = 1; 123 } else if (event.IsMiddleMouseButton()) { 124 button = 2; 125 } else if (event.IsRightMouseButton()) { 126 // Get the top left point of this button in screen coordinates. 127 gfx::Point menu_origin; 128 ConvertPointToScreen(this, &menu_origin); 129 // Make the menu appear below the button. 130 menu_origin.Offset(0, height()); 131 ShowContextMenu(menu_origin, true); 132 return; 133 } 134 135 ExecuteAction(button, false); // inspect_with_devtools 136 } 137 138 bool PageActionImageView::OnKeyPressed(const views::KeyEvent& event) { 139 if (event.key_code() == ui::VKEY_SPACE || 140 event.key_code() == ui::VKEY_RETURN) { 141 ExecuteAction(1, false); 142 return true; 143 } 144 return false; 145 } 146 147 void PageActionImageView::ShowContextMenu(const gfx::Point& p, 148 bool is_mouse_gesture) { 149 const Extension* extension = profile_->GetExtensionService()-> 150 GetExtensionById(page_action()->extension_id(), false); 151 if (!extension->ShowConfigureContextMenus()) 152 return; 153 154 Browser* browser = BrowserView::GetBrowserViewForNativeWindow( 155 platform_util::GetTopLevel(GetWidget()->GetNativeView()))->browser(); 156 context_menu_contents_ = 157 new ExtensionContextMenuModel(extension, browser, this); 158 context_menu_menu_.reset(new views::Menu2(context_menu_contents_.get())); 159 context_menu_menu_->RunContextMenuAt(p); 160 } 161 162 void PageActionImageView::OnImageLoaded( 163 SkBitmap* image, const ExtensionResource& resource, int index) { 164 // We loaded icons()->size() icons, plus one extra if the page action had 165 // a default icon. 166 int total_icons = static_cast<int>(page_action_->icon_paths()->size()); 167 if (!page_action_->default_icon_path().empty()) 168 total_icons++; 169 DCHECK(index < total_icons); 170 171 // Map the index of the loaded image back to its name. If we ever get an 172 // index greater than the number of icons, it must be the default icon. 173 if (image) { 174 if (index < static_cast<int>(page_action_->icon_paths()->size())) 175 page_action_icons_[page_action_->icon_paths()->at(index)] = *image; 176 else 177 page_action_icons_[page_action_->default_icon_path()] = *image; 178 } 179 180 // During object construction (before the parent has been set) we are already 181 // in a UpdatePageActions call, so we don't need to start another one (and 182 // doing so causes crash described in http://crbug.com/57333). 183 if (parent()) 184 owner_->UpdatePageActions(); 185 } 186 187 void PageActionImageView::UpdateVisibility(TabContents* contents, 188 const GURL& url) { 189 // Save this off so we can pass it back to the extension when the action gets 190 // executed. See PageActionImageView::OnMousePressed. 191 current_tab_id_ = contents ? ExtensionTabUtil::GetTabId(contents) : -1; 192 current_url_ = url; 193 194 if (!contents || 195 (!preview_enabled_ && !page_action_->GetIsVisible(current_tab_id_))) { 196 SetVisible(false); 197 return; 198 } 199 200 // Set the tooltip. 201 tooltip_ = page_action_->GetTitle(current_tab_id_); 202 SetTooltipText(UTF8ToWide(tooltip_)); 203 204 // Set the image. 205 // It can come from three places. In descending order of priority: 206 // - The developer can set it dynamically by path or bitmap. It will be in 207 // page_action_->GetIcon(). 208 // - The developer can set it dynamically by index. It will be in 209 // page_action_->GetIconIndex(). 210 // - It can be set in the manifest by path. It will be in 211 // page_action_->default_icon_path(). 212 213 // First look for a dynamically set bitmap. 214 SkBitmap icon = page_action_->GetIcon(current_tab_id_); 215 if (icon.isNull()) { 216 int icon_index = page_action_->GetIconIndex(current_tab_id_); 217 std::string icon_path = (icon_index < 0) ? 218 page_action_->default_icon_path() : 219 page_action_->icon_paths()->at(icon_index); 220 if (!icon_path.empty()) { 221 PageActionMap::iterator iter = page_action_icons_.find(icon_path); 222 if (iter != page_action_icons_.end()) 223 icon = iter->second; 224 } 225 } 226 if (!icon.isNull()) 227 SetImage(&icon); 228 229 SetVisible(true); 230 } 231 232 void PageActionImageView::InspectPopup(ExtensionAction* action) { 233 ExecuteAction(1, // left-click 234 true); // inspect_with_devtools 235 } 236 237 void PageActionImageView::ExtensionPopupIsClosing(ExtensionPopup* popup) { 238 DCHECK_EQ(popup_, popup); 239 // ExtensionPopup is ref-counted, so we don't need to delete it. 240 popup_ = NULL; 241 } 242 243 void PageActionImageView::HidePopup() { 244 if (popup_) 245 popup_->Close(); 246 } 247