page_action_image_view.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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 "chrome/browser/ui/views/location_bar/page_action_image_view.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/extensions/api/commands/command_service.h"
9#include "chrome/browser/extensions/extension_action.h"
10#include "chrome/browser/extensions/extension_action_icon_factory.h"
11#include "chrome/browser/extensions/extension_action_manager.h"
12#include "chrome/browser/extensions/extension_context_menu_model.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/platform_util.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/sessions/session_id.h"
19#include "chrome/browser/ui/browser_list.h"
20#include "chrome/browser/ui/extensions/accelerator_priority.h"
21#include "chrome/browser/ui/views/frame/browser_view.h"
22#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
23#include "chrome/browser/ui/webui/extensions/extension_info_ui.h"
24#include "extensions/browser/extension_registry.h"
25#include "extensions/common/extension.h"
26#include "ui/accessibility/ax_view_state.h"
27#include "ui/events/event.h"
28#include "ui/gfx/canvas.h"
29#include "ui/gfx/image/image.h"
30#include "ui/views/controls/menu/menu_runner.h"
31
32using content::WebContents;
33using extensions::LocationBarController;
34using extensions::Extension;
35
36PageActionImageView::PageActionImageView(LocationBarView* owner,
37                                         ExtensionAction* page_action,
38                                         Browser* browser)
39    : owner_(owner),
40      page_action_(page_action),
41      browser_(browser),
42      current_tab_id_(-1),
43      preview_enabled_(false),
44      popup_(NULL) {
45  const Extension* extension = extensions::ExtensionRegistry::Get(
46      owner_->profile())->enabled_extensions().GetByID(
47          page_action->extension_id());
48  DCHECK(extension);
49
50  icon_factory_.reset(
51      new ExtensionActionIconFactory(
52          owner_->profile(), extension, page_action, this));
53
54  SetAccessibilityFocusable(true);
55  set_context_menu_controller(this);
56
57  extensions::CommandService* command_service =
58      extensions::CommandService::Get(browser_->profile());
59  extensions::Command page_action_command;
60  if (command_service->GetPageActionCommand(
61          extension->id(),
62          extensions::CommandService::ACTIVE_ONLY,
63          &page_action_command,
64          NULL)) {
65    page_action_keybinding_.reset(
66        new ui::Accelerator(page_action_command.accelerator()));
67    owner_->GetFocusManager()->RegisterAccelerator(
68        *page_action_keybinding_.get(),
69        GetAcceleratorPriority(page_action_command.accelerator(), extension),
70        this);
71  }
72}
73
74PageActionImageView::~PageActionImageView() {
75  if (owner_->GetFocusManager()) {
76    if (page_action_keybinding_.get()) {
77      owner_->GetFocusManager()->UnregisterAccelerator(
78          *page_action_keybinding_.get(), this);
79    }
80  }
81
82  if (popup_)
83    popup_->GetWidget()->RemoveObserver(this);
84  HidePopup();
85}
86
87void PageActionImageView::ExecuteAction(
88    ExtensionPopup::ShowAction show_action) {
89  WebContents* web_contents = owner_->GetWebContents();
90  if (!web_contents)
91    return;
92
93  extensions::TabHelper* extensions_tab_helper =
94      extensions::TabHelper::FromWebContents(web_contents);
95  LocationBarController* controller =
96      extensions_tab_helper->location_bar_controller();
97
98  switch (controller->OnClicked(page_action_)) {
99    case LocationBarController::ACTION_NONE:
100      break;
101
102    case LocationBarController::ACTION_SHOW_POPUP:
103      ShowPopupWithURL(page_action_->GetPopupUrl(current_tab_id_), show_action);
104      break;
105
106    case LocationBarController::ACTION_SHOW_CONTEXT_MENU:
107      // We are never passing OnClicked a right-click button, so assume that
108      // we're never going to be asked to show a context menu.
109      // TODO(kalman): if this changes, update this class to pass the real
110      // mouse button through to the LocationBarController.
111      NOTREACHED();
112      break;
113  }
114}
115
116void PageActionImageView::GetAccessibleState(ui::AXViewState* state) {
117  state->role = ui::AX_ROLE_BUTTON;
118  state->name = base::UTF8ToUTF16(tooltip_);
119}
120
121bool PageActionImageView::OnMousePressed(const ui::MouseEvent& event) {
122  // We want to show the bubble on mouse release; that is the standard behavior
123  // for buttons.  (Also, triggering on mouse press causes bugs like
124  // http://crbug.com/33155.)
125  return true;
126}
127
128void PageActionImageView::OnMouseReleased(const ui::MouseEvent& event) {
129  if (!HitTestPoint(event.location()))
130    return;
131
132  if (event.IsRightMouseButton()) {
133    // Don't show a menu here, its handled in View::ProcessMouseReleased. We
134    // show the context menu by way of being the ContextMenuController.
135    return;
136  }
137
138  ExecuteAction(ExtensionPopup::SHOW);
139}
140
141bool PageActionImageView::OnKeyPressed(const ui::KeyEvent& event) {
142  if (event.key_code() == ui::VKEY_SPACE ||
143      event.key_code() == ui::VKEY_RETURN) {
144    ExecuteAction(ExtensionPopup::SHOW);
145    return true;
146  }
147  return false;
148}
149
150void PageActionImageView::ShowContextMenuForView(
151    View* source,
152    const gfx::Point& point,
153    ui::MenuSourceType source_type) {
154  const Extension* extension = extensions::ExtensionRegistry::Get(
155      owner_->profile())->enabled_extensions().GetByID(
156          page_action()->extension_id());
157  if (!extension->ShowConfigureContextMenus())
158    return;
159
160  scoped_refptr<ExtensionContextMenuModel> context_menu_model(
161      new ExtensionContextMenuModel(extension, browser_, this));
162  menu_runner_.reset(new views::MenuRunner(
163      context_menu_model.get(),
164      views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU));
165  gfx::Point screen_loc;
166  views::View::ConvertPointToScreen(this, &screen_loc);
167  if (menu_runner_->RunMenuAt(GetWidget(),
168                              NULL,
169                              gfx::Rect(screen_loc, size()),
170                              views::MENU_ANCHOR_TOPLEFT,
171                              source_type) == views::MenuRunner::MENU_DELETED) {
172    return;
173  }
174}
175
176bool PageActionImageView::AcceleratorPressed(
177    const ui::Accelerator& accelerator) {
178  DCHECK(visible());  // Should not have happened due to CanHandleAccelerator.
179
180  const std::string extension_id = page_action()->extension_id();
181  const ui::AcceleratorManager::HandlerPriority priority =
182      GetAcceleratorPriorityById(accelerator, extension_id, owner_->profile());
183  // Normal priority shortcuts must be handled via standard browser commands
184  // to be processed at the proper time.
185  if (priority == ui::AcceleratorManager::kHighPriority)
186    ExecuteAction(ExtensionPopup::SHOW);
187  return priority == ui::AcceleratorManager::kHighPriority;
188}
189
190bool PageActionImageView::CanHandleAccelerators() const {
191  // While visible, we don't handle accelerators and while so we also don't
192  // count as a priority accelerator handler.
193  return visible();
194}
195
196void PageActionImageView::UpdateVisibility(WebContents* contents,
197                                           const GURL& url) {
198  // Save this off so we can pass it back to the extension when the action gets
199  // executed. See PageActionImageView::OnMousePressed.
200  current_tab_id_ =
201      contents ? extensions::ExtensionTabUtil::GetTabId(contents) : -1;
202  current_url_ = url;
203
204  if (!contents ||
205      (!preview_enabled_ && !page_action_->GetIsVisible(current_tab_id_))) {
206    SetVisible(false);
207    return;
208  }
209
210  // Set the tooltip.
211  tooltip_ = page_action_->GetTitle(current_tab_id_);
212  SetTooltipText(base::UTF8ToUTF16(tooltip_));
213
214  // Set the image.
215  gfx::Image icon = icon_factory_->GetIcon(current_tab_id_);
216  if (!icon.IsEmpty())
217    SetImage(*icon.ToImageSkia());
218
219  SetVisible(true);
220}
221
222void PageActionImageView::InspectPopup(ExtensionAction* action) {
223  ExecuteAction(ExtensionPopup::SHOW_AND_INSPECT);
224}
225
226void PageActionImageView::OnWidgetDestroying(views::Widget* widget) {
227  DCHECK_EQ(popup_->GetWidget(), widget);
228  popup_->GetWidget()->RemoveObserver(this);
229  popup_ = NULL;
230}
231
232void PageActionImageView::OnIconUpdated() {
233  WebContents* web_contents = owner_->GetWebContents();
234  if (web_contents)
235    UpdateVisibility(web_contents, current_url_);
236}
237
238void PageActionImageView::PaintChildren(gfx::Canvas* canvas,
239                                        const views::CullSet& cull_set) {
240  View::PaintChildren(canvas, cull_set);
241  if (current_tab_id_ >= 0)
242    page_action_->PaintBadge(canvas, GetLocalBounds(), current_tab_id_);
243}
244
245void PageActionImageView::ShowPopupWithURL(
246    const GURL& popup_url,
247    ExtensionPopup::ShowAction show_action) {
248  bool popup_showing = popup_ != NULL;
249
250  // Always hide the current popup. Only one popup at a time.
251  HidePopup();
252
253  // If we were already showing, then treat this click as a dismiss.
254  if (popup_showing)
255    return;
256
257  views::BubbleBorder::Arrow arrow = base::i18n::IsRTL() ?
258      views::BubbleBorder::TOP_LEFT : views::BubbleBorder::TOP_RIGHT;
259
260  popup_ = ExtensionPopup::ShowPopup(popup_url, browser_, this, arrow,
261                                     show_action);
262  popup_->GetWidget()->AddObserver(this);
263}
264
265void PageActionImageView::HidePopup() {
266  if (popup_)
267    popup_->GetWidget()->Close();
268}
269