root/chrome/browser/ui/gtk/location_bar_view_gtk.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CountVisibleWidgets
  2. content_setting_bubble_
  3. Update
  4. AnimationEnded
  5. GetButtonBorderColor
  6. GetGradientTopColor
  7. GetGradientBottomColor
  8. OnClick
  9. BubbleClosing
  10. AllocationToRect
  11. weak_ptr_factory_
  12. Init
  13. SetPreviewEnabledPageAction
  14. GetPageActionWidget
  15. ShowStarBubble
  16. ShowManagePasswordsBubble
  17. ZoomChangedForActiveTab
  18. SetStarred
  19. Update
  20. OnChanged
  21. OnSetFocus
  22. ShowURL
  23. GetInstant
  24. GetWebContents
  25. GetToolbarModel
  26. GetToolbarModel
  27. ShowFirstRunBubble
  28. GetDestinationURL
  29. GetWindowOpenDisposition
  30. GetPageTransition
  31. AcceptInput
  32. FocusLocation
  33. FocusSearch
  34. UpdateContentSettingsIcons
  35. UpdateManagePasswordsIconAndBubble
  36. UpdatePageActions
  37. InvalidatePageActions
  38. UpdateOpenPDFInReaderPrompt
  39. UpdateGeneratedCreditCardView
  40. SaveStateToContents
  41. Revert
  42. GetOmniboxView
  43. GetOmniboxView
  44. GetLocationBarForTesting
  45. PageActionCount
  46. PageActionVisibleCount
  47. GetPageAction
  48. GetVisiblePageAction
  49. TestPageActionPressed
  50. GetBookmarkStarVisibility
  51. Observe
  52. BuildSiteTypeArea
  53. SetSiteTypeDragSource
  54. HandleExpose
  55. OnIconReleased
  56. OnIconDragData
  57. OnIconDragBegin
  58. OnIconDragEnd
  59. OnHboxSizeAllocate
  60. OnEntryBoxSizeAllocate
  61. OnZoomButtonPress
  62. OnManagePasswordsIconButtonPress
  63. OnStarButtonSizeAllocate
  64. OnStarButtonPress
  65. UpdateSiteTypeArea
  66. UpdateEVCertificateLabelSize
  67. SetKeywordLabel
  68. SetKeywordHintLabel
  69. ShowFirstRunBubbleInternal
  70. ShowZoomBubble
  71. AdjustChildrenVisibility
  72. CreateIconButton
  73. CreateZoomButton
  74. CreateManagePasswordsIconButton
  75. CreateStarButton
  76. UpdateZoomIcon
  77. UpdateManagePasswordsIcon
  78. UpdateStarIcon
  79. ShouldOnlyShowLocation
  80. HideURL
  81. weak_factory_
  82. IsVisible
  83. AnimationProgressed
  84. AnimationEnded
  85. AnimationCanceled
  86. StartAnimating
  87. CloseAnimation
  88. OnButtonPressed
  89. OnExpose
  90. preview_enabled_
  91. IsVisible
  92. UpdateVisibility
  93. OnIconUpdated
  94. TestActivatePageAction
  95. Observe
  96. InspectPopup
  97. ConnectPageActionAccelerator
  98. DisconnectPageActionAccelerator
  99. OnButtonPressed
  100. OnExposeEvent
  101. OnRealize
  102. OnGtkAccelerator

// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/location_bar_view_gtk.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/accessibility/accessibility_events.h"
#include "chrome/browser/accessibility/accessibility_extension_api.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/command_updater.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/defaults.h"
#include "chrome/browser/extensions/api/commands/command_service.h"
#include "chrome/browser/extensions/api/omnibox/omnibox_api.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/location_bar_controller.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_command_controller.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_content_setting_bubble_model_delegate.h"
#include "chrome/browser/ui/browser_instant_controller.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
#include "chrome/browser/ui/content_settings/content_setting_image_model.h"
#include "chrome/browser/ui/gtk/bookmarks/bookmark_bubble_gtk.h"
#include "chrome/browser/ui/gtk/bookmarks/bookmark_utils_gtk.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/content_setting_bubble_gtk.h"
#include "chrome/browser/ui/gtk/extensions/extension_popup_gtk.h"
#include "chrome/browser/ui/gtk/first_run_bubble.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/manage_passwords_bubble_gtk.h"
#include "chrome/browser/ui/gtk/nine_box.h"
#include "chrome/browser/ui/gtk/omnibox/omnibox_view_gtk.h"
#include "chrome/browser/ui/gtk/rounded_window.h"
#include "chrome/browser/ui/gtk/view_id_util.h"
#include "chrome/browser/ui/gtk/zoom_bubble_gtk.h"
#include "chrome/browser/ui/omnibox/location_bar_util.h"
#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
#include "chrome/browser/ui/omnibox/omnibox_popup_model.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_ui_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/extensions/extension_info_ui.h"
#include "chrome/browser/ui/zoom/zoom_controller.h"
#include "chrome/common/badge_util.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "extensions/common/extension.h"
#include "extensions/common/feature_switch.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "net/base/net_util.h"
#include "ui/accessibility/ax_enums.h"
#include "ui/base/accelerators/platform_accelerator_gtk.h"
#include "ui/base/dragdrop/gtk_dnd_util.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/gtk/gtk_signal_registrar.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/canvas_skia_paint.h"
#include "ui/gfx/font.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"

using content::NavigationEntry;
using content::OpenURLParams;
using content::WebContents;
using extensions::LocationBarController;
using extensions::Extension;

namespace {

// We draw a border on the top and bottom (but not on left or right).
const int kBorderThickness = 1;

const int kPopupEdgeThickness = 1;
const int kNormalEdgeThickness = 2;

// Spacing needed to align the bubble with the left side of the omnibox.
const int kFirstRunBubbleLeftSpacing = 4;

// The padding around the top, bottom, and sides of the location bar hbox.
// We don't want to edit control's text to be right against the edge,
// as well the tab to search box and other widgets need to have the padding on
// top and bottom to avoid drawing larger than the location bar space.
const int kHboxBorder = 2;

// Padding between the elements in the bar.
const int kInnerPadding = 2;

// Colors used to draw the EV certificate rounded bubble.
const GdkColor kEvSecureTextColor = GDK_COLOR_RGB(0x07, 0x95, 0x00);
const GdkColor kEvSecureBackgroundColor = GDK_COLOR_RGB(0xef, 0xfc, 0xef);
const GdkColor kEvSecureBorderColor = GDK_COLOR_RGB(0x90, 0xc3, 0x90);

// Colors used to draw the Tab to Search rounded bubble.
const GdkColor kKeywordBackgroundColor = GDK_COLOR_RGB(0xf0, 0xf4, 0xfa);
const GdkColor kKeywordBorderColor = GDK_COLOR_RGB(0xcb, 0xde, 0xf7);

// Use weak gray for showing search and keyword hint text.
const GdkColor kHintTextColor = GDK_COLOR_RGB(0x75, 0x75, 0x75);

// Size of the rounding of the "Search site for:" box.
const int kCornerSize = 3;

// Default page tool animation time (open and close). In ms.
const int kPageToolAnimationTime = 150;

// The time, in ms, that the content setting label is fully displayed, for the
// cases where we animate it into and out of view.
const int kContentSettingImageDisplayTime = 3200;
// The time, in ms, of the animation (open and close).
const int kContentSettingImageAnimationTime = 150;

// Color of border of content setting area (icon/label).
const GdkColor kContentSettingBorderColor = GDK_COLOR_RGB(0xe9, 0xb9, 0x66);
// Colors for the background gradient.
const GdkColor kContentSettingTopColor = GDK_COLOR_RGB(0xff, 0xf8, 0xd4);
const GdkColor kContentSettingBottomColor = GDK_COLOR_RGB(0xff, 0xe6, 0xaf);

// If widget is visible, increment the int pointed to by count.
// Suitible for use with gtk_container_foreach.
void CountVisibleWidgets(GtkWidget* widget, gpointer count) {
  if (gtk_widget_get_visible(widget))
    *static_cast<int*>(count) += 1;
}

class ContentSettingImageViewGtk : public LocationBarViewGtk::PageToolViewGtk,
                                   public BubbleDelegateGtk {
 public:
  ContentSettingImageViewGtk(ContentSettingsType content_type,
                             LocationBarViewGtk* parent);
  virtual ~ContentSettingImageViewGtk();

  // PageToolViewGtk
  virtual void Update(WebContents* web_contents) OVERRIDE;

  // gfx::AnimationDelegate
  virtual void AnimationEnded(const gfx::Animation* animation) OVERRIDE;

 private:
  // PageToolViewGtk
  virtual GdkColor GetButtonBorderColor() const OVERRIDE;
  virtual GdkColor GetGradientTopColor() const OVERRIDE;
  virtual GdkColor GetGradientBottomColor() const OVERRIDE;
  virtual void OnClick(GtkWidget* sender) OVERRIDE;

  // BubbleDelegateGtk
  virtual void BubbleClosing(BubbleGtk* bubble,
                             bool closed_by_escape) OVERRIDE;

  // The owning LocationBarViewGtk.
  LocationBarViewGtk* parent_;

  scoped_ptr<ContentSettingImageModel> content_setting_image_model_;

  // The currently shown bubble if any.
  ContentSettingBubbleGtk* content_setting_bubble_;

  DISALLOW_COPY_AND_ASSIGN(ContentSettingImageViewGtk);
};

ContentSettingImageViewGtk::ContentSettingImageViewGtk(
    ContentSettingsType content_type,
    LocationBarViewGtk* parent)
    : PageToolViewGtk(),
      parent_(parent),
      content_setting_image_model_(
          ContentSettingImageModel::CreateContentSettingImageModel(
              content_type)),
      content_setting_bubble_(NULL) {
  animation_.SetSlideDuration(kContentSettingImageAnimationTime);
}

ContentSettingImageViewGtk::~ContentSettingImageViewGtk() {
  if (content_setting_bubble_)
    content_setting_bubble_->Close();
}

void ContentSettingImageViewGtk::Update(WebContents* web_contents) {
  if (web_contents)
    content_setting_image_model_->UpdateFromWebContents(web_contents);

  if (!content_setting_image_model_->is_visible()) {
    gtk_widget_hide(widget());
    return;
  }

  gtk_image_set_from_pixbuf(GTK_IMAGE(image_.get()),
      GtkThemeService::GetFrom(parent_->browser()->profile())->GetImageNamed(
          content_setting_image_model_->get_icon()).ToGdkPixbuf());

  gtk_widget_set_tooltip_text(widget(),
      content_setting_image_model_->get_tooltip().c_str());
  gtk_widget_show_all(widget());

  if (!web_contents)
    return;

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents);
  if (!content_settings || content_settings->IsBlockageIndicated(
      content_setting_image_model_->get_content_settings_type()))
    return;

  // The content blockage was not yet indicated to the user. Start indication
  // animation and clear "not yet shown" flag.
  content_settings->SetBlockageHasBeenIndicated(
      content_setting_image_model_->get_content_settings_type());

  int label_string_id =
      content_setting_image_model_->explanatory_string_id();
  // If there's no string for the content type, we don't animate.
  if (!label_string_id)
    return;

  gtk_label_set_text(GTK_LABEL(label_.get()),
      l10n_util::GetStringUTF8(label_string_id).c_str());
  StartAnimating();
}

void ContentSettingImageViewGtk::AnimationEnded(
    const gfx::Animation* animation) {
  if (animation_.IsShowing()) {
    base::MessageLoop::current()->PostDelayedTask(
        FROM_HERE,
        base::Bind(&ContentSettingImageViewGtk::CloseAnimation,
                   weak_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(kContentSettingImageDisplayTime));
  } else {
    gtk_widget_hide(label_.get());
    gtk_util::StopActingAsRoundedWindow(event_box_.get());
    gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), FALSE);
  }
}

GdkColor ContentSettingImageViewGtk::GetButtonBorderColor() const {
  return kContentSettingBorderColor;
}

GdkColor ContentSettingImageViewGtk::GetGradientTopColor() const {
  return kContentSettingTopColor;
}

GdkColor ContentSettingImageViewGtk::GetGradientBottomColor() const {
  return kContentSettingBottomColor;
}

void ContentSettingImageViewGtk::OnClick(
    GtkWidget* sender) {
  WebContents* web_contents = parent_->GetWebContents();
  if (!web_contents)
    return;
  Profile* profile = parent_->browser()->profile();
  content_setting_bubble_ = new ContentSettingBubbleGtk(
      sender, this,
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
          parent_->browser()->content_setting_bubble_model_delegate(),
          web_contents,
          profile,
          content_setting_image_model_->get_content_settings_type()),
      profile);
  return;
}

void ContentSettingImageViewGtk::BubbleClosing(
    BubbleGtk* bubble,
    bool closed_by_escape) {
  content_setting_bubble_ = NULL;
}

gfx::Rect AllocationToRect(const GtkAllocation& allocation) {
  return gfx::Rect(allocation.x, allocation.y,
                   allocation.width, allocation.height);
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// LocationBarViewGtk

// static
const GdkColor LocationBarViewGtk::kBackgroundColor =
    GDK_COLOR_RGB(255, 255, 255);

LocationBarViewGtk::LocationBarViewGtk(Browser* browser)
    : OmniboxEditController(browser->command_controller()->command_updater()),
      LocationBar(browser->profile()),
      zoom_image_(NULL),
      manage_passwords_icon_image_(NULL),
      star_image_(NULL),
      starred_(false),
      star_sized_(false),
      site_type_alignment_(NULL),
      site_type_event_box_(NULL),
      location_icon_image_(NULL),
      drag_icon_(NULL),
      enable_location_drag_(false),
      security_info_label_(NULL),
      tab_to_search_alignment_(NULL),
      tab_to_search_box_(NULL),
      tab_to_search_full_label_(NULL),
      tab_to_search_partial_label_(NULL),
      tab_to_search_hint_(NULL),
      tab_to_search_hint_leading_label_(NULL),
      tab_to_search_hint_icon_(NULL),
      tab_to_search_hint_trailing_label_(NULL),
      browser_(browser),
      popup_window_mode_(false),
      theme_service_(NULL),
      hbox_width_(0),
      entry_box_width_(0),
      show_selected_keyword_(false),
      show_keyword_hint_(false),
      weak_ptr_factory_(this) {
}

LocationBarViewGtk::~LocationBarViewGtk() {
  // All of our widgets should be children of / owned by the alignment.
  zoom_.Destroy();
  manage_passwords_icon_.Destroy();
  star_.Destroy();
  hbox_.Destroy();
  content_setting_hbox_.Destroy();
  page_action_hbox_.Destroy();
}

void LocationBarViewGtk::Init(bool popup_window_mode) {
  popup_window_mode_ = popup_window_mode;

  theme_service_ = GtkThemeService::GetFrom(profile());

  // Create the widget first, so we can pass it to the OmniboxViewGtk.
  hbox_.Own(gtk_hbox_new(FALSE, kInnerPadding));
  gtk_container_set_border_width(GTK_CONTAINER(hbox_.get()), kHboxBorder);
  // We will paint for the alignment, to paint the background and border.
  gtk_widget_set_app_paintable(hbox_.get(), TRUE);
  // Redraw the whole location bar when it changes size (e.g., when toggling
  // the home button on/off.
  gtk_widget_set_redraw_on_allocate(hbox_.get(), TRUE);

  // Now initialize the OmniboxViewGtk.
  omnibox_view_.reset(new OmniboxViewGtk(this, browser_, profile(),
                                         command_updater(),
                                         popup_window_mode_, hbox_.get()));
  omnibox_view_->Init();

  g_signal_connect(hbox_.get(), "expose-event",
                   G_CALLBACK(&HandleExposeThunk), this);

  BuildSiteTypeArea();

  // Put |tab_to_search_box_|, |omnibox_view_|, and |tab_to_search_hint_| into
  // a sub hbox, so that we can make this part horizontally shrinkable without
  // affecting other elements in the location bar.
  entry_box_ = gtk_hbox_new(FALSE, kInnerPadding);
  gtk_widget_show(entry_box_);
  gtk_widget_set_size_request(entry_box_, 0, -1);
  gtk_box_pack_start(GTK_BOX(hbox_.get()), entry_box_, TRUE, TRUE, 0);

  // We need to adjust the visibility of the search hint widgets according to
  // the horizontal space in the |entry_box_|.
  g_signal_connect(entry_box_, "size-allocate",
                   G_CALLBACK(&OnEntryBoxSizeAllocateThunk), this);

  // Tab to search (the keyword box on the left hand side).
  tab_to_search_full_label_ =
      theme_service_->BuildLabel(std::string(), ui::kGdkBlack);
  tab_to_search_partial_label_ =
      theme_service_->BuildLabel(std::string(), ui::kGdkBlack);
  GtkWidget* tab_to_search_label_hbox = gtk_hbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(tab_to_search_label_hbox),
                     tab_to_search_full_label_, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(tab_to_search_label_hbox),
                     tab_to_search_partial_label_, FALSE, FALSE, 0);
  GtkWidget* tab_to_search_hbox = gtk_hbox_new(FALSE, 0);
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  tab_to_search_magnifier_ = gtk_image_new_from_pixbuf(
      rb.GetNativeImageNamed(IDR_KEYWORD_SEARCH_MAGNIFIER).ToGdkPixbuf());
  gtk_box_pack_start(GTK_BOX(tab_to_search_hbox), tab_to_search_magnifier_,
                     FALSE, FALSE, 0);
  gtk_util::CenterWidgetInHBox(tab_to_search_hbox, tab_to_search_label_hbox,
                               false, 0);

  // This creates a box around the keyword text with a border, background color,
  // and padding around the text.
  tab_to_search_box_ = gtk_util::CreateGtkBorderBin(
      tab_to_search_hbox, NULL, 1, 1, 1, 3);
  gtk_widget_set_name(tab_to_search_box_, "chrome-tab-to-search-box");
  gtk_util::ActAsRoundedWindow(tab_to_search_box_, kKeywordBorderColor,
                               kCornerSize,
                               gtk_util::ROUNDED_ALL, gtk_util::BORDER_ALL);

  // Put the event box in an alignment to get the padding correct.
  tab_to_search_alignment_ = gtk_alignment_new(0, 0, 1, 1);
  gtk_container_add(GTK_CONTAINER(tab_to_search_alignment_),
                    tab_to_search_box_);
  gtk_box_pack_start(GTK_BOX(entry_box_), tab_to_search_alignment_,
                     FALSE, FALSE, 0);

  // Show all children widgets of |tab_to_search_box_| initially, except
  // |tab_to_search_partial_label_|.
  gtk_widget_show_all(tab_to_search_box_);
  gtk_widget_hide(tab_to_search_partial_label_);

  omnibox_view_alignment_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  gtk_container_add(GTK_CONTAINER(omnibox_view_alignment_),
                    omnibox_view_->GetNativeView());
  gtk_box_pack_start(GTK_BOX(entry_box_), omnibox_view_alignment_,
                     TRUE, TRUE, 0);

  // Tab to search notification (the hint on the right hand side).
  tab_to_search_hint_ = gtk_hbox_new(FALSE, 0);
  gtk_widget_set_name(tab_to_search_hint_, "chrome-tab-to-search-hint");
  tab_to_search_hint_leading_label_ =
      theme_service_->BuildLabel(std::string(), kHintTextColor);
  gtk_widget_set_sensitive(tab_to_search_hint_leading_label_, FALSE);
  tab_to_search_hint_icon_ = gtk_image_new_from_pixbuf(
      rb.GetNativeImageNamed(IDR_OMNIBOX_KEYWORD_HINT_TAB).ToGdkPixbuf());
  tab_to_search_hint_trailing_label_ =
      theme_service_->BuildLabel(std::string(), kHintTextColor);
  gtk_widget_set_sensitive(tab_to_search_hint_trailing_label_, FALSE);
  gtk_box_pack_start(GTK_BOX(tab_to_search_hint_),
                     tab_to_search_hint_leading_label_,
                     FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(tab_to_search_hint_),
                     tab_to_search_hint_icon_,
                     FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(tab_to_search_hint_),
                     tab_to_search_hint_trailing_label_,
                     FALSE, FALSE, 0);
  // Show all children widgets of |tab_to_search_hint_| initially.
  gtk_widget_show_all(tab_to_search_hint_);
  gtk_widget_hide(tab_to_search_hint_);
  // tab_to_search_hint_ gets hidden initially in OnChanged.  Hiding it here
  // doesn't work, someone is probably calling show_all on our parent box.
  gtk_box_pack_end(GTK_BOX(entry_box_), tab_to_search_hint_, FALSE, FALSE, 0);

  if (browser_defaults::bookmarks_enabled && !ShouldOnlyShowLocation()) {
    // Hide the star icon in popups, app windows, etc.
    CreateStarButton();
    gtk_box_pack_end(GTK_BOX(hbox_.get()), star_.get(), FALSE, FALSE, 0);
  }

  CreateZoomButton();
  gtk_box_pack_end(GTK_BOX(hbox_.get()), zoom_.get(), FALSE, FALSE, 0);

  CreateManagePasswordsIconButton();
  gtk_box_pack_end(GTK_BOX(hbox_.get()), manage_passwords_icon_.get(), FALSE,
                   FALSE, 0);

  content_setting_hbox_.Own(gtk_hbox_new(FALSE, kInnerPadding + 1));
  gtk_widget_set_name(content_setting_hbox_.get(),
                      "chrome-content-setting-hbox");
  gtk_box_pack_end(GTK_BOX(hbox_.get()), content_setting_hbox_.get(),
                   FALSE, FALSE, 1);

  for (int i = 0; i < CONTENT_SETTINGS_NUM_TYPES; ++i) {
    ContentSettingImageViewGtk* content_setting_view =
        new ContentSettingImageViewGtk(
            static_cast<ContentSettingsType>(i), this);
    content_setting_views_.push_back(content_setting_view);
    gtk_box_pack_end(GTK_BOX(content_setting_hbox_.get()),
                     content_setting_view->widget(), FALSE, FALSE, 0);
  }

  page_action_hbox_.Own(gtk_hbox_new(FALSE, kInnerPadding));
  gtk_widget_set_name(page_action_hbox_.get(),
                      "chrome-page-action-hbox");
  gtk_box_pack_end(GTK_BOX(hbox_.get()), page_action_hbox_.get(),
                   FALSE, FALSE, 0);

  // Now that we've created the widget hierarchy, connect to the main |hbox_|'s
  // size-allocate so we can do proper resizing and eliding on
  // |security_info_label_|.
  g_signal_connect(hbox_.get(), "size-allocate",
                   G_CALLBACK(&OnHboxSizeAllocateThunk), this);

  registrar_.Add(this,
                 chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
                 content::Source<ThemeService>(theme_service_));
  registrar_.Add(this,
                 chrome::NOTIFICATION_EXTENSION_LOCATION_BAR_UPDATED,
                 content::Source<Profile>(profile()));
  edit_bookmarks_enabled_.Init(prefs::kEditBookmarksEnabled,
                               profile()->GetPrefs(),
                               base::Bind(&LocationBarViewGtk::UpdateStarIcon,
                                          base::Unretained(this)));

  theme_service_->InitThemesFor(this);
}

void LocationBarViewGtk::SetPreviewEnabledPageAction(
    ExtensionAction* page_action,
    bool preview_enabled) {
  DCHECK(page_action);
  for (ScopedVector<PageActionViewGtk>::iterator iter =
       page_action_views_.begin(); iter != page_action_views_.end();
       ++iter) {
    if ((*iter)->page_action() == page_action) {
      (*iter)->set_preview_enabled(preview_enabled);
      UpdatePageActions();
      return;
    }
  }
}

GtkWidget* LocationBarViewGtk::GetPageActionWidget(
    ExtensionAction* page_action) {
  DCHECK(page_action);
  for (ScopedVector<PageActionViewGtk>::iterator iter =
           page_action_views_.begin();
       iter != page_action_views_.end();
       ++iter) {
    if ((*iter)->page_action() == page_action)
      return (*iter)->widget();
  }
  return NULL;
}

void LocationBarViewGtk::ShowStarBubble(const GURL& url,
                                        bool newly_bookmarked) {
  if (!star_.get())
    return;

  if (star_sized_) {
    BookmarkBubbleGtk::Show(star_.get(), profile(), url, newly_bookmarked);
  } else {
    on_star_sized_ = base::Bind(&BookmarkBubbleGtk::Show, star_.get(),
                                profile(), url, newly_bookmarked);
  }
}

void LocationBarViewGtk::ShowManagePasswordsBubble() {
  if (GetToolbarModel()->input_in_progress() || !GetWebContents())
    return;

  ManagePasswordsBubbleGtk::ShowBubble(GetWebContents());
}

void LocationBarViewGtk::ZoomChangedForActiveTab(bool can_show_bubble) {
  UpdateZoomIcon();

  if (can_show_bubble && gtk_widget_get_visible(zoom_.get()))
    ShowZoomBubble();
}

void LocationBarViewGtk::SetStarred(bool starred) {
  if (starred == starred_)
    return;

  starred_ = starred;
  UpdateStarIcon();
}

void LocationBarViewGtk::Update(const WebContents* contents) {
  UpdateZoomIcon();
  UpdateStarIcon();
  UpdateSiteTypeArea();
  UpdateContentSettingsIcons();
  UpdatePageActions();
  if (contents)
    omnibox_view_->OnTabChanged(contents);
  else
    omnibox_view_->Update();
  // The security level (background color) could have changed, etc.
  if (theme_service_->UsingNativeTheme()) {
    // In GTK mode, we need our parent to redraw, as it draws the text entry
    // border.
    gtk_widget_queue_draw(gtk_widget_get_parent(widget()));
  } else {
    gtk_widget_queue_draw(widget());
  }
  ZoomBubbleGtk::CloseBubble();
}

void LocationBarViewGtk::OnChanged() {
  UpdateSiteTypeArea();

  const base::string16 keyword(omnibox_view_->model()->keyword());
  const bool is_keyword_hint = omnibox_view_->model()->is_keyword_hint();
  show_selected_keyword_ = !keyword.empty() && !is_keyword_hint;
  show_keyword_hint_ = !keyword.empty() && is_keyword_hint;

  if (show_selected_keyword_)
    SetKeywordLabel(keyword);

  if (show_keyword_hint_)
    SetKeywordHintLabel(keyword);

  AdjustChildrenVisibility();
}

void LocationBarViewGtk::OnSetFocus() {
  AccessibilityTextBoxInfo info(profile(),
                                l10n_util::GetStringUTF8(IDS_ACCNAME_LOCATION),
                                std::string(), false);
  ExtensionAccessibilityEventRouter::GetInstance()->HandleControlEvent(
      ui::AX_EVENT_FOCUS, &info);

  // Update the keyword and search hint states.
  OnChanged();
}

void LocationBarViewGtk::ShowURL() {
  omnibox_view_->ShowURL();
}

InstantController* LocationBarViewGtk::GetInstant() {
  return browser_->instant_controller() ?
      browser_->instant_controller()->instant() : NULL;
}

WebContents* LocationBarViewGtk::GetWebContents() {
  return browser_->tab_strip_model()->GetActiveWebContents();
}

ToolbarModel* LocationBarViewGtk::GetToolbarModel() {
  return browser_->toolbar_model();
}

const ToolbarModel* LocationBarViewGtk::GetToolbarModel() const {
  return browser_->toolbar_model();
}

void LocationBarViewGtk::ShowFirstRunBubble() {
  // We need the browser window to be shown before we can show the bubble, but
  // we get called before that's happened.
  base::MessageLoop::current()->PostTask(
      FROM_HERE,
      base::Bind(&LocationBarViewGtk::ShowFirstRunBubbleInternal,
                 weak_ptr_factory_.GetWeakPtr()));
}

GURL LocationBarViewGtk::GetDestinationURL() const {
  return destination_url();
}

WindowOpenDisposition LocationBarViewGtk::GetWindowOpenDisposition() const {
  return disposition();
}

content::PageTransition LocationBarViewGtk::GetPageTransition() const {
  return transition();
}

void LocationBarViewGtk::AcceptInput() {
  omnibox_view_->model()->AcceptInput(CURRENT_TAB, false);
}

void LocationBarViewGtk::FocusLocation(bool select_all) {
  omnibox_view_->SetFocus();
  if (select_all)
    omnibox_view_->SelectAll(true);
}

void LocationBarViewGtk::FocusSearch() {
  omnibox_view_->SetFocus();
  omnibox_view_->SetForcedQuery();
}

void LocationBarViewGtk::UpdateContentSettingsIcons() {
  bool any_visible = false;
  for (ScopedVector<PageToolViewGtk>::iterator i(
           content_setting_views_.begin());
       i != content_setting_views_.end(); ++i) {
    (*i)->Update(GetToolbarModel()->input_in_progress() ?
        NULL : GetWebContents());
    any_visible = (*i)->IsVisible() || any_visible;
  }
  // If there are no visible content things, hide the top level box so it
  // doesn't mess with padding.
  gtk_widget_set_visible(content_setting_hbox_.get(), any_visible);
}

void LocationBarViewGtk::UpdateManagePasswordsIconAndBubble() {
  UpdateManagePasswordsIcon();
}

void LocationBarViewGtk::UpdatePageActions() {
  std::vector<ExtensionAction*> new_page_actions;

  WebContents* contents = GetWebContents();
  if (contents) {
    LocationBarController* location_bar_controller =
        extensions::TabHelper::FromWebContents(contents)->
            location_bar_controller();
    new_page_actions = location_bar_controller->GetCurrentActions();
  }

  // Initialize on the first call, or re-initialize if more extensions have been
  // loaded or added after startup.
  if (new_page_actions != page_actions_) {
    page_actions_.swap(new_page_actions);
    page_action_views_.clear();

    for (size_t i = 0; i < page_actions_.size(); ++i) {
      page_action_views_.push_back(
          new PageActionViewGtk(this, page_actions_[i]));
      gtk_box_pack_end(GTK_BOX(page_action_hbox_.get()),
                       page_action_views_[i]->widget(), FALSE, FALSE, 0);
    }
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_COUNT_CHANGED,
        content::Source<LocationBar>(this),
        content::NotificationService::NoDetails());
  }

  if (!page_action_views_.empty() && contents) {
    GURL url = GetWebContents()->GetURL();

    for (size_t i = 0; i < page_action_views_.size(); i++) {
      page_action_views_[i]->UpdateVisibility(
          GetToolbarModel()->input_in_progress() ? NULL : contents, url);
    }
    gtk_widget_queue_draw(hbox_.get());
  }

  // If there are no visible page actions, hide the hbox too, so that it does
  // not affect the padding in the location bar.
  gtk_widget_set_visible(page_action_hbox_.get(),
                         PageActionVisibleCount() && !ShouldOnlyShowLocation());
}

void LocationBarViewGtk::InvalidatePageActions() {
  size_t count_before = page_action_views_.size();
  page_action_views_.clear();
  if (page_action_views_.size() != count_before) {
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_COUNT_CHANGED,
        content::Source<LocationBar>(this),
        content::NotificationService::NoDetails());
  }
}

void LocationBarViewGtk::UpdateOpenPDFInReaderPrompt() {
  // Not implemented on Gtk.
}

void LocationBarViewGtk::UpdateGeneratedCreditCardView() {
  NOTIMPLEMENTED();
}

void LocationBarViewGtk::SaveStateToContents(WebContents* contents) {
  omnibox_view_->SaveStateToTab(contents);
}

void LocationBarViewGtk::Revert() {
  omnibox_view_->RevertAll();
}

const OmniboxView* LocationBarViewGtk::GetOmniboxView() const {
  return omnibox_view_.get();
}

OmniboxView* LocationBarViewGtk::GetOmniboxView() {
  return omnibox_view_.get();
}

LocationBarTesting* LocationBarViewGtk::GetLocationBarForTesting() {
  return this;
}

int LocationBarViewGtk::PageActionCount() {
  return page_action_views_.size();
}

int LocationBarViewGtk::PageActionVisibleCount() {
  int count = 0;
  gtk_container_foreach(GTK_CONTAINER(page_action_hbox_.get()),
                        CountVisibleWidgets, &count);
  return count;
}

ExtensionAction* LocationBarViewGtk::GetPageAction(size_t index) {
  if (index >= page_action_views_.size()) {
    NOTREACHED();
    return NULL;
  }

  return page_action_views_[index]->page_action();
}

ExtensionAction* LocationBarViewGtk::GetVisiblePageAction(size_t index) {
  size_t visible_index = 0;
  for (size_t i = 0; i < page_action_views_.size(); ++i) {
    if (page_action_views_[i]->IsVisible()) {
      if (index == visible_index++)
        return page_action_views_[i]->page_action();
    }
  }

  NOTREACHED();
  return NULL;
}

void LocationBarViewGtk::TestPageActionPressed(size_t index) {
  if (index >= page_action_views_.size()) {
    NOTREACHED();
    return;
  }

  page_action_views_[index]->TestActivatePageAction();
}

bool LocationBarViewGtk::GetBookmarkStarVisibility() {
  return starred_;
}

void LocationBarViewGtk::Observe(int type,
                                 const content::NotificationSource& source,
                                 const content::NotificationDetails& details) {
  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_LOCATION_BAR_UPDATED: {
      // Only update if the updated action box was for the active tab contents.
      WebContents* target_tab = content::Details<WebContents>(details).ptr();
      if (target_tab == GetWebContents())
        UpdatePageActions();
      break;
    }

    case chrome::NOTIFICATION_BROWSER_THEME_CHANGED: {
      if (theme_service_->UsingNativeTheme()) {
        gtk_widget_modify_bg(tab_to_search_box_, GTK_STATE_NORMAL, NULL);

        GdkColor border_color = theme_service_->GetGdkColor(
            ThemeProperties::COLOR_FRAME);
        gtk_util::SetRoundedWindowBorderColor(tab_to_search_box_, border_color);

        gtk_util::UndoForceFontSize(security_info_label_);
        gtk_util::UndoForceFontSize(tab_to_search_full_label_);
        gtk_util::UndoForceFontSize(tab_to_search_partial_label_);
        gtk_util::UndoForceFontSize(tab_to_search_hint_leading_label_);
        gtk_util::UndoForceFontSize(tab_to_search_hint_trailing_label_);

        gtk_alignment_set_padding(GTK_ALIGNMENT(omnibox_view_alignment_),
                                  0, 0, 0, 0);
        gtk_alignment_set_padding(GTK_ALIGNMENT(tab_to_search_alignment_),
                                  1, 1, 1, 0);
        gtk_alignment_set_padding(GTK_ALIGNMENT(site_type_alignment_),
                                  1, 1, 1, 0);
      } else {
        gtk_widget_modify_bg(tab_to_search_box_, GTK_STATE_NORMAL,
                             &kKeywordBackgroundColor);
        gtk_util::SetRoundedWindowBorderColor(tab_to_search_box_,
                                              kKeywordBorderColor);

        // Until we switch to vector graphics, force the font size of labels.
        // 12.1px = 9pt @ 96dpi
        gtk_util::ForceFontSizePixels(security_info_label_, 12.1);
        gtk_util::ForceFontSizePixels(tab_to_search_full_label_,
                                      browser_defaults::kOmniboxFontPixelSize);
        gtk_util::ForceFontSizePixels(tab_to_search_partial_label_,
                                      browser_defaults::kOmniboxFontPixelSize);
        gtk_util::ForceFontSizePixels(tab_to_search_hint_leading_label_,
                                      browser_defaults::kOmniboxFontPixelSize);
        gtk_util::ForceFontSizePixels(tab_to_search_hint_trailing_label_,
                                      browser_defaults::kOmniboxFontPixelSize);

        const int left_right = popup_window_mode_ ? kPopupEdgeThickness : 0;
        gtk_alignment_set_padding(GTK_ALIGNMENT(omnibox_view_alignment_),
                                  kBorderThickness, kBorderThickness,
                                  left_right, left_right);
        gtk_alignment_set_padding(GTK_ALIGNMENT(tab_to_search_alignment_),
                                  1, 1, 0, 0);
        gtk_alignment_set_padding(GTK_ALIGNMENT(site_type_alignment_),
                                  1, 1, 0, 0);
      }

      UpdateZoomIcon();
      UpdateManagePasswordsIcon();
      UpdateStarIcon();
      UpdateSiteTypeArea();
      UpdateContentSettingsIcons();
      break;
    }

    default:
      NOTREACHED();
  }
}

void LocationBarViewGtk::BuildSiteTypeArea() {
  location_icon_image_ = gtk_image_new();
  gtk_widget_set_name(location_icon_image_, "chrome-location-icon");

  GtkWidget* icon_alignment = gtk_alignment_new(0, 0, 1, 1);
  gtk_alignment_set_padding(GTK_ALIGNMENT(icon_alignment), 0, 0, 2, 0);
  gtk_container_add(GTK_CONTAINER(icon_alignment), location_icon_image_);
  gtk_widget_show_all(icon_alignment);

  security_info_label_ = gtk_label_new(NULL);
  gtk_label_set_ellipsize(GTK_LABEL(security_info_label_),
                          PANGO_ELLIPSIZE_MIDDLE);
  gtk_widget_modify_fg(GTK_WIDGET(security_info_label_), GTK_STATE_NORMAL,
                       &kEvSecureTextColor);
  gtk_widget_set_name(security_info_label_,
                      "chrome-location-bar-security-info-label");

  GtkWidget* site_type_hbox = gtk_hbox_new(FALSE, 1);
  gtk_box_pack_start(GTK_BOX(site_type_hbox), icon_alignment,
                     FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(site_type_hbox), security_info_label_,
                     FALSE, FALSE, 2);

  site_type_event_box_ = gtk_event_box_new();
  gtk_widget_modify_bg(site_type_event_box_, GTK_STATE_NORMAL,
                       &kEvSecureBackgroundColor);
  g_signal_connect(site_type_event_box_, "drag-data-get",
                   G_CALLBACK(&OnIconDragDataThunk), this);
  g_signal_connect(site_type_event_box_, "drag-begin",
                   G_CALLBACK(&OnIconDragBeginThunk), this);
  g_signal_connect(site_type_event_box_, "drag-end",
                   G_CALLBACK(&OnIconDragEndThunk), this);

  // Make the event box not visible so it does not paint a background.
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(site_type_event_box_),
                                   FALSE);
  gtk_widget_set_name(site_type_event_box_,
                      "chrome-location-icon-eventbox");
  gtk_container_add(GTK_CONTAINER(site_type_event_box_),
                    site_type_hbox);

  // Put the event box in an alignment to get the padding correct.
  site_type_alignment_ = gtk_alignment_new(0, 0, 1, 1);
  gtk_container_add(GTK_CONTAINER(site_type_alignment_),
                    site_type_event_box_);
  gtk_box_pack_start(GTK_BOX(hbox_.get()), site_type_alignment_,
                     FALSE, FALSE, 0);

  gtk_widget_set_tooltip_text(location_icon_image_,
      l10n_util::GetStringUTF8(IDS_TOOLTIP_LOCATION_ICON).c_str());

  g_signal_connect(site_type_event_box_, "button-release-event",
                   G_CALLBACK(&OnIconReleasedThunk), this);
}

void LocationBarViewGtk::SetSiteTypeDragSource() {
  bool enable = !GetOmniboxView()->IsEditingOrEmpty();
  if (enable_location_drag_ == enable)
    return;
  enable_location_drag_ = enable;

  if (!enable) {
    gtk_drag_source_unset(site_type_event_box_);
    return;
  }

  gtk_drag_source_set(site_type_event_box_, GDK_BUTTON1_MASK,
                      NULL, 0, GDK_ACTION_COPY);
  ui::SetSourceTargetListFromCodeMask(site_type_event_box_,
                                      ui::TEXT_PLAIN |
                                      ui::TEXT_URI_LIST |
                                      ui::CHROME_NAMED_URL);
}

gboolean LocationBarViewGtk::HandleExpose(GtkWidget* widget,
                                          GdkEventExpose* event) {
  // If we're not using GTK theming, draw our own border over the edge pixels
  // of the background.
  GtkThemeService* theme_service = GtkThemeService::GetFrom(profile());
  if (!theme_service->UsingNativeTheme()) {
    // Perform a scoped paint to fill in the background color.
    {
      gfx::CanvasSkiaPaint canvas(event, /*opaque=*/false);

      GtkAllocation allocation;
      gtk_widget_get_allocation(widget, &allocation);

      int thickness = popup_window_mode_ ?
          kPopupEdgeThickness : kNormalEdgeThickness;
      gfx::Rect bounds(allocation);
      bounds.Inset(thickness, thickness);

      const SkColor color = SK_ColorWHITE;
      if (popup_window_mode_) {
        canvas.FillRect(bounds, color);
      } else {
        SkPaint paint;
        paint.setStyle(SkPaint::kFill_Style);
        paint.setColor(color);
        const int kBorderCornerRadius = 2;
        canvas.DrawRoundRect(bounds, kBorderCornerRadius, paint);
      }
    }

    if (popup_window_mode_) {
      NineBox(IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP_LEFT,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_TOP_RIGHT,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_LEFT,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_CENTER,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_RIGHT,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM_LEFT,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM,
              IDR_OMNIBOX_POPUP_BORDER_AND_SHADOW_BOTTOM_RIGHT).
          RenderToWidget(widget);
    } else {
      NineBox(IDR_TEXTFIELD_TOP_LEFT,
              IDR_TEXTFIELD_TOP,
              IDR_TEXTFIELD_TOP_RIGHT,
              IDR_TEXTFIELD_LEFT,
              IDR_TEXTFIELD_CENTER,
              IDR_TEXTFIELD_RIGHT,
              IDR_TEXTFIELD_BOTTOM_LEFT,
              IDR_TEXTFIELD_BOTTOM,
              IDR_TEXTFIELD_BOTTOM_RIGHT).
          RenderToWidget(widget);
    }
  }

  return FALSE;  // Continue propagating the expose.
}

gboolean LocationBarViewGtk::OnIconReleased(GtkWidget* sender,
                                            GdkEventButton* event) {
  WebContents* tab = GetWebContents();

  if (event->button == 1) {
    // Do not show page info if the user has been editing the location
    // bar, or the location bar is at the NTP.
    if (GetOmniboxView()->IsEditingOrEmpty())
      return FALSE;

    // (0,0) event coordinates indicates that the release came at the end of
    // a drag.
    if (event->x == 0 && event->y == 0)
      return FALSE;

    // Important to use GetVisibleEntry to match what's showing in the omnibox.
    NavigationEntry* nav_entry = tab->GetController().GetVisibleEntry();
    if (!nav_entry) {
      NOTREACHED();
      return FALSE;
    }
    chrome::ShowWebsiteSettings(browser_, tab, nav_entry->GetURL(),
                                nav_entry->GetSSL());
    return TRUE;
  } else if (event->button == 2) {
    // When the user middle clicks on the location icon, try to open the
    // contents of the PRIMARY selection in the current tab.
    // If the click was outside our bounds, do nothing.
    if (!gtk_util::WidgetBounds(sender).Contains(
            gfx::Point(event->x, event->y))) {
      return FALSE;
    }

    GURL url;
    if (!gtk_util::URLFromPrimarySelection(profile(), &url))
      return FALSE;

    tab->OpenURL(OpenURLParams(
        url, content::Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_TYPED,
        false));
    return TRUE;
  }

  return FALSE;
}

void LocationBarViewGtk::OnIconDragData(GtkWidget* sender,
                                        GdkDragContext* context,
                                        GtkSelectionData* data,
                                        guint info, guint time) {
  ui::WriteURLWithName(data, drag_url_, drag_title_, info);
}

void LocationBarViewGtk::OnIconDragBegin(GtkWidget* sender,
                                         GdkDragContext* context) {
  content::WebContents* web_contents = GetWebContents();
  gfx::Image favicon =
      FaviconTabHelper::FromWebContents(web_contents)->GetFavicon();
  if (favicon.IsEmpty())
    return;
  drag_url_ = web_contents->GetURL();
  drag_title_ = web_contents->GetTitle();
  drag_icon_ = GetDragRepresentation(favicon.ToGdkPixbuf(), drag_title_,
                                     theme_service_);
  gtk_drag_set_icon_widget(context, drag_icon_, 0, 0);
}

void LocationBarViewGtk::OnIconDragEnd(GtkWidget* sender,
                                       GdkDragContext* context) {
  DCHECK(drag_icon_);
  gtk_widget_destroy(drag_icon_);
  drag_icon_ = NULL;
  drag_url_ = GURL::EmptyGURL();
  drag_title_.clear();
}

void LocationBarViewGtk::OnHboxSizeAllocate(GtkWidget* sender,
                                            GtkAllocation* allocation) {
  if (hbox_width_ != allocation->width) {
    hbox_width_ = allocation->width;
    UpdateEVCertificateLabelSize();
  }
  InstantService* instant_service =
      InstantServiceFactory::GetForProfile(profile());
  if (instant_service) {
    instant_service->OnOmniboxStartMarginChanged(
        AllocationToRect(*allocation).x());
  }
}

void LocationBarViewGtk::OnEntryBoxSizeAllocate(GtkWidget* sender,
                                                GtkAllocation* allocation) {
  if (entry_box_width_ != allocation->width) {
    entry_box_width_ = allocation->width;
    AdjustChildrenVisibility();
  }
}

gboolean LocationBarViewGtk::OnZoomButtonPress(GtkWidget* widget,
                                               GdkEventButton* event) {
  if (event->button == 1 && GetWebContents()) {
    // If the zoom icon is clicked, show the zoom bubble and keep it open until
    // it loses focus.
    ZoomBubbleGtk::ShowBubble(GetWebContents(), false);
    return TRUE;
  }
  return FALSE;
}

gboolean LocationBarViewGtk::OnManagePasswordsIconButtonPress(
    GtkWidget* widget, GdkEventButton* event) {
  if (event->button == 1 && GetWebContents()) {
    // If the manage passwords icon is clicked, show the manage passwords bubble
    // and keep it open until the user makes a choice or clicks outside the
    // bubble.
    ManagePasswordsBubbleGtk::ShowBubble(GetWebContents());
    return TRUE;
  }
  return FALSE;
}

void LocationBarViewGtk::OnStarButtonSizeAllocate(GtkWidget* sender,
                                                  GtkAllocation* allocation) {
  if (!on_star_sized_.is_null()) {
    on_star_sized_.Run();
    on_star_sized_.Reset();
  }
  star_sized_ = true;
}

gboolean LocationBarViewGtk::OnStarButtonPress(GtkWidget* widget,
                                               GdkEventButton* event) {
  if (event->button != 1)
    return FALSE;
  chrome::ExecuteCommand(browser_, IDC_BOOKMARK_PAGE_FROM_STAR);
  return TRUE;
}

void LocationBarViewGtk::UpdateSiteTypeArea() {
  // The icon is always visible except when the |tab_to_search_alignment_| is
  // visible.
  if (!omnibox_view_->model()->keyword().empty() &&
      !omnibox_view_->model()->is_keyword_hint()) {
    gtk_widget_hide(site_type_area());
    return;
  }

  int resource_id = omnibox_view_->GetIcon();
  gtk_image_set_from_pixbuf(
      GTK_IMAGE(location_icon_image_),
      theme_service_->GetImageNamed(resource_id).ToGdkPixbuf());

  if (GetToolbarModel()->GetSecurityLevel(false) == ToolbarModel::EV_SECURE) {
    if (!gtk_util::IsActingAsRoundedWindow(site_type_event_box_)) {
      // Fun fact: If wee try to make |site_type_event_box_| act as a
      // rounded window while it doesn't have a visible window, GTK interprets
      // this as a sign that it should paint the skyline texture into the
      // omnibox.
      gtk_event_box_set_visible_window(GTK_EVENT_BOX(site_type_event_box_),
                                       TRUE);

      gtk_util::ActAsRoundedWindow(site_type_event_box_,
                                   kEvSecureBorderColor,
                                   kCornerSize,
                                   gtk_util::ROUNDED_ALL,
                                   gtk_util::BORDER_ALL);
    }

    base::string16 info_text = GetToolbarModel()->GetEVCertName();
    gtk_label_set_text(GTK_LABEL(security_info_label_),
                       base::UTF16ToUTF8(info_text).c_str());

    UpdateEVCertificateLabelSize();

    gtk_widget_show(GTK_WIDGET(security_info_label_));
  } else {
    if (gtk_util::IsActingAsRoundedWindow(site_type_event_box_)) {
      gtk_util::StopActingAsRoundedWindow(site_type_event_box_);

      gtk_event_box_set_visible_window(GTK_EVENT_BOX(site_type_event_box_),
                                       FALSE);
    }

    gtk_widget_hide(GTK_WIDGET(security_info_label_));
  }

  if (GetOmniboxView()->IsEditingOrEmpty()) {
    // Do not show the tooltip if the user has been editing the location
    // bar, or the location bar is at the NTP.
    gtk_widget_set_tooltip_text(location_icon_image_, "");
  } else {
    gtk_widget_set_tooltip_text(location_icon_image_,
        l10n_util::GetStringUTF8(IDS_TOOLTIP_LOCATION_ICON).c_str());
  }

  gtk_widget_show(site_type_area());

  SetSiteTypeDragSource();
}

void LocationBarViewGtk::UpdateEVCertificateLabelSize() {
  // Figure out the width of the average character.
  PangoLayout* layout = gtk_label_get_layout(GTK_LABEL(security_info_label_));
  PangoContext* context = pango_layout_get_context(layout);
  PangoFontMetrics* metrics = pango_context_get_metrics(
      context,
      gtk_widget_get_style(security_info_label_)->font_desc,
      pango_context_get_language(context));
  int char_width =
      pango_font_metrics_get_approximate_char_width(metrics) / PANGO_SCALE;

  // The EV label should never take up more than half the hbox. We try to
  // correct our inaccurate measurement units ("the average character width")
  // by dividing more than an even 2.
  GtkAllocation security_label_allocation;
  gtk_widget_get_allocation(security_info_label_, &security_label_allocation);
  GtkAllocation entry_box_allocation;
  gtk_widget_get_allocation(entry_box_, &entry_box_allocation);
  int text_area = security_label_allocation.width +
                  entry_box_allocation.width;
  int max_chars = static_cast<int>(static_cast<float>(text_area) /
                                   static_cast<float>(char_width) / 2.75);
  // Don't let the label be smaller than 10 characters so that the country
  // code is always visible.
  gtk_label_set_max_width_chars(GTK_LABEL(security_info_label_),
                                std::max(10, max_chars));

  pango_font_metrics_unref(metrics);
}

void LocationBarViewGtk::SetKeywordLabel(const base::string16& keyword) {
  if (keyword.empty())
    return;

  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(profile());
  if (!template_url_service)
    return;

  bool is_extension_keyword;
  const base::string16 short_name = template_url_service->GetKeywordShortName(
      keyword, &is_extension_keyword);
  const base::string16 min_string =
      location_bar_util::CalculateMinString(short_name);
  const base::string16 full_name = is_extension_keyword ?
      short_name :
      l10n_util::GetStringFUTF16(IDS_OMNIBOX_KEYWORD_TEXT, short_name);
  const base::string16 partial_name = is_extension_keyword ?
      min_string :
      l10n_util::GetStringFUTF16(IDS_OMNIBOX_KEYWORD_TEXT, min_string);
  gtk_label_set_text(GTK_LABEL(tab_to_search_full_label_),
                     base::UTF16ToUTF8(full_name).c_str());
  gtk_label_set_text(GTK_LABEL(tab_to_search_partial_label_),
                     base::UTF16ToUTF8(partial_name).c_str());

  if (last_keyword_ != keyword) {
    last_keyword_ = keyword;

    if (is_extension_keyword) {
      const TemplateURL* template_url =
          template_url_service->GetTemplateURLForKeyword(keyword);
      gfx::Image image = extensions::OmniboxAPI::Get(profile())->
          GetOmniboxIcon(template_url->GetExtensionId());
      gtk_image_set_from_pixbuf(GTK_IMAGE(tab_to_search_magnifier_),
                                image.ToGdkPixbuf());
    } else {
      ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
      gtk_image_set_from_pixbuf(GTK_IMAGE(tab_to_search_magnifier_),
          rb.GetNativeImageNamed(IDR_OMNIBOX_SEARCH).ToGdkPixbuf());
    }
  }
}

void LocationBarViewGtk::SetKeywordHintLabel(const base::string16& keyword) {
  if (keyword.empty())
    return;

  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(profile());
  if (!template_url_service)
    return;

  bool is_extension_keyword;
  const base::string16 short_name = template_url_service->
      GetKeywordShortName(keyword, &is_extension_keyword);
  int message_id = is_extension_keyword ?
      IDS_OMNIBOX_EXTENSION_KEYWORD_HINT : IDS_OMNIBOX_KEYWORD_HINT;
  std::vector<size_t> content_param_offsets;
  const base::string16 keyword_hint = l10n_util::GetStringFUTF16(
      message_id,
      base::string16(),
      short_name,
      &content_param_offsets);
  if (content_param_offsets.size() != 2) {
    // See comments on an identical NOTREACHED() in search_provider.cc.
    NOTREACHED();
    return;
  }

  std::string leading(base::UTF16ToUTF8(
      keyword_hint.substr(0, content_param_offsets.front())));
  std::string trailing(base::UTF16ToUTF8(
      keyword_hint.substr(content_param_offsets.front())));
  gtk_label_set_text(GTK_LABEL(tab_to_search_hint_leading_label_),
                     leading.c_str());
  gtk_label_set_text(GTK_LABEL(tab_to_search_hint_trailing_label_),
                     trailing.c_str());
}

void LocationBarViewGtk::ShowFirstRunBubbleInternal() {
  if (!omnibox_view_.get() || !gtk_widget_get_window(widget()))
    return;

  gfx::Rect bounds = gtk_util::WidgetBounds(location_icon_image_);
  bounds.set_x(bounds.x() + kFirstRunBubbleLeftSpacing);
  FirstRunBubble::Show(browser_, location_icon_image_, bounds);
}

void LocationBarViewGtk::ShowZoomBubble() {
  if (GetToolbarModel()->input_in_progress() || !GetWebContents())
    return;

  ZoomBubbleGtk::ShowBubble(GetWebContents(), true);
}

void LocationBarViewGtk::AdjustChildrenVisibility() {
  int text_width = omnibox_view_->GetTextWidth();
  int available_width = entry_box_width_ - text_width - kInnerPadding;

  // Only one of |tab_to_search_alignment_| and |tab_to_search_hint_| can be
  // visible at the same time.
  if (!show_selected_keyword_ &&
      gtk_widget_get_visible(tab_to_search_alignment_)) {
    gtk_widget_hide(tab_to_search_alignment_);
  } else if (!show_keyword_hint_ &&
             gtk_widget_get_visible(tab_to_search_hint_)) {
    gtk_widget_hide(tab_to_search_hint_);
  }

  if (show_selected_keyword_) {
    GtkRequisition box, full_label, partial_label;
    gtk_widget_size_request(tab_to_search_box_, &box);
    gtk_widget_size_request(tab_to_search_full_label_, &full_label);
    gtk_widget_size_request(tab_to_search_partial_label_, &partial_label);
    int full_partial_width_diff = full_label.width - partial_label.width;
    int full_box_width;
    int partial_box_width;
    if (gtk_widget_get_visible(tab_to_search_full_label_)) {
      full_box_width = box.width;
      partial_box_width = full_box_width - full_partial_width_diff;
    } else {
      partial_box_width = box.width;
      full_box_width = partial_box_width + full_partial_width_diff;
    }

    if (partial_box_width >= entry_box_width_ - kInnerPadding) {
      gtk_widget_hide(tab_to_search_alignment_);
    } else if (full_box_width >= available_width) {
      gtk_widget_hide(tab_to_search_full_label_);
      gtk_widget_show(tab_to_search_partial_label_);
      gtk_widget_show(tab_to_search_alignment_);
    } else if (full_box_width < available_width) {
      gtk_widget_hide(tab_to_search_partial_label_);
      gtk_widget_show(tab_to_search_full_label_);
      gtk_widget_show(tab_to_search_alignment_);
    }
  } else if (show_keyword_hint_) {
    GtkRequisition leading, icon, trailing;
    gtk_widget_size_request(tab_to_search_hint_leading_label_, &leading);
    gtk_widget_size_request(tab_to_search_hint_icon_, &icon);
    gtk_widget_size_request(tab_to_search_hint_trailing_label_, &trailing);
    int full_width = leading.width + icon.width + trailing.width;

    if (icon.width >= entry_box_width_ - kInnerPadding) {
      gtk_widget_hide(tab_to_search_hint_);
    } else if (full_width >= available_width) {
      gtk_widget_hide(tab_to_search_hint_leading_label_);
      gtk_widget_hide(tab_to_search_hint_trailing_label_);
      gtk_widget_show(tab_to_search_hint_);
    } else if (full_width < available_width) {
      gtk_widget_show(tab_to_search_hint_leading_label_);
      gtk_widget_show(tab_to_search_hint_trailing_label_);
      gtk_widget_show(tab_to_search_hint_);
    }
  }
}

GtkWidget* LocationBarViewGtk::CreateIconButton(
    GtkWidget** image,
    int image_id,
    ViewID debug_id,
    int tooltip_id,
    gboolean (click_callback)(GtkWidget*, GdkEventButton*, gpointer)) {
  *image = image_id ?
      gtk_image_new_from_pixbuf(
          theme_service_->GetImageNamed(image_id).ToGdkPixbuf()) :
      gtk_image_new();

  GtkWidget* alignment = gtk_alignment_new(0, 0, 1, 1);
  gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), 0, 0,
                            0, kInnerPadding);
  gtk_container_add(GTK_CONTAINER(alignment), *image);

  GtkWidget* result = gtk_event_box_new();
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(result), FALSE);
  gtk_container_add(GTK_CONTAINER(result), alignment);
  gtk_widget_show_all(result);

  if (debug_id != VIEW_ID_NONE)
    ViewIDUtil::SetID(result, debug_id);

  if (tooltip_id) {
    gtk_widget_set_tooltip_text(result,
                                l10n_util::GetStringUTF8(tooltip_id).c_str());
  }

  g_signal_connect(result, "button-press-event",
                     G_CALLBACK(click_callback), this);

  return result;
}

void LocationBarViewGtk::CreateZoomButton() {
  zoom_.Own(CreateIconButton(&zoom_image_,
                             0,
                             VIEW_ID_ZOOM_BUTTON,
                             0,
                             OnZoomButtonPressThunk));
}

void LocationBarViewGtk::CreateManagePasswordsIconButton() {
  manage_passwords_icon_.Own(CreateIconButton(
      &manage_passwords_icon_image_, 0, VIEW_ID_MANAGE_PASSWORDS_ICON_BUTTON, 0,
      OnManagePasswordsIconButtonPressThunk));
}

void LocationBarViewGtk::CreateStarButton() {
  star_.Own(CreateIconButton(&star_image_,
                             0,
                             VIEW_ID_STAR_BUTTON,
                             IDS_TOOLTIP_STAR,
                             OnStarButtonPressThunk));
  // We need to track when the star button is resized to show any bubble
  // attached to it at this time.
  g_signal_connect(star_image_, "size-allocate",
                   G_CALLBACK(&OnStarButtonSizeAllocateThunk), this);
}

void LocationBarViewGtk::UpdateZoomIcon() {
  WebContents* web_contents = GetWebContents();
  if (!zoom_.get() || !web_contents)
    return;

  ZoomController* zoom_controller =
      ZoomController::FromWebContents(web_contents);
  if (!zoom_controller || zoom_controller->IsAtDefaultZoom() ||
      GetToolbarModel()->input_in_progress()) {
    gtk_widget_hide(zoom_.get());
    ZoomBubbleGtk::CloseBubble();
    return;
  }

  const int zoom_resource = zoom_controller->GetResourceForZoomLevel();
  gtk_image_set_from_pixbuf(GTK_IMAGE(zoom_image_),
      theme_service_->GetImageNamed(zoom_resource).ToGdkPixbuf());

  base::string16 tooltip = l10n_util::GetStringFUTF16Int(
      IDS_TOOLTIP_ZOOM, zoom_controller->zoom_percent());
  gtk_widget_set_tooltip_text(zoom_.get(), base::UTF16ToUTF8(tooltip).c_str());

  gtk_widget_show(zoom_.get());
}

void LocationBarViewGtk::UpdateManagePasswordsIcon() {
  WebContents* web_contents = GetWebContents();
  if (!manage_passwords_icon_.get() || !web_contents)
    return;

  ManagePasswordsBubbleUIController* manage_passwords_bubble_ui_controller =
      ManagePasswordsBubbleUIController::FromWebContents(web_contents);
  if (!manage_passwords_bubble_ui_controller ||
      !manage_passwords_bubble_ui_controller->password_to_be_saved() ||
      GetToolbarModel()->input_in_progress()) {
    gtk_widget_hide(manage_passwords_icon_.get());
    ManagePasswordsBubbleGtk::CloseBubble();
    return;
  }

  gtk_image_set_from_pixbuf(
      GTK_IMAGE(manage_passwords_icon_image_),
      theme_service_->GetImageNamed(IDR_SAVE_PASSWORD).ToGdkPixbuf());

  base::string16 tooltip =
      l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_TOOLTIP_SAVE);
  gtk_widget_set_tooltip_text(manage_passwords_icon_.get(),
                              base::UTF16ToUTF8(tooltip).c_str());

  gtk_widget_show(manage_passwords_icon_.get());
  if (manage_passwords_bubble_ui_controller->
          manage_passwords_bubble_needs_showing()) {
    ShowManagePasswordsBubble();
    manage_passwords_bubble_ui_controller->OnBubbleShown();
  }
}

void LocationBarViewGtk::UpdateStarIcon() {
  if (!star_.get())
    return;
  // Indicate the star icon is not correctly sized. It will be marked as sized
  // when the next size-allocate signal is received by the star widget.
  star_sized_ = false;
  if (browser_defaults::bookmarks_enabled && !popup_window_mode_ &&
      !GetToolbarModel()->input_in_progress() &&
      edit_bookmarks_enabled_.GetValue() &&
      !IsBookmarkStarHiddenByExtension()) {
    gtk_widget_show_all(star_.get());
    int id = starred_ ? IDR_STAR_LIT : IDR_STAR;
    gtk_image_set_from_pixbuf(GTK_IMAGE(star_image_),
                              theme_service_->GetImageNamed(id).ToGdkPixbuf());
    gtk_widget_set_tooltip_text(star_.get(), l10n_util::GetStringUTF8(
          starred_ ? IDS_TOOLTIP_STARRED : IDS_TOOLTIP_STAR).c_str());
  } else {
    gtk_widget_hide_all(star_.get());
  }
}

bool LocationBarViewGtk::ShouldOnlyShowLocation() {
  return !browser_->is_type_tabbed();
}

void LocationBarViewGtk::HideURL() {
  omnibox_view_->HideURL();
}

////////////////////////////////////////////////////////////////////////////////
// LocationBarViewGtk::PageToolViewGtk

LocationBarViewGtk::PageToolViewGtk::PageToolViewGtk()
    : alignment_(gtk_alignment_new(0, 0, 1, 1)),
      event_box_(gtk_event_box_new()),
      hbox_(gtk_hbox_new(FALSE, kInnerPadding)),
      image_(gtk_image_new()),
      label_(gtk_label_new(NULL)),
      animation_(this),
      weak_factory_(this) {
  gtk_alignment_set_padding(GTK_ALIGNMENT(alignment_.get()), 1, 1, 0, 0);
  gtk_container_add(GTK_CONTAINER(alignment_.get()), event_box_.get());

  // Make the event box not visible so it does not paint a background.
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), FALSE);
  g_signal_connect(event_box_.get(), "button-press-event",
                   G_CALLBACK(&OnButtonPressedThunk), this);
  g_signal_connect(event_box_.get(), "expose-event",
                   G_CALLBACK(&OnExposeThunk), this);

  gtk_widget_set_no_show_all(label_.get(), TRUE);
  gtk_label_set_line_wrap(GTK_LABEL(label_.get()), FALSE);

  gtk_box_pack_start(GTK_BOX(hbox_), image_.get(), FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox_), label_.get(), FALSE, FALSE, 0);

  gtk_container_set_border_width(GTK_CONTAINER(hbox_), kHboxBorder);

  gtk_container_add(GTK_CONTAINER(event_box_.get()), hbox_);
  gtk_widget_hide(widget());

  animation_.SetSlideDuration(kPageToolAnimationTime);
}

LocationBarViewGtk::PageToolViewGtk::~PageToolViewGtk() {
  image_.Destroy();
  label_.Destroy();
  event_box_.Destroy();
  alignment_.Destroy();
}

bool LocationBarViewGtk::PageToolViewGtk::IsVisible() {
  return gtk_widget_get_visible(widget());
}

void LocationBarViewGtk::PageToolViewGtk::AnimationProgressed(
    const gfx::Animation* animation) {
  gtk_widget_set_size_request(
      label_.get(),
      animation->GetCurrentValue() * label_req_.width,
      -1);
}

void LocationBarViewGtk::PageToolViewGtk::AnimationEnded(
    const gfx::Animation* animation) {
}

void LocationBarViewGtk::PageToolViewGtk::AnimationCanceled(
    const gfx::Animation* animation) {
}

void LocationBarViewGtk::PageToolViewGtk::StartAnimating() {
  if (animation_.IsShowing() || animation_.IsClosing())
    return;

  gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), TRUE);
  GdkColor border_color = GetButtonBorderColor();
  gtk_util::ActAsRoundedWindow(event_box_.get(), border_color,
                               kCornerSize,
                               gtk_util::ROUNDED_ALL, gtk_util::BORDER_ALL);

  gtk_widget_set_size_request(label_.get(), -1, -1);
  gtk_widget_size_request(label_.get(), &label_req_);
  gtk_widget_set_size_request(label_.get(), 0, -1);
  gtk_widget_show(label_.get());

  animation_.Show();
}

void LocationBarViewGtk::PageToolViewGtk::CloseAnimation() {
  animation_.Hide();
}

gboolean LocationBarViewGtk::PageToolViewGtk::OnButtonPressed(
    GtkWidget* sender, GdkEvent* event) {
  OnClick(sender);
  return TRUE;
}

gboolean LocationBarViewGtk::PageToolViewGtk::OnExpose(
    GtkWidget* sender, GdkEventExpose* event) {
  TRACE_EVENT0("ui::gtk", "LocationBarViewGtk::PageToolViewGtk::OnExpose");

  if (!(animation_.IsShowing() || animation_.IsClosing()))
    return FALSE;

  GtkAllocation allocation;
  gtk_widget_get_allocation(sender, &allocation);
  const int height = allocation.height;

  cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(sender));
  gdk_cairo_rectangle(cr, &event->area);
  cairo_clip(cr);

  cairo_pattern_t* pattern = cairo_pattern_create_linear(0, 0, 0, height);

  const GdkColor top_color = GetGradientTopColor();
  const GdkColor bottom_color = GetGradientBottomColor();
  cairo_pattern_add_color_stop_rgb(
      pattern, 0.0,
      top_color.red/255.0,
      top_color.blue/255.0,
      top_color.green/255.0);
  cairo_pattern_add_color_stop_rgb(
      pattern, 1.0,
      bottom_color.red/255.0,
      bottom_color.blue/255.0,
      bottom_color.green/255.0);

  cairo_set_source(cr, pattern);
  cairo_paint(cr);
  cairo_pattern_destroy(pattern);
  cairo_destroy(cr);

  return FALSE;
}

////////////////////////////////////////////////////////////////////////////////
// LocationBarViewGtk::PageActionViewGtk

LocationBarViewGtk::PageActionViewGtk::PageActionViewGtk(
    LocationBarViewGtk* owner,
    ExtensionAction* page_action)
    : owner_(NULL),
      page_action_(page_action),
      current_tab_id_(-1),
      window_(NULL),
      accel_group_(NULL),
      preview_enabled_(false) {
  event_box_.Own(gtk_event_box_new());
  gtk_widget_set_size_request(event_box_.get(),
                              extensions::IconsInfo::kPageActionIconMaxSize,
                              extensions::IconsInfo::kPageActionIconMaxSize);

  // Make the event box not visible so it does not paint a background.
  gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box_.get()), FALSE);
  g_signal_connect(event_box_.get(), "button-press-event",
                   G_CALLBACK(&OnButtonPressedThunk), this);
  g_signal_connect_after(event_box_.get(), "expose-event",
                         G_CALLBACK(OnExposeEventThunk), this);
  g_signal_connect(event_box_.get(), "realize",
                   G_CALLBACK(OnRealizeThunk), this);

  image_.Own(gtk_image_new());
  gtk_container_add(GTK_CONTAINER(event_box_.get()), image_.get());

  const Extension* extension =
      owner->profile()->GetExtensionService()->GetExtensionById(
          page_action->extension_id(), false);
  DCHECK(extension);

  icon_factory_.reset(new ExtensionActionIconFactory(
      owner->profile(), extension, page_action, this));

  // We set the owner last of all so that we can determine whether we are in
  // the process of initializing this class or not.
  owner_ = owner;
}

LocationBarViewGtk::PageActionViewGtk::~PageActionViewGtk() {
  DisconnectPageActionAccelerator();

  image_.Destroy();
  event_box_.Destroy();
}

bool LocationBarViewGtk::PageActionViewGtk::IsVisible() {
  return gtk_widget_get_visible(widget());
}

void LocationBarViewGtk::PageActionViewGtk::UpdateVisibility(
    WebContents* contents, const GURL& url) {
  // Save this off so we can pass it back to the extension when the action gets
  // executed. See PageActionImageView::OnMousePressed.
  current_tab_id_ =
      contents ? extensions::ExtensionTabUtil::GetTabId(contents) : -1;
  current_url_ = url;

  bool visible = contents &&
      (preview_enabled_ || page_action_->GetIsVisible(current_tab_id_));
  if (visible) {
    // Set the tooltip.
    gtk_widget_set_tooltip_text(event_box_.get(),
        page_action_->GetTitle(current_tab_id_).c_str());

    // Set the image.
    gfx::Image icon = icon_factory_->GetIcon(current_tab_id_);
    if (!icon.IsEmpty()) {
      GdkPixbuf* pixbuf = icon.ToGdkPixbuf();
      DCHECK(pixbuf);
      gtk_image_set_from_pixbuf(GTK_IMAGE(image_.get()), pixbuf);
    }
  }

  bool old_visible = IsVisible();
  if (visible)
    gtk_widget_show_all(event_box_.get());
  else
    gtk_widget_hide_all(event_box_.get());

  if (visible != old_visible) {
    content::NotificationService::current()->Notify(
        chrome::NOTIFICATION_EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED,
        content::Source<ExtensionAction>(page_action_),
        content::Details<WebContents>(contents));
  }
}

void LocationBarViewGtk::PageActionViewGtk::OnIconUpdated() {
  // If we have no owner, that means this class is still being constructed.
  WebContents* web_contents = owner_ ? owner_->GetWebContents() : NULL;
  if (web_contents)
    UpdateVisibility(web_contents, current_url_);
}

void LocationBarViewGtk::PageActionViewGtk::TestActivatePageAction() {
  GdkEventButton event = {};
  event.type = GDK_BUTTON_PRESS;
  event.button = 1;
  OnButtonPressed(widget(), &event);
}

void LocationBarViewGtk::PageActionViewGtk::Observe(
    int type,
    const content::NotificationSource& source,
    const content::NotificationDetails& details) {
  DCHECK_EQ(type, chrome::NOTIFICATION_WINDOW_CLOSED);
  DisconnectPageActionAccelerator();
}

void LocationBarViewGtk::PageActionViewGtk::InspectPopup(
    ExtensionAction* action) {
  ExtensionPopupGtk::Show(
      action->GetPopupUrl(current_tab_id_),
      owner_->browser_,
      event_box_.get(),
      ExtensionPopupGtk::SHOW_AND_INSPECT);
}

void LocationBarViewGtk::PageActionViewGtk::ConnectPageActionAccelerator() {
  const extensions::ExtensionSet* extensions =
      owner_->profile()->GetExtensionService()->extensions();
  const Extension* extension =
      extensions->GetByID(page_action_->extension_id());
  window_ = owner_->browser()->window()->GetNativeWindow();

  extensions::CommandService* command_service =
      extensions::CommandService::Get(owner_->profile());

  extensions::Command command_page_action;
  if (command_service->GetPageActionCommand(
          extension->id(),
          extensions::CommandService::ACTIVE_ONLY,
          &command_page_action,
          NULL)) {
    // Found the page action shortcut command, register it.
    page_action_keybinding_.reset(
        new ui::Accelerator(command_page_action.accelerator()));
  }

  if (page_action_keybinding_.get()) {
    accel_group_ = gtk_accel_group_new();
    gtk_window_add_accel_group(window_, accel_group_);

    gtk_accel_group_connect(
        accel_group_,
        ui::GetGdkKeyCodeForAccelerator(*page_action_keybinding_),
        ui::GetGdkModifierForAccelerator(*page_action_keybinding_),
        GtkAccelFlags(0),
        g_cclosure_new(G_CALLBACK(OnGtkAccelerator), this, NULL));

    // Since we've added an accelerator, we'll need to unregister it before
    // the window is closed, so we listen for the window being closed.
    registrar_.Add(this,
                   chrome::NOTIFICATION_WINDOW_CLOSED,
                   content::Source<GtkWindow>(window_));
  }
}

void LocationBarViewGtk::PageActionViewGtk::DisconnectPageActionAccelerator() {
  if (accel_group_) {
    if (page_action_keybinding_.get()) {
      gtk_accel_group_disconnect_key(
          accel_group_,
          ui::GetGdkKeyCodeForAccelerator(*page_action_keybinding_),
          ui::GetGdkModifierForAccelerator(*page_action_keybinding_));
    }
    gtk_window_remove_accel_group(window_, accel_group_);
    g_object_unref(accel_group_);
    accel_group_ = NULL;
    page_action_keybinding_.reset(NULL);
  }
}

gboolean LocationBarViewGtk::PageActionViewGtk::OnButtonPressed(
    GtkWidget* sender,
    GdkEventButton* event) {
  // Double and triple-clicks generate both a GDK_BUTTON_PRESS and a
  // GDK_[23]BUTTON_PRESS event. We don't want to double-trigger by acting on
  // both.
  if (event->type != GDK_BUTTON_PRESS)
    return TRUE;

  WebContents* web_contents = owner_->GetWebContents();
  if (!web_contents)
    return TRUE;

  ExtensionService* extension_service =
      owner_->profile()->GetExtensionService();
  if (!extension_service)
    return TRUE;

  const Extension* extension =
      extension_service->extensions()->GetByID(page_action()->extension_id());
  if (!extension)
    return TRUE;

  LocationBarController* controller =
      extensions::TabHelper::FromWebContents(web_contents)->
          location_bar_controller();

  switch (controller->OnClicked(extension->id(), event->button)) {
    case LocationBarController::ACTION_NONE:
      break;

    case LocationBarController::ACTION_SHOW_POPUP:
      ExtensionPopupGtk::Show(
          page_action_->GetPopupUrl(current_tab_id_),
          owner_->browser_,
          event_box_.get(),
          ExtensionPopupGtk::SHOW);
      break;

    case LocationBarController::ACTION_SHOW_CONTEXT_MENU:
      context_menu_model_ =
          new ExtensionContextMenuModel(extension, owner_->browser_, this);
      context_menu_.reset(
          new MenuGtk(NULL, context_menu_model_.get()));
      context_menu_->PopupForWidget(sender, event->button, event->time);
      break;
  }

  return TRUE;
}

gboolean LocationBarViewGtk::PageActionViewGtk::OnExposeEvent(
    GtkWidget* widget,
    GdkEventExpose* event) {
  TRACE_EVENT0("ui::gtk", "LocationBarViewGtk::PageActionViewGtk::OnExpose");
  WebContents* contents = owner_->GetWebContents();
  if (!contents)
    return FALSE;

  int tab_id = extensions::ExtensionTabUtil::GetTabId(contents);
  if (tab_id < 0)
    return FALSE;

  std::string badge_text = page_action_->GetBadgeText(tab_id);
  if (badge_text.empty())
    return FALSE;

  gfx::CanvasSkiaPaint canvas(event, false);
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  page_action_->PaintBadge(&canvas, gfx::Rect(allocation), tab_id);
  return FALSE;
}

void LocationBarViewGtk::PageActionViewGtk::OnRealize(GtkWidget* widget) {
  ConnectPageActionAccelerator();
}

// static
gboolean LocationBarViewGtk::PageActionViewGtk::OnGtkAccelerator(
    GtkAccelGroup* accel_group,
    GObject* acceleratable,
    guint keyval,
    GdkModifierType modifier,
    void* user_data) {
  PageActionViewGtk* view = static_cast<PageActionViewGtk*>(user_data);
  if (!gtk_widget_get_visible(view->widget()))
    return FALSE;

  GdkEventButton event = {};
  event.type = GDK_BUTTON_PRESS;
  event.button = 1;
  return view->OnButtonPressed(view->widget(), &event);
}

/* [<][>][^][v][top][bottom][index][help] */