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