// Copyright 2013 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/views/frame/global_menu_bar_x11.h"

#include <dlfcn.h>
#include <glib-object.h>

#include "base/debug/leak_annotations.h"
#include "base/logging.h"
#include "base/prefs/pref_service.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/history/top_sites.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_info_cache.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/tab_restore_service.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tab_restore_service_delegate.h"
#include "chrome/browser/ui/views/frame/browser_desktop_window_tree_host_x11.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/notification_source.h"
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/gfx/text_elider.h"

// libdbusmenu-glib types
typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)();
typedef bool (*dbusmenu_menuitem_child_add_position_func)(
    DbusmenuMenuitem* parent,
    DbusmenuMenuitem* child,
    unsigned int position);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)(
    DbusmenuMenuitem* parent,
    DbusmenuMenuitem* child);
typedef bool (*dbusmenu_menuitem_child_delete_func)(
    DbusmenuMenuitem* parent,
    DbusmenuMenuitem* child);
typedef GList* (*dbusmenu_menuitem_get_children_func)(
    DbusmenuMenuitem* item);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)(
    DbusmenuMenuitem* item,
    const char* property,
    const char* value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)(
    DbusmenuMenuitem* item,
    const char* property,
    GVariant* value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)(
    DbusmenuMenuitem* item,
    const char* property,
    bool value);
typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)(
    DbusmenuMenuitem* item,
    const char* property,
    int value);

typedef struct _DbusmenuServer      DbusmenuServer;
typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object);
typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self,
                                              DbusmenuMenuitem* root);

// A line in the static menu definitions.
struct GlobalMenuBarCommand {
  int str_id;
  int command;
  int tag;
};

namespace {

// Retrieved functions from libdbusmenu-glib.

// DbusmenuMenuItem methods:
dbusmenu_menuitem_new_func menuitem_new = NULL;
dbusmenu_menuitem_get_children_func menuitem_get_children = NULL;
dbusmenu_menuitem_child_add_position_func menuitem_child_add_position = NULL;
dbusmenu_menuitem_child_append_func menuitem_child_append = NULL;
dbusmenu_menuitem_child_delete_func menuitem_child_delete = NULL;
dbusmenu_menuitem_property_set_func menuitem_property_set = NULL;
dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant =
    NULL;
dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL;
dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL;

// DbusmenuServer methods:
dbusmenu_server_new_func server_new = NULL;
dbusmenu_server_set_root_func server_set_root = NULL;

// Properties that we set on menu items:
const char kPropertyEnabled[] = "enabled";
const char kPropertyLabel[] = "label";
const char kPropertyShortcut[] = "shortcut";
const char kPropertyType[] = "type";
const char kPropertyToggleType[] = "toggle-type";
const char kPropertyToggleState[] = "toggle-state";
const char kPropertyVisible[] = "visible";

const char kTypeCheckmark[] = "checkmark";
const char kTypeSeparator[] = "separator";

// Data set on GObjectgs.
const char kTypeTag[] = "type-tag";
const char kHistoryItem[] = "history-item";
const char kProfileId[] = "profile-id";

// The maximum number of most visited items to display.
const unsigned int kMostVisitedCount = 8;

// The number of recently closed items to get.
const unsigned int kRecentlyClosedCount = 8;

// Menus more than this many chars long will get trimmed.
const int kMaximumMenuWidthInChars = 50;

// Constants used in menu definitions.
const int MENU_SEPARATOR =-1;
const int MENU_END = -2;
const int MENU_DISABLED_ID = -3;

// These tag values are used to refer to menu items.
const int TAG_MOST_VISITED = 1;
const int TAG_RECENTLY_CLOSED = 2;
const int TAG_MOST_VISITED_HEADER = 3;
const int TAG_RECENTLY_CLOSED_HEADER = 4;
const int TAG_PROFILES = 5;

GlobalMenuBarCommand file_menu[] = {
  { IDS_NEW_TAB, IDC_NEW_TAB },
  { IDS_NEW_WINDOW, IDC_NEW_WINDOW },
  { IDS_NEW_INCOGNITO_WINDOW, IDC_NEW_INCOGNITO_WINDOW },
  { IDS_REOPEN_CLOSED_TABS_LINUX, IDC_RESTORE_TAB },
  { IDS_OPEN_FILE_LINUX, IDC_OPEN_FILE },
  { IDS_OPEN_LOCATION_LINUX, IDC_FOCUS_LOCATION },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_CREATE_SHORTCUTS, IDC_CREATE_SHORTCUTS },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_CLOSE_WINDOW_LINUX, IDC_CLOSE_WINDOW },
  { IDS_CLOSE_TAB_LINUX, IDC_CLOSE_TAB },
  { IDS_SAVE_PAGE, IDC_SAVE_PAGE },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_PRINT, IDC_PRINT },

  { MENU_END, MENU_END }
};

GlobalMenuBarCommand edit_menu[] = {
  { IDS_CUT, IDC_CUT },
  { IDS_COPY, IDC_COPY },
  { IDS_PASTE, IDC_PASTE },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_FIND, IDC_FIND },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_PREFERENCES, IDC_OPTIONS },

  { MENU_END, MENU_END }
};

GlobalMenuBarCommand view_menu[] = {
  { IDS_SHOW_BOOKMARK_BAR, IDC_SHOW_BOOKMARK_BAR },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_STOP_MENU_LINUX, IDC_STOP },
  { IDS_RELOAD_MENU_LINUX, IDC_RELOAD },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_FULLSCREEN, IDC_FULLSCREEN },
  { IDS_TEXT_DEFAULT_LINUX, IDC_ZOOM_NORMAL },
  { IDS_TEXT_BIGGER_LINUX, IDC_ZOOM_PLUS },
  { IDS_TEXT_SMALLER_LINUX, IDC_ZOOM_MINUS },

  { MENU_END, MENU_END }
};

GlobalMenuBarCommand history_menu[] = {
  { IDS_HISTORY_HOME_LINUX, IDC_HOME },
  { IDS_HISTORY_BACK_LINUX, IDC_BACK },
  { IDS_HISTORY_FORWARD_LINUX, IDC_FORWARD },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_HISTORY_VISITED_LINUX, MENU_DISABLED_ID, TAG_MOST_VISITED_HEADER },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_HISTORY_CLOSED_LINUX, MENU_DISABLED_ID, TAG_RECENTLY_CLOSED_HEADER },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_SHOWFULLHISTORY_LINK, IDC_SHOW_HISTORY },

  { MENU_END, MENU_END }
};

GlobalMenuBarCommand tools_menu[] = {
  { IDS_SHOW_DOWNLOADS, IDC_SHOW_DOWNLOADS },
  { IDS_SHOW_HISTORY, IDC_SHOW_HISTORY },
  { IDS_SHOW_EXTENSIONS, IDC_MANAGE_EXTENSIONS },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_TASK_MANAGER, IDC_TASK_MANAGER },
  { IDS_CLEAR_BROWSING_DATA, IDC_CLEAR_BROWSING_DATA },

  { MENU_SEPARATOR, MENU_SEPARATOR },

  { IDS_VIEW_SOURCE, IDC_VIEW_SOURCE },
  { IDS_DEV_TOOLS, IDC_DEV_TOOLS },
  { IDS_DEV_TOOLS_CONSOLE, IDC_DEV_TOOLS_CONSOLE },
  { IDS_DEV_TOOLS_DEVICES, IDC_DEV_TOOLS_DEVICES },

  { MENU_END, MENU_END }
};

GlobalMenuBarCommand help_menu[] = {
#if defined(GOOGLE_CHROME_BUILD)
  { IDS_FEEDBACK, IDC_FEEDBACK },
#endif
  { IDS_HELP_PAGE , IDC_HELP_PAGE_VIA_MENU },
  { MENU_END, MENU_END }
};

GlobalMenuBarCommand profiles_menu[] = {
  { MENU_SEPARATOR, MENU_SEPARATOR },
  { MENU_END, MENU_END }
};

void EnsureMethodsLoaded() {
  static bool attempted_load = false;
  if (attempted_load)
    return;
  attempted_load = true;

  void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY);
  if (!dbusmenu_lib)
    dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY);
  if (!dbusmenu_lib)
    return;

  // DbusmenuMenuItem methods.
  menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>(
      dlsym(dbusmenu_lib, "dbusmenu_menuitem_new"));
  menuitem_child_add_position =
      reinterpret_cast<dbusmenu_menuitem_child_add_position_func>(
          dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_add_position"));
  menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>(
      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append"));
  menuitem_child_delete = reinterpret_cast<dbusmenu_menuitem_child_delete_func>(
      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_delete"));
  menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>(
      dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children"));
  menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>(
      dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set"));
  menuitem_property_set_variant =
      reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>(
          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant"));
  menuitem_property_set_bool =
      reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>(
          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool"));
  menuitem_property_set_int =
      reinterpret_cast<dbusmenu_menuitem_property_set_int_func>(
          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int"));

  // DbusmenuServer methods.
  server_new = reinterpret_cast<dbusmenu_server_new_func>(
      dlsym(dbusmenu_lib, "dbusmenu_server_new"));
  server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>(
      dlsym(dbusmenu_lib, "dbusmenu_server_set_root"));
}

}  // namespace

struct GlobalMenuBarX11::HistoryItem {
  HistoryItem() : session_id(0) {}

  // The title for the menu item.
  base::string16 title;
  // The URL that will be navigated to if the user selects this item.
  GURL url;

  // This ID is unique for a browser session and can be passed to the
  // TabRestoreService to re-open the closed window or tab that this
  // references. A non-0 session ID indicates that this is an entry can be
  // restored that way. Otherwise, the URL will be used to open the item and
  // this ID will be 0.
  SessionID::id_type session_id;

  // If the HistoryItem is a window, this will be the vector of tabs. Note
  // that this is a list of weak references. The |menu_item_map_| is the owner
  // of all items. If it is not a window, then the entry is a single page and
  // the vector will be empty.
  std::vector<HistoryItem*> tabs;

 private:
  DISALLOW_COPY_AND_ASSIGN(HistoryItem);
};

GlobalMenuBarX11::GlobalMenuBarX11(BrowserView* browser_view,
                                   BrowserDesktopWindowTreeHostX11* host)
    : browser_(browser_view->browser()),
      profile_(browser_->profile()),
      browser_view_(browser_view),
      host_(host),
      server_(NULL),
      root_item_(NULL),
      history_menu_(NULL),
      profiles_menu_(NULL),
      top_sites_(NULL),
      tab_restore_service_(NULL),
      weak_ptr_factory_(this) {
  EnsureMethodsLoaded();

  if (server_new)
    host_->AddObserver(this);
}

GlobalMenuBarX11::~GlobalMenuBarX11() {
  if (server_) {
    Disable();

    if (tab_restore_service_)
      tab_restore_service_->RemoveObserver(this);

    g_object_unref(server_);
    host_->RemoveObserver(this);
  }
  BrowserList::RemoveObserver(this);
}

// static
std::string GlobalMenuBarX11::GetPathForWindow(unsigned long xid) {
  return base::StringPrintf("/com/canonical/menu/%lX", xid);
}

DbusmenuMenuitem* GlobalMenuBarX11::BuildSeparator() {
  DbusmenuMenuitem* item = menuitem_new();
  menuitem_property_set(item, kPropertyType, kTypeSeparator);
  menuitem_property_set_bool(item, kPropertyVisible, true);
  return item;
}

DbusmenuMenuitem* GlobalMenuBarX11::BuildMenuItem(
    const std::string& label,
    int tag_id) {
  DbusmenuMenuitem* item = menuitem_new();
  menuitem_property_set(item, kPropertyLabel, label.c_str());
  menuitem_property_set_bool(item, kPropertyVisible, true);

  if (tag_id)
    g_object_set_data(G_OBJECT(item), kTypeTag, GINT_TO_POINTER(tag_id));

  return item;
}

void GlobalMenuBarX11::InitServer(unsigned long xid) {
  std::string path = GetPathForWindow(xid);
  {
    ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087
    server_ = server_new(path.c_str());
  }

  root_item_ = menuitem_new();
  menuitem_property_set(root_item_, kPropertyLabel, "Root");
  menuitem_property_set_bool(root_item_, kPropertyVisible, true);

  // First build static menu content.
  BuildStaticMenu(root_item_, IDS_FILE_MENU_LINUX, file_menu);
  BuildStaticMenu(root_item_, IDS_EDIT_MENU_LINUX, edit_menu);
  BuildStaticMenu(root_item_, IDS_VIEW_MENU_LINUX, view_menu);
  history_menu_ = BuildStaticMenu(
      root_item_, IDS_HISTORY_MENU_LINUX, history_menu);
  BuildStaticMenu(root_item_, IDS_TOOLS_MENU_LINUX, tools_menu);
  profiles_menu_ = BuildStaticMenu(
      root_item_, IDS_PROFILES_OPTIONS_GROUP_NAME, profiles_menu);
  BuildStaticMenu(root_item_, IDS_HELP_MENU_LINUX, help_menu);

  // We have to connect to |history_menu_item|'s "activate" signal instead of
  // |history_menu|'s "show" signal because we are not supposed to modify the
  // menu during "show"
  g_signal_connect(history_menu_, "about-to-show",
                   G_CALLBACK(OnHistoryMenuAboutToShowThunk), this);

  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
       it != id_to_menu_item_.end(); ++it) {
    menuitem_property_set_bool(it->second, kPropertyEnabled,
                               chrome::IsCommandEnabled(browser_, it->first));

    ui::Accelerator accelerator;
    if (browser_view_->GetAccelerator(it->first, &accelerator))
      RegisterAccelerator(it->second, accelerator);

    chrome::AddCommandObserver(browser_, it->first, this);
  }

  pref_change_registrar_.Init(browser_->profile()->GetPrefs());
  pref_change_registrar_.Add(
      bookmarks::prefs::kShowBookmarkBar,
      base::Bind(&GlobalMenuBarX11::OnBookmarkBarVisibilityChanged,
                 base::Unretained(this)));
  OnBookmarkBarVisibilityChanged();

  top_sites_ = profile_->GetTopSites();
  if (top_sites_) {
    GetTopSitesData();

    // Register for notification when TopSites changes so that we can update
    // ourself.
    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
                   content::Source<history::TopSites>(top_sites_));
  }

  ProfileManager* profile_manager = g_browser_process->profile_manager();
  DCHECK(profile_manager);
  avatar_menu_.reset(new AvatarMenu(
      &profile_manager->GetProfileInfoCache(), this, NULL));
  avatar_menu_->RebuildMenu();
  BrowserList::AddObserver(this);

  RebuildProfilesMenu();

  server_set_root(server_, root_item_);
}

void GlobalMenuBarX11::Disable() {
  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
       it != id_to_menu_item_.end(); ++it) {
    chrome::RemoveCommandObserver(browser_, it->first, this);
  }
  id_to_menu_item_.clear();

  pref_change_registrar_.RemoveAll();
}

DbusmenuMenuitem* GlobalMenuBarX11::BuildStaticMenu(
    DbusmenuMenuitem* parent,
    int menu_str_id,
    GlobalMenuBarCommand* commands) {
  DbusmenuMenuitem* top = menuitem_new();
  menuitem_property_set(
      top, kPropertyLabel,
      ui::RemoveWindowsStyleAccelerators(
          l10n_util::GetStringUTF8(menu_str_id)).c_str());
  menuitem_property_set_bool(top, kPropertyVisible, true);

  for (int i = 0; commands[i].str_id != MENU_END; ++i) {
    DbusmenuMenuitem* menu_item = NULL;
    int command_id = commands[i].command;
    if (commands[i].str_id == MENU_SEPARATOR) {
      menu_item = BuildSeparator();
    } else {
      std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(commands[i].str_id));

      menu_item = BuildMenuItem(label, commands[i].tag);

      if (command_id == MENU_DISABLED_ID) {
        menuitem_property_set_bool(menu_item, kPropertyEnabled, false);
      } else {
        if (command_id == IDC_SHOW_BOOKMARK_BAR)
          menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark);

        id_to_menu_item_.insert(std::make_pair(command_id, menu_item));
        g_object_set_data(G_OBJECT(menu_item), "command-id",
                          GINT_TO_POINTER(command_id));
        g_signal_connect(menu_item, "item-activated",
                         G_CALLBACK(OnItemActivatedThunk), this);
      }
    }

    menuitem_child_append(top, menu_item);
    g_object_unref(menu_item);
  }

  menuitem_child_append(parent, top);
  g_object_unref(top);
  return top;
}

void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item,
                                           const ui::Accelerator& accelerator) {
  // A translation of libdbusmenu-gtk's menuitem_property_set_shortcut()
  // translated from GDK types to ui::Accelerator types.
  GVariantBuilder builder;
  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);

  if (accelerator.IsCtrlDown())
    g_variant_builder_add(&builder, "s", "Control");
  if (accelerator.IsAltDown())
    g_variant_builder_add(&builder, "s", "Alt");
  if (accelerator.IsShiftDown())
    g_variant_builder_add(&builder, "s", "Shift");

  char* name = XKeysymToString(XKeysymForWindowsKeyCode(
      accelerator.key_code(), false));
  if (!name) {
    NOTIMPLEMENTED();
    return;
  }
  g_variant_builder_add(&builder, "s", name);

  GVariant* inside_array = g_variant_builder_end(&builder);
  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
  g_variant_builder_add_value(&builder, inside_array);
  GVariant* outside_array = g_variant_builder_end(&builder);

  menuitem_property_set_variant(item, kPropertyShortcut, outside_array);
}

GlobalMenuBarX11::HistoryItem* GlobalMenuBarX11::HistoryItemForTab(
    const TabRestoreService::Tab& entry) {
  const sessions::SerializedNavigationEntry& current_navigation =
      entry.navigations.at(entry.current_navigation_index);
  HistoryItem* item = new HistoryItem();
  item->title = current_navigation.title();
  item->url = current_navigation.virtual_url();
  item->session_id = entry.id;

  return item;
}

void GlobalMenuBarX11::AddHistoryItemToMenu(HistoryItem* item,
                                            DbusmenuMenuitem* menu,
                                            int tag,
                                            int index) {
  base::string16 title = item->title;
  std::string url_string = item->url.possibly_invalid_spec();

  if (title.empty())
    title = base::UTF8ToUTF16(url_string);
  gfx::ElideString(title, kMaximumMenuWidthInChars, &title);

  DbusmenuMenuitem* menu_item = BuildMenuItem(base::UTF16ToUTF8(title), tag);
  g_signal_connect(menu_item, "item-activated",
                   G_CALLBACK(OnHistoryItemActivatedThunk), this);

  g_object_set_data_full(G_OBJECT(menu_item), kHistoryItem, item,
                         DeleteHistoryItem);
  menuitem_child_add_position(menu, menu_item, index);
  g_object_unref(menu_item);
}

void GlobalMenuBarX11::GetTopSitesData() {
  DCHECK(top_sites_);

  top_sites_->GetMostVisitedURLs(
      base::Bind(&GlobalMenuBarX11::OnTopSitesReceived,
                 weak_ptr_factory_.GetWeakPtr()), false);
}

void GlobalMenuBarX11::OnTopSitesReceived(
    const history::MostVisitedURLList& visited_list) {
  ClearMenuSection(history_menu_, TAG_MOST_VISITED);

  int index = GetIndexOfMenuItemWithTag(history_menu_,
                                        TAG_MOST_VISITED_HEADER) + 1;

  for (size_t i = 0; i < visited_list.size() && i < kMostVisitedCount; ++i) {
    const history::MostVisitedURL& visited = visited_list[i];
    if (visited.url.spec().empty())
      break;  // This is the signal that there are no more real visited sites.

    HistoryItem* item = new HistoryItem();
    item->title = visited.title;
    item->url = visited.url;

    AddHistoryItemToMenu(item,
                         history_menu_,
                         TAG_MOST_VISITED,
                         index++);
  }
}

void GlobalMenuBarX11::OnBookmarkBarVisibilityChanged() {
  CommandIDMenuItemMap::iterator it =
      id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR);
  if (it != id_to_menu_item_.end()) {
    PrefService* prefs = browser_->profile()->GetPrefs();
    // Note: Unlike the GTK version, we don't appear to need to do tricks where
    // we block activation while setting the toggle.
    menuitem_property_set_int(
        it->second,
        kPropertyToggleState,
        prefs->GetBoolean(bookmarks::prefs::kShowBookmarkBar));
  }
}

void GlobalMenuBarX11::RebuildProfilesMenu() {
  ClearMenuSection(profiles_menu_, TAG_PROFILES);

  // Don't call avatar_menu_->GetActiveProfileIndex() as the as the index might
  // be incorrect if RebuildProfilesMenu() is called while we deleting the
  // active profile and closing all its browser windows.
  int active_profile_index = -1;

  for (size_t i = 0; i < avatar_menu_->GetNumberOfItems(); ++i) {
    const AvatarMenu::Item& item = avatar_menu_->GetItemAt(i);
    base::string16 title = item.name;
    gfx::ElideString(title, kMaximumMenuWidthInChars, &title);

    DbusmenuMenuitem* menu_item = BuildMenuItem(
        base::UTF16ToUTF8(title), TAG_PROFILES);
    g_object_set_data(G_OBJECT(menu_item), kProfileId, GINT_TO_POINTER(i));
    g_signal_connect(menu_item, "item-activated",
                     G_CALLBACK(OnProfileItemActivatedThunk), this);
    menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark);
    menuitem_property_set_int(menu_item, kPropertyToggleState, item.active);

    if (item.active)
      active_profile_index = i;

    menuitem_child_add_position(profiles_menu_, menu_item, i);
    g_object_unref(menu_item);
  }

  // There is a separator between the list of profiles and the possible actions.
  int index = avatar_menu_->GetNumberOfItems() + 1;

  DbusmenuMenuitem* edit_profile_item = BuildMenuItem(
      l10n_util::GetStringUTF8(IDS_PROFILES_MANAGE_BUTTON_LABEL), TAG_PROFILES);
  DbusmenuMenuitem* create_profile_item = BuildMenuItem(
      l10n_util::GetStringUTF8(IDS_PROFILES_CREATE_BUTTON_LABEL),
      TAG_PROFILES);

  // There is no active profile in Guest mode, in which case the action buttons
  // should be disabled.
  if (active_profile_index >= 0) {
    g_object_set_data(G_OBJECT(edit_profile_item), kProfileId,
                      GINT_TO_POINTER(active_profile_index));
    g_signal_connect(edit_profile_item, "item-activated",
                     G_CALLBACK(OnEditProfileItemActivatedThunk), this);
    g_signal_connect(create_profile_item, "item-activated",
                     G_CALLBACK(OnCreateProfileItemActivatedThunk), this);
  } else {
    menuitem_property_set_bool(edit_profile_item, kPropertyEnabled, false);
    menuitem_property_set_bool(create_profile_item, kPropertyEnabled, false);
  }

  menuitem_child_add_position(profiles_menu_, edit_profile_item, index++);
  menuitem_child_add_position(profiles_menu_, create_profile_item, index);
  g_object_unref(edit_profile_item);
  g_object_unref(create_profile_item);
}

int GlobalMenuBarX11::GetIndexOfMenuItemWithTag(DbusmenuMenuitem* menu,
                                                int tag_id) {
  GList* childs = menuitem_get_children(menu);
  int i = 0;
  for (; childs != NULL; childs = childs->next, i++) {
    int tag =
        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
    if (tag == tag_id)
      return i;
  }

  NOTREACHED();
  return -1;
}

void GlobalMenuBarX11::ClearMenuSection(DbusmenuMenuitem* menu, int tag_id) {
  std::vector<DbusmenuMenuitem*> menuitems_to_delete;

  GList* childs = menuitem_get_children(menu);
  for (; childs != NULL; childs = childs->next) {
    DbusmenuMenuitem* current_item = reinterpret_cast<DbusmenuMenuitem*>(
        childs->data);
    ClearMenuSection(current_item, tag_id);

    int tag =
        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
    if (tag == tag_id)
      menuitems_to_delete.push_back(current_item);
  }

  for (std::vector<DbusmenuMenuitem*>::const_iterator it =
           menuitems_to_delete.begin(); it != menuitems_to_delete.end(); ++it) {
    menuitem_child_delete(menu, *it);
  }
}

// static
void GlobalMenuBarX11::DeleteHistoryItem(void* void_item) {
  HistoryItem* item =
      reinterpret_cast<GlobalMenuBarX11::HistoryItem*>(void_item);
  delete item;
}

void GlobalMenuBarX11::OnAvatarMenuChanged(AvatarMenu* avatar_menu) {
  RebuildProfilesMenu();
}

void GlobalMenuBarX11::OnBrowserSetLastActive(Browser* browser) {
  // Rebuild the avatar menu so that the items have the correct active state.
  avatar_menu_->RebuildMenu();
  avatar_menu_->ActiveBrowserChanged(browser);
  RebuildProfilesMenu();
}

void GlobalMenuBarX11::EnabledStateChangedForCommand(int id, bool enabled) {
  CommandIDMenuItemMap::iterator it = id_to_menu_item_.find(id);
  if (it != id_to_menu_item_.end())
    menuitem_property_set_bool(it->second, kPropertyEnabled, enabled);
}

void GlobalMenuBarX11::Observe(int type,
                               const content::NotificationSource& source,
                               const content::NotificationDetails& details) {
  if (type == chrome::NOTIFICATION_TOP_SITES_CHANGED) {
    GetTopSitesData();
  } else {
    NOTREACHED();
  }
}

void GlobalMenuBarX11::TabRestoreServiceChanged(TabRestoreService* service) {
  const TabRestoreService::Entries& entries = service->entries();

  ClearMenuSection(history_menu_, TAG_RECENTLY_CLOSED);

  // We'll get the index the "Recently Closed" header. (This can vary depending
  // on the number of "Most Visited" items.
  int index = GetIndexOfMenuItemWithTag(history_menu_,
                                        TAG_RECENTLY_CLOSED_HEADER) + 1;

  unsigned int added_count = 0;
  for (TabRestoreService::Entries::const_iterator it = entries.begin();
       it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
    TabRestoreService::Entry* entry = *it;

    if (entry->type == TabRestoreService::WINDOW) {
      TabRestoreService::Window* entry_win =
          static_cast<TabRestoreService::Window*>(entry);
      std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
      if (tabs.empty())
        continue;

      // Create the item for the parent/window.
      HistoryItem* item = new HistoryItem();
      item->session_id = entry_win->id;

      std::string title = tabs.size() == 1 ?
          l10n_util::GetStringUTF8(
              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
          l10n_util::GetStringFUTF8(
              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
              base::IntToString16(tabs.size()));
      DbusmenuMenuitem* parent_item = BuildMenuItem(
          title, TAG_RECENTLY_CLOSED);
      menuitem_child_add_position(history_menu_, parent_item, index++);
      g_object_unref(parent_item);

      // The mac version of this code allows the user to click on the parent
      // menu item to have the same effect as clicking the restore window
      // submenu item. GTK+ helpfully activates a menu item when it shows a
      // submenu so toss that feature out.
      DbusmenuMenuitem* restore_item = BuildMenuItem(
          l10n_util::GetStringUTF8(
              IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str(),
          TAG_RECENTLY_CLOSED);
      g_signal_connect(restore_item, "item-activated",
                       G_CALLBACK(OnHistoryItemActivatedThunk), this);
      g_object_set_data_full(G_OBJECT(restore_item), kHistoryItem, item,
                             DeleteHistoryItem);
      menuitem_child_append(parent_item, restore_item);
      g_object_unref(restore_item);

      DbusmenuMenuitem* separator = BuildSeparator();
      menuitem_child_append(parent_item, separator);
      g_object_unref(separator);

      // Loop over the window's tabs and add them to the submenu.
      int subindex = 2;
      std::vector<TabRestoreService::Tab>::const_iterator iter;
      for (iter = tabs.begin(); iter != tabs.end(); ++iter) {
        TabRestoreService::Tab tab = *iter;
        HistoryItem* tab_item = HistoryItemForTab(tab);
        item->tabs.push_back(tab_item);
        AddHistoryItemToMenu(tab_item,
                             parent_item,
                             TAG_RECENTLY_CLOSED,
                             subindex++);
      }

      ++added_count;
    } else if (entry->type == TabRestoreService::TAB) {
      TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
      HistoryItem* item = HistoryItemForTab(*tab);
      AddHistoryItemToMenu(item,
                           history_menu_,
                           TAG_RECENTLY_CLOSED,
                           index++);
      ++added_count;
    }
  }
}

void GlobalMenuBarX11::TabRestoreServiceDestroyed(
    TabRestoreService* service) {
  tab_restore_service_ = NULL;
}

void GlobalMenuBarX11::OnWindowMapped(unsigned long xid) {
  if (!server_)
    InitServer(xid);

  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid);
}

void GlobalMenuBarX11::OnWindowUnmapped(unsigned long xid) {
  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid);
}

void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item,
                                       unsigned int timestamp) {
  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "command-id"));
  chrome::ExecuteCommand(browser_, id);
}

void GlobalMenuBarX11::OnHistoryItemActivated(DbusmenuMenuitem* sender,
                                              unsigned int timestamp) {
  // Note: We don't have access to the event modifiers used to click the menu
  // item since that happens in a different process.
  HistoryItem* item = reinterpret_cast<HistoryItem*>(
      g_object_get_data(G_OBJECT(sender), kHistoryItem));

  // If this item can be restored using TabRestoreService, do so. Otherwise,
  // just load the URL.
  TabRestoreService* service =
      TabRestoreServiceFactory::GetForProfile(profile_);
  if (item->session_id && service) {
    service->RestoreEntryById(browser_->tab_restore_service_delegate(),
                              item->session_id, browser_->host_desktop_type(),
                              UNKNOWN);
  } else {
    DCHECK(item->url.is_valid());
    browser_->OpenURL(content::OpenURLParams(
        item->url,
        content::Referrer(),
        NEW_FOREGROUND_TAB,
        ui::PAGE_TRANSITION_AUTO_BOOKMARK,
        false));
  }
}

void GlobalMenuBarX11::OnHistoryMenuAboutToShow(DbusmenuMenuitem* item) {
  if (!tab_restore_service_) {
    tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_);
    if (tab_restore_service_) {
      tab_restore_service_->LoadTabsFromLastSession();
      tab_restore_service_->AddObserver(this);

      // If LoadTabsFromLastSession doesn't load tabs, it won't call
      // TabRestoreServiceChanged(). This ensures that all new windows after
      // the first one will have their menus populated correctly.
      TabRestoreServiceChanged(tab_restore_service_);
    }
  }
}

void GlobalMenuBarX11::OnProfileItemActivated(DbusmenuMenuitem* sender,
                                              unsigned int timestamp) {
  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sender), kProfileId));
  avatar_menu_->SwitchToProfile(id, false, ProfileMetrics::SWITCH_PROFILE_MENU);
}

void GlobalMenuBarX11::OnEditProfileItemActivated(DbusmenuMenuitem* sender,
                                                  unsigned int timestamp) {
  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(sender), kProfileId));
  avatar_menu_->EditProfile(id);
}

void GlobalMenuBarX11::OnCreateProfileItemActivated(DbusmenuMenuitem* sender,
                                                    unsigned int timestamp) {
  avatar_menu_->AddNewProfile(ProfileMetrics::ADD_NEW_USER_MENU);
}