root/chrome/browser/ui/views/frame/global_menu_bar_x11.cc

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

DEFINITIONS

This source file includes following definitions.
  1. EnsureMethodsLoaded
  2. weak_ptr_factory_
  3. GetPathForWindow
  4. BuildSeparator
  5. BuildMenuItem
  6. InitServer
  7. Disable
  8. BuildStaticMenu
  9. RegisterAccelerator
  10. HistoryItemForTab
  11. AddHistoryItemToMenu
  12. GetTopSitesData
  13. OnTopSitesReceived
  14. OnBookmarkBarVisibilityChanged
  15. GetIndexOfMenuItemWithTag
  16. ClearMenuSection
  17. DeleteHistoryItem
  18. EnabledStateChangedForCommand
  19. Observe
  20. TabRestoreServiceChanged
  21. TabRestoreServiceDestroyed
  22. OnWindowMapped
  23. OnWindowUnmapped
  24. OnItemActivated
  25. OnHistoryItemActivated
  26. OnHistoryMenuAboutToShow

// 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/chrome_notification_types.h"
#include "chrome/browser/history/top_sites.h"
#include "chrome/browser/profiles/profile.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_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 "content/public/browser/notification_source.h"
#include "grit/generated_resources.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";

// 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;

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 }
};

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),
      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);
  }
}

// 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);
  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(
      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_));
  }

  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(prefs::kShowBookmarkBar));
  }
}

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::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,
        content::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_);
    }
  }
}

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