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