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