toolbar_view.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2013 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/toolbar/toolbar_view.h"
6
7#include "base/command_line.h"
8#include "base/debug/trace_event.h"
9#include "base/i18n/number_formatting.h"
10#include "base/prefs/pref_service.h"
11#include "base/strings/utf_string_conversions.h"
12#include "chrome/app/chrome_command_ids.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/command_updater.h"
15#include "chrome/browser/extensions/extension_action.h"
16#include "chrome/browser/extensions/extension_action_manager.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/themes/theme_service.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_command_controller.h"
21#include "chrome/browser/ui/browser_commands.h"
22#include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
23#include "chrome/browser/ui/browser_instant_controller.h"
24#include "chrome/browser/ui/browser_tabstrip.h"
25#include "chrome/browser/ui/browser_window.h"
26#include "chrome/browser/ui/global_error/global_error_service.h"
27#include "chrome/browser/ui/global_error/global_error_service_factory.h"
28#include "chrome/browser/ui/omnibox/omnibox_view.h"
29#include "chrome/browser/ui/tabs/tab_strip_model.h"
30#include "chrome/browser/ui/toolbar/wrench_menu_model.h"
31#include "chrome/browser/ui/view_ids.h"
32#include "chrome/browser/ui/views/extensions/extension_message_bubble_view.h"
33#include "chrome/browser/ui/views/extensions/extension_popup.h"
34#include "chrome/browser/ui/views/frame/browser_view.h"
35#include "chrome/browser/ui/views/location_bar/page_action_image_view.h"
36#include "chrome/browser/ui/views/location_bar/page_action_with_badge_view.h"
37#include "chrome/browser/ui/views/location_bar/star_view.h"
38#include "chrome/browser/ui/views/location_bar/translate_icon_view.h"
39#include "chrome/browser/ui/views/outdated_upgrade_bubble_view.h"
40#include "chrome/browser/ui/views/toolbar/back_button.h"
41#include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
42#include "chrome/browser/ui/views/toolbar/home_button.h"
43#include "chrome/browser/ui/views/toolbar/reload_button.h"
44#include "chrome/browser/ui/views/toolbar/toolbar_button.h"
45#include "chrome/browser/ui/views/toolbar/wrench_menu.h"
46#include "chrome/browser/ui/views/toolbar/wrench_toolbar_button.h"
47#include "chrome/browser/upgrade_detector.h"
48#include "chrome/common/chrome_switches.h"
49#include "chrome/common/pref_names.h"
50#include "content/public/browser/browser_accessibility_state.h"
51#include "content/public/browser/notification_service.h"
52#include "content/public/browser/render_view_host.h"
53#include "content/public/browser/user_metrics.h"
54#include "content/public/browser/web_contents.h"
55#include "grit/chromium_strings.h"
56#include "grit/generated_resources.h"
57#include "grit/theme_resources.h"
58#include "ui/accessibility/ax_view_state.h"
59#include "ui/aura/window.h"
60#include "ui/base/l10n/l10n_util.h"
61#include "ui/base/theme_provider.h"
62#include "ui/base/window_open_disposition.h"
63#include "ui/compositor/layer.h"
64#include "ui/gfx/canvas.h"
65#include "ui/gfx/image/canvas_image_source.h"
66#include "ui/keyboard/keyboard_controller.h"
67#include "ui/native_theme/native_theme_aura.h"
68#include "ui/views/controls/menu/menu_listener.h"
69#include "ui/views/focus/view_storage.h"
70#include "ui/views/widget/tooltip_manager.h"
71#include "ui/views/widget/widget.h"
72#include "ui/views/window/non_client_view.h"
73
74#if defined(OS_WIN)
75#include "base/win/windows_version.h"
76#include "chrome/browser/enumerate_modules_model_win.h"
77#include "chrome/browser/ui/views/conflicting_module_view_win.h"
78#include "chrome/browser/ui/views/critical_notification_bubble_view.h"
79#endif
80
81#if !defined(OS_CHROMEOS)
82#include "chrome/browser/signin/signin_global_error_factory.h"
83#include "chrome/browser/sync/sync_global_error_factory.h"
84#endif
85
86#if defined(USE_ASH)
87#include "ash/shell.h"
88#endif
89
90using base::UserMetricsAction;
91using content::WebContents;
92
93namespace {
94
95// The edge graphics have some built-in spacing/shadowing, so we have to adjust
96// our spacing to make it match.
97const int kLeftEdgeSpacing = 3;
98const int kRightEdgeSpacing = 2;
99
100// Ash doesn't use a rounded content area and its top edge has an extra shadow.
101const int kContentShadowHeightAsh = 2;
102
103// Non-ash uses a rounded content area with no shadow in the assets.
104const int kContentShadowHeight = 0;
105
106bool IsStreamlinedHostedAppsEnabled() {
107  return CommandLine::ForCurrentProcess()->HasSwitch(
108      switches::kEnableStreamlinedHostedApps);
109}
110
111#if !defined(OS_CHROMEOS)
112bool HasAshShell() {
113#if defined(USE_ASH)
114  return ash::Shell::HasInstance();
115#else
116  return false;
117#endif  // USE_ASH
118}
119#endif  // OS_CHROMEOS
120
121}  // namespace
122
123// static
124const char ToolbarView::kViewClassName[] = "ToolbarView";
125
126////////////////////////////////////////////////////////////////////////////////
127// ToolbarView, public:
128
129ToolbarView::ToolbarView(Browser* browser)
130    : back_(NULL),
131      forward_(NULL),
132      reload_(NULL),
133      home_(NULL),
134      location_bar_(NULL),
135      browser_actions_(NULL),
136      app_menu_(NULL),
137      browser_(browser),
138      extension_message_bubble_factory_(
139          new extensions::ExtensionMessageBubbleFactory(browser->profile(),
140                                                        this)) {
141  set_id(VIEW_ID_TOOLBAR);
142
143  chrome::AddCommandObserver(browser_, IDC_BACK, this);
144  chrome::AddCommandObserver(browser_, IDC_FORWARD, this);
145  chrome::AddCommandObserver(browser_, IDC_RELOAD, this);
146  chrome::AddCommandObserver(browser_, IDC_HOME, this);
147  chrome::AddCommandObserver(browser_, IDC_LOAD_NEW_TAB_PAGE, this);
148
149  display_mode_ = DISPLAYMODE_LOCATION;
150  if (browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ||
151      (browser->is_app() && IsStreamlinedHostedAppsEnabled()))
152    display_mode_ = DISPLAYMODE_NORMAL;
153
154  registrar_.Add(this, chrome::NOTIFICATION_UPGRADE_RECOMMENDED,
155                 content::NotificationService::AllSources());
156  if (OutdatedUpgradeBubbleView::IsAvailable()) {
157    registrar_.Add(this, chrome::NOTIFICATION_OUTDATED_INSTALL,
158                   content::NotificationService::AllSources());
159    registrar_.Add(this, chrome::NOTIFICATION_OUTDATED_INSTALL_NO_AU,
160                   content::NotificationService::AllSources());
161  }
162#if defined(OS_WIN)
163  registrar_.Add(this, chrome::NOTIFICATION_CRITICAL_UPGRADE_INSTALLED,
164                 content::NotificationService::AllSources());
165  if (base::win::GetVersion() == base::win::VERSION_XP) {
166    registrar_.Add(this, chrome::NOTIFICATION_MODULE_LIST_ENUMERATED,
167                   content::NotificationService::AllSources());
168  }
169#endif
170  registrar_.Add(this,
171                 chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE,
172                 content::NotificationService::AllSources());
173  registrar_.Add(this, chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED,
174                 content::Source<Profile>(browser_->profile()));
175}
176
177ToolbarView::~ToolbarView() {
178  // NOTE: Don't remove the command observers here.  This object gets destroyed
179  // after the Browser (which owns the CommandUpdater), so the CommandUpdater is
180  // already gone.
181}
182
183void ToolbarView::Init() {
184  GetWidget()->AddObserver(this);
185
186  back_ = new BackButton(this, new BackForwardMenuModel(
187      browser_, BackForwardMenuModel::BACKWARD_MENU));
188  back_->set_triggerable_event_flags(
189      ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON);
190  back_->set_tag(IDC_BACK);
191  back_->SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_BACK));
192  back_->SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_BACK));
193  back_->set_id(VIEW_ID_BACK_BUTTON);
194  back_->Init();
195
196  forward_ = new ToolbarButton(this, new BackForwardMenuModel(
197      browser_, BackForwardMenuModel::FORWARD_MENU));
198  forward_->set_triggerable_event_flags(
199      ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON);
200  forward_->set_tag(IDC_FORWARD);
201  forward_->SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_FORWARD));
202  forward_->SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_FORWARD));
203  forward_->set_id(VIEW_ID_FORWARD_BUTTON);
204  forward_->Init();
205
206  location_bar_ = new LocationBarView(
207      browser_, browser_->profile(),
208      browser_->command_controller()->command_updater(), this,
209      display_mode_ == DISPLAYMODE_LOCATION);
210
211  reload_ = new ReloadButton(browser_->command_controller()->command_updater());
212  reload_->set_triggerable_event_flags(
213      ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON);
214  reload_->set_tag(IDC_RELOAD);
215  reload_->SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_RELOAD));
216  reload_->set_id(VIEW_ID_RELOAD_BUTTON);
217  reload_->Init();
218
219  home_ = new HomeButton(this, browser_);
220  home_->set_triggerable_event_flags(
221      ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON);
222  home_->set_tag(IDC_HOME);
223  home_->SetTooltipText(l10n_util::GetStringUTF16(IDS_TOOLTIP_HOME));
224  home_->SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_HOME));
225  home_->set_id(VIEW_ID_HOME_BUTTON);
226  home_->Init();
227
228  browser_actions_ = new BrowserActionsContainer(browser_, this);
229
230  app_menu_ = new WrenchToolbarButton(this);
231  app_menu_->SetBorder(views::Border::NullBorder());
232  app_menu_->EnableCanvasFlippingForRTLUI(true);
233  app_menu_->SetAccessibleName(l10n_util::GetStringUTF16(IDS_ACCNAME_APP));
234  app_menu_->SetTooltipText(l10n_util::GetStringUTF16(IDS_APPMENU_TOOLTIP));
235  app_menu_->set_id(VIEW_ID_APP_MENU);
236
237  // Always add children in order from left to right, for accessibility.
238  AddChildView(back_);
239  AddChildView(forward_);
240  AddChildView(reload_);
241  AddChildView(home_);
242  AddChildView(location_bar_);
243  AddChildView(browser_actions_);
244  AddChildView(app_menu_);
245
246  LoadImages();
247
248  // Start global error services now so we badge the menu correctly in non-Ash.
249#if !defined(OS_CHROMEOS)
250  if (!HasAshShell()) {
251    SigninGlobalErrorFactory::GetForProfile(browser_->profile());
252#if !defined(OS_ANDROID)
253    SyncGlobalErrorFactory::GetForProfile(browser_->profile());
254#endif
255  }
256#endif  // OS_CHROMEOS
257
258  // Add any necessary badges to the menu item based on the system state.
259  // Do this after |app_menu_| has been added as a bubble may be shown that
260  // needs the widget (widget found by way of app_menu_->GetWidget()).
261  UpdateAppMenuState();
262
263  location_bar_->Init();
264
265  show_home_button_.Init(prefs::kShowHomeButton,
266                         browser_->profile()->GetPrefs(),
267                         base::Bind(&ToolbarView::OnShowHomeButtonChanged,
268                                    base::Unretained(this)));
269
270  browser_actions_->Init();
271
272  // Accessibility specific tooltip text.
273  if (content::BrowserAccessibilityState::GetInstance()->
274          IsAccessibleBrowser()) {
275    back_->SetTooltipText(
276        l10n_util::GetStringUTF16(IDS_ACCNAME_TOOLTIP_BACK));
277    forward_->SetTooltipText(
278        l10n_util::GetStringUTF16(IDS_ACCNAME_TOOLTIP_FORWARD));
279  }
280}
281
282void ToolbarView::OnWidgetVisibilityChanged(views::Widget* widget,
283                                            bool visible) {
284  // Safe to call multiple times; the bubble will only appear once.
285  if (visible)
286    extension_message_bubble_factory_->MaybeShow(app_menu_);
287}
288
289void ToolbarView::Update(WebContents* tab) {
290  if (location_bar_)
291    location_bar_->Update(tab);
292  if (browser_actions_)
293    browser_actions_->RefreshBrowserActionViews();
294  if (reload_)
295    reload_->set_menu_enabled(chrome::IsDebuggerAttachedToCurrentTab(browser_));
296}
297
298void ToolbarView::SetPaneFocusAndFocusAppMenu() {
299  SetPaneFocus(app_menu_);
300}
301
302bool ToolbarView::IsAppMenuFocused() {
303  return app_menu_->HasFocus();
304}
305
306void ToolbarView::AddMenuListener(views::MenuListener* listener) {
307  menu_listeners_.AddObserver(listener);
308}
309
310void ToolbarView::RemoveMenuListener(views::MenuListener* listener) {
311  menu_listeners_.RemoveObserver(listener);
312}
313
314views::View* ToolbarView::GetBookmarkBubbleAnchor() {
315  views::View* star_view = location_bar()->star_view();
316  return (star_view && star_view->visible()) ? star_view : app_menu_;
317}
318
319views::View* ToolbarView::GetTranslateBubbleAnchor() {
320  views::View* translate_icon_view = location_bar()->translate_icon_view();
321  return (translate_icon_view && translate_icon_view->visible()) ?
322      translate_icon_view : app_menu_;
323}
324
325void ToolbarView::ExecuteExtensionCommand(
326    const extensions::Extension* extension,
327    const extensions::Command& command) {
328  browser_actions_->ExecuteExtensionCommand(extension, command);
329}
330
331void ToolbarView::ShowPageActionPopup(const extensions::Extension* extension) {
332  extensions::ExtensionActionManager* extension_manager =
333      extensions::ExtensionActionManager::Get(browser_->profile());
334  ExtensionAction* extension_action =
335      extension_manager->GetPageAction(*extension);
336  if (extension_action) {
337    location_bar_->GetPageActionView(extension_action)->image_view()->
338        ExecuteAction(ExtensionPopup::SHOW);
339  }
340}
341
342void ToolbarView::ShowBrowserActionPopup(
343    const extensions::Extension* extension) {
344  browser_actions_->ShowPopup(extension, true);
345}
346
347views::MenuButton* ToolbarView::app_menu() const {
348  return app_menu_;
349}
350
351////////////////////////////////////////////////////////////////////////////////
352// ToolbarView, AccessiblePaneView overrides:
353
354bool ToolbarView::SetPaneFocus(views::View* initial_focus) {
355  if (!AccessiblePaneView::SetPaneFocus(initial_focus))
356    return false;
357
358  location_bar_->SetShowFocusRect(true);
359  return true;
360}
361
362void ToolbarView::GetAccessibleState(ui::AXViewState* state) {
363  state->role = ui::AX_ROLE_TOOLBAR;
364  state->name = l10n_util::GetStringUTF16(IDS_ACCNAME_TOOLBAR);
365}
366
367////////////////////////////////////////////////////////////////////////////////
368// ToolbarView, Menu::Delegate overrides:
369
370bool ToolbarView::GetAcceleratorInfo(int id, ui::Accelerator* accel) {
371  return GetWidget()->GetAccelerator(id, accel);
372}
373
374////////////////////////////////////////////////////////////////////////////////
375// ToolbarView, views::MenuButtonListener implementation:
376
377void ToolbarView::OnMenuButtonClicked(views::View* source,
378                                      const gfx::Point& point) {
379  TRACE_EVENT0("views", "ToolbarView::OnMenuButtonClicked");
380  DCHECK_EQ(VIEW_ID_APP_MENU, source->id());
381
382  bool use_new_menu = false;
383  bool supports_new_separators = false;
384  // TODO: remove this.
385#if !defined(OS_LINUX) || defined(OS_CHROMEOS)
386  supports_new_separators =
387      GetNativeTheme() == ui::NativeThemeAura::instance();
388  use_new_menu = supports_new_separators;
389#endif
390
391  if (keyboard::KeyboardController::GetInstance() &&
392      keyboard::KeyboardController::GetInstance()->keyboard_visible()) {
393    keyboard::KeyboardController::GetInstance()->HideKeyboard(
394        keyboard::KeyboardController::HIDE_REASON_AUTOMATIC);
395  }
396
397  wrench_menu_.reset(new WrenchMenu(browser_, use_new_menu,
398                                    supports_new_separators));
399  wrench_menu_model_.reset(new WrenchMenuModel(this, browser_, use_new_menu));
400  wrench_menu_->Init(wrench_menu_model_.get());
401
402  FOR_EACH_OBSERVER(views::MenuListener, menu_listeners_, OnMenuOpened());
403
404  wrench_menu_->RunMenu(app_menu_);
405}
406
407////////////////////////////////////////////////////////////////////////////////
408// ToolbarView, LocationBarView::Delegate implementation:
409
410WebContents* ToolbarView::GetWebContents() {
411  return browser_->tab_strip_model()->GetActiveWebContents();
412}
413
414ToolbarModel* ToolbarView::GetToolbarModel() {
415  return browser_->toolbar_model();
416}
417
418const ToolbarModel* ToolbarView::GetToolbarModel() const {
419  return browser_->toolbar_model();
420}
421
422InstantController* ToolbarView::GetInstant() {
423  return browser_->instant_controller() ?
424      browser_->instant_controller()->instant() : NULL;
425}
426
427ContentSettingBubbleModelDelegate*
428ToolbarView::GetContentSettingBubbleModelDelegate() {
429  return browser_->content_setting_bubble_model_delegate();
430}
431
432void ToolbarView::ShowWebsiteSettings(content::WebContents* web_contents,
433                                      const GURL& url,
434                                      const content::SSLStatus& ssl) {
435  chrome::ShowWebsiteSettings(browser_, web_contents, url, ssl);
436}
437
438views::Widget* ToolbarView::CreateViewsBubble(
439    views::BubbleDelegateView* bubble_delegate) {
440  return views::BubbleDelegateView::CreateBubble(bubble_delegate);
441}
442
443PageActionImageView* ToolbarView::CreatePageActionImageView(
444    LocationBarView* owner, ExtensionAction* action) {
445  return new PageActionImageView(owner, action, browser_);
446}
447
448////////////////////////////////////////////////////////////////////////////////
449// ToolbarView, CommandObserver implementation:
450
451void ToolbarView::EnabledStateChangedForCommand(int id, bool enabled) {
452  views::Button* button = NULL;
453  switch (id) {
454    case IDC_BACK:
455      button = back_;
456      break;
457    case IDC_FORWARD:
458      button = forward_;
459      break;
460    case IDC_RELOAD:
461      button = reload_;
462      break;
463    case IDC_HOME:
464      button = home_;
465      break;
466  }
467  if (button)
468    button->SetEnabled(enabled);
469}
470
471////////////////////////////////////////////////////////////////////////////////
472// ToolbarView, views::Button::ButtonListener implementation:
473
474void ToolbarView::ButtonPressed(views::Button* sender,
475                                const ui::Event& event) {
476  chrome::ExecuteCommandWithDisposition(
477      browser_, sender->tag(), ui::DispositionFromEventFlags(event.flags()));
478}
479
480////////////////////////////////////////////////////////////////////////////////
481// ToolbarView, content::NotificationObserver implementation:
482
483void ToolbarView::Observe(int type,
484                          const content::NotificationSource& source,
485                          const content::NotificationDetails& details) {
486  switch (type) {
487    case chrome::NOTIFICATION_UPGRADE_RECOMMENDED:
488    case chrome::NOTIFICATION_MODULE_INCOMPATIBILITY_BADGE_CHANGE:
489    case chrome::NOTIFICATION_GLOBAL_ERRORS_CHANGED:
490    case chrome::NOTIFICATION_MODULE_LIST_ENUMERATED:
491      UpdateAppMenuState();
492      break;
493    case chrome::NOTIFICATION_OUTDATED_INSTALL:
494      ShowOutdatedInstallNotification(true);
495      break;
496    case chrome::NOTIFICATION_OUTDATED_INSTALL_NO_AU:
497      ShowOutdatedInstallNotification(false);
498      break;
499#if defined(OS_WIN)
500    case chrome::NOTIFICATION_CRITICAL_UPGRADE_INSTALLED:
501      ShowCriticalNotification();
502      break;
503#endif
504    default:
505      NOTREACHED();
506  }
507}
508
509////////////////////////////////////////////////////////////////////////////////
510// ToolbarView, ui::AcceleratorProvider implementation:
511
512bool ToolbarView::GetAcceleratorForCommandId(int command_id,
513    ui::Accelerator* accelerator) {
514  return GetWidget()->GetAccelerator(command_id, accelerator);
515}
516
517////////////////////////////////////////////////////////////////////////////////
518// ToolbarView, views::View overrides:
519
520gfx::Size ToolbarView::GetPreferredSize() const {
521  gfx::Size size(location_bar_->GetPreferredSize());
522  if (is_display_mode_normal()) {
523    int content_width = kLeftEdgeSpacing + back_->GetPreferredSize().width() +
524        forward_->GetPreferredSize().width() +
525        reload_->GetPreferredSize().width() +
526        (show_home_button_.GetValue() ? home_->GetPreferredSize().width() : 0) +
527        kStandardSpacing + browser_actions_->GetPreferredSize().width() +
528        app_menu_->GetPreferredSize().width() + kRightEdgeSpacing;
529    size.Enlarge(content_width, 0);
530  }
531  return SizeForContentSize(size);
532}
533
534gfx::Size ToolbarView::GetMinimumSize() const {
535  gfx::Size size(location_bar_->GetMinimumSize());
536  if (is_display_mode_normal()) {
537    int content_width = kLeftEdgeSpacing + back_->GetMinimumSize().width() +
538        forward_->GetMinimumSize().width() + reload_->GetMinimumSize().width() +
539        (show_home_button_.GetValue() ? home_->GetMinimumSize().width() : 0) +
540        kStandardSpacing + browser_actions_->GetMinimumSize().width() +
541        app_menu_->GetMinimumSize().width() + kRightEdgeSpacing;
542    size.Enlarge(content_width, 0);
543  }
544  return SizeForContentSize(size);
545}
546
547void ToolbarView::Layout() {
548  // If we have not been initialized yet just do nothing.
549  if (back_ == NULL)
550    return;
551
552  if (!is_display_mode_normal()) {
553    location_bar_->SetBounds(0, PopupTopSpacing(), width(),
554                             location_bar_->GetPreferredSize().height());
555    return;
556  }
557
558  // We assume all child elements except the location bar are the same height.
559  // Set child_y such that buttons appear vertically centered. We put any excess
560  // padding above the buttons.
561  int child_height =
562      std::min(back_->GetPreferredSize().height(), height());
563  int child_y = (height() - child_height + 1) / 2;
564
565  // If the window is maximized, we extend the back button to the left so that
566  // clicking on the left-most pixel will activate the back button.
567  // TODO(abarth):  If the window becomes maximized but is not resized,
568  //                then Layout() might not be called and the back button
569  //                will be slightly the wrong size.  We should force a
570  //                Layout() in this case.
571  //                http://crbug.com/5540
572  bool maximized = browser_->window() && browser_->window()->IsMaximized();
573  int back_width = back_->GetPreferredSize().width();
574  if (maximized) {
575    back_->SetBounds(0, child_y, back_width + kLeftEdgeSpacing, child_height);
576    back_->SetLeadingMargin(kLeftEdgeSpacing);
577  } else {
578    back_->SetBounds(kLeftEdgeSpacing, child_y, back_width, child_height);
579    back_->SetLeadingMargin(0);
580  }
581  int next_element_x = back_->bounds().right();
582
583  forward_->SetBounds(next_element_x, child_y,
584                      forward_->GetPreferredSize().width(), child_height);
585  next_element_x = forward_->bounds().right();
586
587  reload_->SetBounds(next_element_x, child_y,
588                     reload_->GetPreferredSize().width(), child_height);
589  next_element_x = reload_->bounds().right();
590
591  if (show_home_button_.GetValue() ||
592      (browser_->is_app() && IsStreamlinedHostedAppsEnabled())) {
593    home_->SetVisible(true);
594    home_->SetBounds(next_element_x, child_y,
595                     home_->GetPreferredSize().width(), child_height);
596  } else {
597    home_->SetVisible(false);
598    home_->SetBounds(next_element_x, child_y, 0, child_height);
599  }
600  next_element_x = home_->bounds().right() + kStandardSpacing;
601
602  int browser_actions_width = browser_actions_->GetPreferredSize().width();
603  int app_menu_width = app_menu_->GetPreferredSize().width();
604  int available_width = std::max(0, width() - kRightEdgeSpacing -
605      app_menu_width - browser_actions_width - next_element_x);
606
607  int location_height = location_bar_->GetPreferredSize().height();
608  int location_y = (height() - location_height + 1) / 2;
609  location_bar_->SetBounds(next_element_x, location_y,
610                           std::max(available_width, 0), location_height);
611  next_element_x = location_bar_->bounds().right();
612
613  browser_actions_->SetBounds(
614      next_element_x, child_y, browser_actions_width, child_height);
615  next_element_x = browser_actions_->bounds().right();
616
617  // The browser actions need to do a layout explicitly, because when an
618  // extension is loaded/unloaded/changed, BrowserActionContainer removes and
619  // re-adds everything, regardless of whether it has a page action. For a
620  // page action, browser action bounds do not change, as a result of which
621  // SetBounds does not do a layout at all.
622  // TODO(sidchat): Rework the above behavior so that explicit layout is not
623  //                required.
624  browser_actions_->Layout();
625
626  // Extend the app menu to the screen's right edge in maximized mode just like
627  // we extend the back button to the left edge.
628  if (maximized)
629    app_menu_width += kRightEdgeSpacing;
630  app_menu_->SetBounds(next_element_x, child_y, app_menu_width, child_height);
631}
632
633bool ToolbarView::HitTestRect(const gfx::Rect& rect) const {
634  // Fall through to the tab strip above us if none of |rect| intersects
635  // with this view (intersection with the top shadow edge does not
636  // count as intersection with this view).
637  if (rect.bottom() < content_shadow_height())
638    return false;
639  // Otherwise let our superclass take care of it.
640  return AccessiblePaneView::HitTestRect(rect);
641}
642
643void ToolbarView::OnPaint(gfx::Canvas* canvas) {
644  View::OnPaint(canvas);
645
646  if (is_display_mode_normal())
647    return;
648
649  // For glass, we need to draw a black line below the location bar to separate
650  // it from the content area.  For non-glass, the NonClientView draws the
651  // toolbar background below the location bar for us.
652  // NOTE: Keep this in sync with BrowserView::GetInfoBarSeparatorColor()!
653  if (GetWidget()->ShouldWindowContentsBeTransparent())
654    canvas->FillRect(gfx::Rect(0, height() - 1, width(), 1), SK_ColorBLACK);
655}
656
657void ToolbarView::OnThemeChanged() {
658  LoadImages();
659}
660
661const char* ToolbarView::GetClassName() const {
662  return kViewClassName;
663}
664
665bool ToolbarView::AcceleratorPressed(const ui::Accelerator& accelerator) {
666  const views::View* focused_view = focus_manager()->GetFocusedView();
667  if (focused_view && (focused_view->id() == VIEW_ID_OMNIBOX))
668    return false;  // Let the omnibox handle all accelerator events.
669  return AccessiblePaneView::AcceleratorPressed(accelerator);
670}
671
672bool ToolbarView::IsWrenchMenuShowing() const {
673  return wrench_menu_.get() && wrench_menu_->IsShowing();
674}
675
676bool ToolbarView::ShouldPaintBackground() const {
677  return display_mode_ == DISPLAYMODE_NORMAL;
678}
679
680////////////////////////////////////////////////////////////////////////////////
681// ToolbarView, protected:
682
683// Override this so that when the user presses F6 to rotate toolbar panes,
684// the location bar gets focus, not the first control in the toolbar - and
685// also so that it selects all content in the location bar.
686bool ToolbarView::SetPaneFocusAndFocusDefault() {
687  if (!location_bar_->HasFocus()) {
688    SetPaneFocus(location_bar_);
689    location_bar_->FocusLocation(true);
690    return true;
691  }
692
693  if (!AccessiblePaneView::SetPaneFocusAndFocusDefault())
694    return false;
695  browser_->window()->RotatePaneFocus(true);
696  return true;
697}
698
699void ToolbarView::RemovePaneFocus() {
700  AccessiblePaneView::RemovePaneFocus();
701  location_bar_->SetShowFocusRect(false);
702}
703
704////////////////////////////////////////////////////////////////////////////////
705// ToolbarView, private:
706
707bool ToolbarView::ShouldShowUpgradeRecommended() {
708#if defined(OS_CHROMEOS)
709  // In chromeos, the update recommendation is shown in the system tray. So it
710  // should not be displayed in the wrench menu.
711  return false;
712#else
713  return (UpgradeDetector::GetInstance()->notify_upgrade());
714#endif
715}
716
717bool ToolbarView::ShouldShowIncompatibilityWarning() {
718#if defined(OS_WIN)
719  EnumerateModulesModel* loaded_modules = EnumerateModulesModel::GetInstance();
720  loaded_modules->MaybePostScanningTask();
721  return loaded_modules->ShouldShowConflictWarning();
722#else
723  return false;
724#endif
725}
726
727int ToolbarView::PopupTopSpacing() const {
728  const int kPopupTopSpacingNonGlass = 3;
729  return GetWidget()->ShouldWindowContentsBeTransparent() ?
730      0 : kPopupTopSpacingNonGlass;
731}
732
733gfx::Size ToolbarView::SizeForContentSize(gfx::Size size) const {
734  if (is_display_mode_normal()) {
735    gfx::ImageSkia* normal_background =
736        GetThemeProvider()->GetImageSkiaNamed(IDR_CONTENT_TOP_CENTER);
737    size.SetToMax(
738        gfx::Size(0, normal_background->height() - content_shadow_height()));
739  } else {
740    const int kPopupBottomSpacingGlass = 1;
741    const int kPopupBottomSpacingNonGlass = 2;
742    size.Enlarge(
743        0,
744        PopupTopSpacing() + (GetWidget()->ShouldWindowContentsBeTransparent() ?
745            kPopupBottomSpacingGlass : kPopupBottomSpacingNonGlass));
746  }
747  return size;
748}
749
750void ToolbarView::LoadImages() {
751  ui::ThemeProvider* tp = GetThemeProvider();
752
753  back_->SetImage(views::Button::STATE_NORMAL,
754                  *(tp->GetImageSkiaNamed(IDR_BACK)));
755  back_->SetImage(views::Button::STATE_DISABLED,
756                  *(tp->GetImageSkiaNamed(IDR_BACK_D)));
757
758  forward_->SetImage(views::Button::STATE_NORMAL,
759                    *(tp->GetImageSkiaNamed(IDR_FORWARD)));
760  forward_->SetImage(views::Button::STATE_DISABLED,
761                     *(tp->GetImageSkiaNamed(IDR_FORWARD_D)));
762
763  reload_->LoadImages();
764
765  home_->SetImage(views::Button::STATE_NORMAL,
766                  *(tp->GetImageSkiaNamed(IDR_HOME)));
767}
768
769void ToolbarView::ShowCriticalNotification() {
770#if defined(OS_WIN)
771  CriticalNotificationBubbleView* bubble_delegate =
772      new CriticalNotificationBubbleView(app_menu_);
773  views::BubbleDelegateView::CreateBubble(bubble_delegate)->Show();
774#endif
775}
776
777void ToolbarView::ShowOutdatedInstallNotification(bool auto_update_enabled) {
778  if (OutdatedUpgradeBubbleView::IsAvailable()) {
779    OutdatedUpgradeBubbleView::ShowBubble(
780        app_menu_, browser_, auto_update_enabled);
781  }
782}
783
784void ToolbarView::UpdateAppMenuState() {
785  base::string16 accname_app = l10n_util::GetStringUTF16(IDS_ACCNAME_APP);
786  if (ShouldShowUpgradeRecommended()) {
787    accname_app = l10n_util::GetStringFUTF16(
788        IDS_ACCNAME_APP_UPGRADE_RECOMMENDED, accname_app);
789  }
790  app_menu_->SetAccessibleName(accname_app);
791
792  UpdateWrenchButtonSeverity();
793  SchedulePaint();
794}
795
796void ToolbarView::UpdateWrenchButtonSeverity() {
797  // Showing the bubble requires |app_menu_| to be in a widget. See comment
798  // in ConflictingModuleView for details.
799  DCHECK(app_menu_->GetWidget());
800
801  // Keep track of whether we were showing the badge before, so we don't send
802  // multiple UMA events for example when multiple Chrome windows are open.
803  static bool incompatibility_badge_showing = false;
804  // Save the old value before resetting it.
805  bool was_showing = incompatibility_badge_showing;
806  incompatibility_badge_showing = false;
807
808  if (ShouldShowUpgradeRecommended()) {
809    UpgradeDetector::UpgradeNotificationAnnoyanceLevel level =
810        UpgradeDetector::GetInstance()->upgrade_notification_stage();
811    app_menu_->SetSeverity(WrenchIconPainter::SeverityFromUpgradeLevel(level),
812                           WrenchIconPainter::ShouldAnimateUpgradeLevel(level));
813    return;
814  }
815
816  if (ShouldShowIncompatibilityWarning()) {
817    if (!was_showing) {
818      content::RecordAction(UserMetricsAction("ConflictBadge"));
819#if defined(OS_WIN)
820      ConflictingModuleView::MaybeShow(browser_, app_menu_);
821#endif
822    }
823    app_menu_->SetSeverity(WrenchIconPainter::SEVERITY_MEDIUM, true);
824    incompatibility_badge_showing = true;
825    return;
826  }
827
828  GlobalErrorService* service =
829      GlobalErrorServiceFactory::GetForProfile(browser_->profile());
830  GlobalError* error =
831      service->GetHighestSeverityGlobalErrorWithWrenchMenuItem();
832  if (error) {
833    app_menu_->SetSeverity(WrenchIconPainter::GlobalErrorSeverity(), true);
834    return;
835  }
836
837  app_menu_->SetSeverity(WrenchIconPainter::SEVERITY_NONE, true);
838}
839
840void ToolbarView::OnShowHomeButtonChanged() {
841  Layout();
842  SchedulePaint();
843}
844
845int ToolbarView::content_shadow_height() const {
846  return browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH ?
847      kContentShadowHeightAsh : kContentShadowHeight;
848}
849