global_menu_bar_x11.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/views/frame/global_menu_bar_x11.h"
6
7#include <dlfcn.h>
8#include <glib-object.h>
9
10#include "base/debug/leak_annotations.h"
11#include "base/logging.h"
12#include "base/prefs/pref_service.h"
13#include "base/stl_util.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/app/chrome_command_ids.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/history/top_sites.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/sessions/tab_restore_service.h"
22#include "chrome/browser/sessions/tab_restore_service_factory.h"
23#include "chrome/browser/ui/browser.h"
24#include "chrome/browser/ui/browser_commands.h"
25#include "chrome/browser/ui/browser_tab_restore_service_delegate.h"
26#include "chrome/browser/ui/views/frame/browser_desktop_root_window_host_x11.h"
27#include "chrome/browser/ui/views/frame/browser_view.h"
28#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
29#include "chrome/common/pref_names.h"
30#include "content/public/browser/notification_source.h"
31#include "grit/generated_resources.h"
32#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
33#include "ui/base/l10n/l10n_util.h"
34#include "ui/events/keycodes/keyboard_code_conversion_x.h"
35#include "ui/gfx/text_elider.h"
36
37// libdbusmenu-glib types
38typedef struct _DbusmenuMenuitem DbusmenuMenuitem;
39typedef DbusmenuMenuitem* (*dbusmenu_menuitem_new_func)();
40typedef bool (*dbusmenu_menuitem_child_add_position_func)(
41    DbusmenuMenuitem* parent,
42    DbusmenuMenuitem* child,
43    unsigned int position);
44typedef DbusmenuMenuitem* (*dbusmenu_menuitem_child_append_func)(
45    DbusmenuMenuitem* parent,
46    DbusmenuMenuitem* child);
47typedef bool (*dbusmenu_menuitem_child_delete_func)(
48    DbusmenuMenuitem* parent,
49    DbusmenuMenuitem* child);
50typedef GList* (*dbusmenu_menuitem_get_children_func)(
51    DbusmenuMenuitem* item);
52typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_func)(
53    DbusmenuMenuitem* item,
54    const char* property,
55    const char* value);
56typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_variant_func)(
57    DbusmenuMenuitem* item,
58    const char* property,
59    GVariant* value);
60typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_bool_func)(
61    DbusmenuMenuitem* item,
62    const char* property,
63    bool value);
64typedef DbusmenuMenuitem* (*dbusmenu_menuitem_property_set_int_func)(
65    DbusmenuMenuitem* item,
66    const char* property,
67    int value);
68
69typedef struct _DbusmenuServer      DbusmenuServer;
70typedef DbusmenuServer* (*dbusmenu_server_new_func)(const char* object);
71typedef void (*dbusmenu_server_set_root_func)(DbusmenuServer* self,
72                                              DbusmenuMenuitem* root);
73
74// A line in the static menu definitions.
75struct GlobalMenuBarCommand {
76  int str_id;
77  int command;
78  int tag;
79};
80
81namespace {
82
83// Retrieved functions from libdbusmenu-glib.
84
85// DbusmenuMenuItem methods:
86dbusmenu_menuitem_new_func menuitem_new = NULL;
87dbusmenu_menuitem_get_children_func menuitem_get_children = NULL;
88dbusmenu_menuitem_child_add_position_func menuitem_child_add_position = NULL;
89dbusmenu_menuitem_child_append_func menuitem_child_append = NULL;
90dbusmenu_menuitem_child_delete_func menuitem_child_delete = NULL;
91dbusmenu_menuitem_property_set_func menuitem_property_set = NULL;
92dbusmenu_menuitem_property_set_variant_func menuitem_property_set_variant =
93    NULL;
94dbusmenu_menuitem_property_set_bool_func menuitem_property_set_bool = NULL;
95dbusmenu_menuitem_property_set_int_func menuitem_property_set_int = NULL;
96
97// DbusmenuServer methods:
98dbusmenu_server_new_func server_new = NULL;
99dbusmenu_server_set_root_func server_set_root = NULL;
100
101// Properties that we set on menu items:
102const char kPropertyEnabled[] = "enabled";
103const char kPropertyLabel[] = "label";
104const char kPropertyShortcut[] = "shortcut";
105const char kPropertyType[] = "type";
106const char kPropertyToggleType[] = "toggle-type";
107const char kPropertyToggleState[] = "toggle-state";
108const char kPropertyVisible[] = "visible";
109
110const char kTypeCheckmark[] = "checkmark";
111const char kTypeSeparator[] = "separator";
112
113// Data set on GObjectgs.
114const char kTypeTag[] = "type-tag";
115const char kHistoryItem[] = "history-item";
116
117// The maximum number of most visited items to display.
118const unsigned int kMostVisitedCount = 8;
119
120// The number of recently closed items to get.
121const unsigned int kRecentlyClosedCount = 8;
122
123// Menus more than this many chars long will get trimmed.
124const int kMaximumMenuWidthInChars = 50;
125
126// Constants used in menu definitions.
127const int MENU_SEPARATOR =-1;
128const int MENU_END = -2;
129const int MENU_DISABLED_ID = -3;
130
131// These tag values are used to refer to menu items.
132const int TAG_MOST_VISITED = 1;
133const int TAG_RECENTLY_CLOSED = 2;
134const int TAG_MOST_VISITED_HEADER = 3;
135const int TAG_RECENTLY_CLOSED_HEADER = 4;
136
137GlobalMenuBarCommand file_menu[] = {
138  { IDS_NEW_TAB, IDC_NEW_TAB },
139  { IDS_NEW_WINDOW, IDC_NEW_WINDOW },
140  { IDS_NEW_INCOGNITO_WINDOW, IDC_NEW_INCOGNITO_WINDOW },
141  { IDS_REOPEN_CLOSED_TABS_LINUX, IDC_RESTORE_TAB },
142  { IDS_OPEN_FILE_LINUX, IDC_OPEN_FILE },
143  { IDS_OPEN_LOCATION_LINUX, IDC_FOCUS_LOCATION },
144
145  { MENU_SEPARATOR, MENU_SEPARATOR },
146
147  { IDS_CREATE_SHORTCUTS, IDC_CREATE_SHORTCUTS },
148
149  { MENU_SEPARATOR, MENU_SEPARATOR },
150
151  { IDS_CLOSE_WINDOW_LINUX, IDC_CLOSE_WINDOW },
152  { IDS_CLOSE_TAB_LINUX, IDC_CLOSE_TAB },
153  { IDS_SAVE_PAGE, IDC_SAVE_PAGE },
154
155  { MENU_SEPARATOR, MENU_SEPARATOR },
156
157  { IDS_PRINT, IDC_PRINT },
158
159  { MENU_END, MENU_END }
160};
161
162GlobalMenuBarCommand edit_menu[] = {
163  { IDS_CUT, IDC_CUT },
164  { IDS_COPY, IDC_COPY },
165  { IDS_PASTE, IDC_PASTE },
166
167  { MENU_SEPARATOR, MENU_SEPARATOR },
168
169  { IDS_FIND, IDC_FIND },
170
171  { MENU_SEPARATOR, MENU_SEPARATOR },
172
173  { IDS_PREFERENCES, IDC_OPTIONS },
174
175  { MENU_END, MENU_END }
176};
177
178GlobalMenuBarCommand view_menu[] = {
179  { IDS_SHOW_BOOKMARK_BAR, IDC_SHOW_BOOKMARK_BAR },
180
181  { MENU_SEPARATOR, MENU_SEPARATOR },
182
183  { IDS_STOP_MENU_LINUX, IDC_STOP },
184  { IDS_RELOAD_MENU_LINUX, IDC_RELOAD },
185
186  { MENU_SEPARATOR, MENU_SEPARATOR },
187
188  { IDS_FULLSCREEN, IDC_FULLSCREEN },
189  { IDS_TEXT_DEFAULT_LINUX, IDC_ZOOM_NORMAL },
190  { IDS_TEXT_BIGGER_LINUX, IDC_ZOOM_PLUS },
191  { IDS_TEXT_SMALLER_LINUX, IDC_ZOOM_MINUS },
192
193  { MENU_END, MENU_END }
194};
195
196GlobalMenuBarCommand history_menu[] = {
197  { IDS_HISTORY_HOME_LINUX, IDC_HOME },
198  { IDS_HISTORY_BACK_LINUX, IDC_BACK },
199  { IDS_HISTORY_FORWARD_LINUX, IDC_FORWARD },
200
201  { MENU_SEPARATOR, MENU_SEPARATOR },
202
203  { IDS_HISTORY_VISITED_LINUX, MENU_DISABLED_ID, TAG_MOST_VISITED_HEADER },
204
205  { MENU_SEPARATOR, MENU_SEPARATOR },
206
207  { IDS_HISTORY_CLOSED_LINUX, MENU_DISABLED_ID, TAG_RECENTLY_CLOSED_HEADER },
208
209  { MENU_SEPARATOR, MENU_SEPARATOR },
210
211  { IDS_SHOWFULLHISTORY_LINK, IDC_SHOW_HISTORY },
212
213  { MENU_END, MENU_END }
214};
215
216GlobalMenuBarCommand tools_menu[] = {
217  { IDS_SHOW_DOWNLOADS, IDC_SHOW_DOWNLOADS },
218  { IDS_SHOW_HISTORY, IDC_SHOW_HISTORY },
219  { IDS_SHOW_EXTENSIONS, IDC_MANAGE_EXTENSIONS },
220
221  { MENU_SEPARATOR, MENU_SEPARATOR },
222
223  { IDS_TASK_MANAGER, IDC_TASK_MANAGER },
224  { IDS_CLEAR_BROWSING_DATA, IDC_CLEAR_BROWSING_DATA },
225
226  { MENU_SEPARATOR, MENU_SEPARATOR },
227
228  { IDS_VIEW_SOURCE, IDC_VIEW_SOURCE },
229  { IDS_DEV_TOOLS, IDC_DEV_TOOLS },
230  { IDS_DEV_TOOLS_CONSOLE, IDC_DEV_TOOLS_CONSOLE },
231  { IDS_DEV_TOOLS_DEVICES, IDC_DEV_TOOLS_DEVICES },
232
233  { MENU_END, MENU_END }
234};
235
236GlobalMenuBarCommand help_menu[] = {
237#if defined(GOOGLE_CHROME_BUILD)
238  { IDS_FEEDBACK, IDC_FEEDBACK },
239#endif
240  { IDS_HELP_PAGE , IDC_HELP_PAGE_VIA_MENU },
241  { MENU_END, MENU_END }
242};
243
244void EnsureMethodsLoaded() {
245  static bool attempted_load = false;
246  if (attempted_load)
247    return;
248  attempted_load = true;
249
250  void* dbusmenu_lib = dlopen("libdbusmenu-glib.so", RTLD_LAZY);
251  if (!dbusmenu_lib)
252    dbusmenu_lib = dlopen("libdbusmenu-glib.so.4", RTLD_LAZY);
253  if (!dbusmenu_lib)
254    return;
255
256  // DbusmenuMenuItem methods.
257  menuitem_new = reinterpret_cast<dbusmenu_menuitem_new_func>(
258      dlsym(dbusmenu_lib, "dbusmenu_menuitem_new"));
259  menuitem_child_add_position =
260      reinterpret_cast<dbusmenu_menuitem_child_add_position_func>(
261          dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_add_position"));
262  menuitem_child_append = reinterpret_cast<dbusmenu_menuitem_child_append_func>(
263      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_append"));
264  menuitem_child_delete = reinterpret_cast<dbusmenu_menuitem_child_delete_func>(
265      dlsym(dbusmenu_lib, "dbusmenu_menuitem_child_delete"));
266  menuitem_get_children = reinterpret_cast<dbusmenu_menuitem_get_children_func>(
267      dlsym(dbusmenu_lib, "dbusmenu_menuitem_get_children"));
268  menuitem_property_set = reinterpret_cast<dbusmenu_menuitem_property_set_func>(
269      dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set"));
270  menuitem_property_set_variant =
271      reinterpret_cast<dbusmenu_menuitem_property_set_variant_func>(
272          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_variant"));
273  menuitem_property_set_bool =
274      reinterpret_cast<dbusmenu_menuitem_property_set_bool_func>(
275          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_bool"));
276  menuitem_property_set_int =
277      reinterpret_cast<dbusmenu_menuitem_property_set_int_func>(
278          dlsym(dbusmenu_lib, "dbusmenu_menuitem_property_set_int"));
279
280  // DbusmenuServer methods.
281  server_new = reinterpret_cast<dbusmenu_server_new_func>(
282      dlsym(dbusmenu_lib, "dbusmenu_server_new"));
283  server_set_root = reinterpret_cast<dbusmenu_server_set_root_func>(
284      dlsym(dbusmenu_lib, "dbusmenu_server_set_root"));
285}
286
287}  // namespace
288
289struct GlobalMenuBarX11::HistoryItem {
290  HistoryItem() : session_id(0) {}
291
292  // The title for the menu item.
293  string16 title;
294  // The URL that will be navigated to if the user selects this item.
295  GURL url;
296
297  // This ID is unique for a browser session and can be passed to the
298  // TabRestoreService to re-open the closed window or tab that this
299  // references. A non-0 session ID indicates that this is an entry can be
300  // restored that way. Otherwise, the URL will be used to open the item and
301  // this ID will be 0.
302  SessionID::id_type session_id;
303
304  // If the HistoryItem is a window, this will be the vector of tabs. Note
305  // that this is a list of weak references. The |menu_item_map_| is the owner
306  // of all items. If it is not a window, then the entry is a single page and
307  // the vector will be empty.
308  std::vector<HistoryItem*> tabs;
309
310 private:
311  DISALLOW_COPY_AND_ASSIGN(HistoryItem);
312};
313
314GlobalMenuBarX11::GlobalMenuBarX11(BrowserView* browser_view,
315                                   BrowserDesktopRootWindowHostX11* host)
316    : browser_(browser_view->browser()),
317      profile_(browser_->profile()),
318      browser_view_(browser_view),
319      host_(host),
320      server_(NULL),
321      root_item_(NULL),
322      history_menu_(NULL),
323      top_sites_(NULL),
324      tab_restore_service_(NULL),
325      weak_ptr_factory_(this) {
326  EnsureMethodsLoaded();
327
328  if (server_new)
329    host_->AddObserver(this);
330}
331
332GlobalMenuBarX11::~GlobalMenuBarX11() {
333  if (server_) {
334    Disable();
335
336    if (tab_restore_service_)
337      tab_restore_service_->RemoveObserver(this);
338
339    g_object_unref(server_);
340    host_->RemoveObserver(this);
341  }
342}
343
344// static
345std::string GlobalMenuBarX11::GetPathForWindow(unsigned long xid) {
346  return base::StringPrintf("/com/canonical/menu/%lX", xid);
347}
348
349DbusmenuMenuitem* GlobalMenuBarX11::BuildSeparator() {
350  DbusmenuMenuitem* item = menuitem_new();
351  menuitem_property_set(item, kPropertyType, kTypeSeparator);
352  menuitem_property_set_bool(item, kPropertyVisible, true);
353  return item;
354}
355
356DbusmenuMenuitem* GlobalMenuBarX11::BuildMenuItem(
357    const std::string& label,
358    int tag_id) {
359  DbusmenuMenuitem* item = menuitem_new();
360  menuitem_property_set(item, kPropertyLabel, label.c_str());
361  menuitem_property_set_bool(item, kPropertyVisible, true);
362
363  if (tag_id)
364    g_object_set_data(G_OBJECT(item), kTypeTag, GINT_TO_POINTER(tag_id));
365
366  return item;
367}
368
369void GlobalMenuBarX11::InitServer(unsigned long xid) {
370  std::string path = GetPathForWindow(xid);
371  {
372    ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087
373    server_ = server_new(path.c_str());
374  }
375
376  root_item_ = menuitem_new();
377  menuitem_property_set(root_item_, kPropertyLabel, "Root");
378  menuitem_property_set_bool(root_item_, kPropertyVisible, true);
379
380  // First build static menu content.
381  BuildStaticMenu(root_item_, IDS_FILE_MENU_LINUX, file_menu);
382  BuildStaticMenu(root_item_, IDS_EDIT_MENU_LINUX, edit_menu);
383  BuildStaticMenu(root_item_, IDS_VIEW_MENU_LINUX, view_menu);
384  history_menu_ = BuildStaticMenu(
385      root_item_, IDS_HISTORY_MENU_LINUX, history_menu);
386  BuildStaticMenu(root_item_, IDS_TOOLS_MENU_LINUX, tools_menu);
387  BuildStaticMenu(root_item_, IDS_HELP_MENU_LINUX, help_menu);
388
389  // We have to connect to |history_menu_item|'s "activate" signal instead of
390  // |history_menu|'s "show" signal because we are not supposed to modify the
391  // menu during "show"
392  g_signal_connect(history_menu_, "about-to-show",
393                   G_CALLBACK(OnHistoryMenuAboutToShowThunk), this);
394
395  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
396       it != id_to_menu_item_.end(); ++it) {
397    menuitem_property_set_bool(it->second, kPropertyEnabled,
398                               chrome::IsCommandEnabled(browser_, it->first));
399
400    ui::Accelerator accelerator;
401    if (browser_view_->GetAccelerator(it->first, &accelerator))
402      RegisterAccelerator(it->second, accelerator);
403
404    chrome::AddCommandObserver(browser_, it->first, this);
405  }
406
407  pref_change_registrar_.Init(browser_->profile()->GetPrefs());
408  pref_change_registrar_.Add(
409      prefs::kShowBookmarkBar,
410      base::Bind(&GlobalMenuBarX11::OnBookmarkBarVisibilityChanged,
411                 base::Unretained(this)));
412  OnBookmarkBarVisibilityChanged();
413
414  top_sites_ = profile_->GetTopSites();
415  if (top_sites_) {
416    GetTopSitesData();
417
418    // Register for notification when TopSites changes so that we can update
419    // ourself.
420    registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED,
421                   content::Source<history::TopSites>(top_sites_));
422  }
423
424  server_set_root(server_, root_item_);
425}
426
427void GlobalMenuBarX11::Disable() {
428  for (CommandIDMenuItemMap::const_iterator it = id_to_menu_item_.begin();
429       it != id_to_menu_item_.end(); ++it) {
430    chrome::RemoveCommandObserver(browser_, it->first, this);
431  }
432  id_to_menu_item_.clear();
433
434  pref_change_registrar_.RemoveAll();
435}
436
437DbusmenuMenuitem* GlobalMenuBarX11::BuildStaticMenu(
438    DbusmenuMenuitem* parent,
439    int menu_str_id,
440    GlobalMenuBarCommand* commands) {
441  DbusmenuMenuitem* top = menuitem_new();
442  menuitem_property_set(
443      top, kPropertyLabel,
444      ui::RemoveWindowsStyleAccelerators(
445          l10n_util::GetStringUTF8(menu_str_id)).c_str());
446  menuitem_property_set_bool(top, kPropertyVisible, true);
447
448  for (int i = 0; commands[i].str_id != MENU_END; ++i) {
449    DbusmenuMenuitem* menu_item = NULL;
450    int command_id = commands[i].command;
451    if (commands[i].str_id == MENU_SEPARATOR) {
452      menu_item = BuildSeparator();
453    } else {
454      std::string label = ui::ConvertAcceleratorsFromWindowsStyle(
455          l10n_util::GetStringUTF8(commands[i].str_id));
456
457      menu_item = BuildMenuItem(label, commands[i].tag);
458
459      if (command_id == MENU_DISABLED_ID) {
460        menuitem_property_set_bool(menu_item, kPropertyEnabled, false);
461      } else {
462        if (command_id == IDC_SHOW_BOOKMARK_BAR)
463          menuitem_property_set(menu_item, kPropertyToggleType, kTypeCheckmark);
464
465        id_to_menu_item_.insert(std::make_pair(command_id, menu_item));
466        g_object_set_data(G_OBJECT(menu_item), "command-id",
467                          GINT_TO_POINTER(command_id));
468        g_signal_connect(menu_item, "item-activated",
469                         G_CALLBACK(OnItemActivatedThunk), this);
470      }
471    }
472
473    menuitem_child_append(top, menu_item);
474    g_object_unref(menu_item);
475  }
476
477  menuitem_child_append(parent, top);
478  g_object_unref(top);
479  return top;
480}
481
482void GlobalMenuBarX11::RegisterAccelerator(DbusmenuMenuitem* item,
483                                           const ui::Accelerator& accelerator) {
484  // A translation of libdbusmenu-gtk's menuitem_property_set_shortcut()
485  // translated from GDK types to ui::Accelerator types.
486  GVariantBuilder builder;
487  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
488
489  if (accelerator.IsCtrlDown())
490    g_variant_builder_add(&builder, "s", "Control");
491  if (accelerator.IsAltDown())
492    g_variant_builder_add(&builder, "s", "Alt");
493  if (accelerator.IsShiftDown())
494    g_variant_builder_add(&builder, "s", "Shift");
495
496  char* name = XKeysymToString(XKeysymForWindowsKeyCode(
497      accelerator.key_code(), false));
498  if (!name) {
499    NOTIMPLEMENTED();
500    return;
501  }
502  g_variant_builder_add(&builder, "s", name);
503
504  GVariant* inside_array = g_variant_builder_end(&builder);
505  g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY);
506  g_variant_builder_add_value(&builder, inside_array);
507  GVariant* outside_array = g_variant_builder_end(&builder);
508
509  menuitem_property_set_variant(item, kPropertyShortcut, outside_array);
510}
511
512GlobalMenuBarX11::HistoryItem* GlobalMenuBarX11::HistoryItemForTab(
513    const TabRestoreService::Tab& entry) {
514  const sessions::SerializedNavigationEntry& current_navigation =
515      entry.navigations.at(entry.current_navigation_index);
516  HistoryItem* item = new HistoryItem();
517  item->title = current_navigation.title();
518  item->url = current_navigation.virtual_url();
519  item->session_id = entry.id;
520
521  return item;
522}
523
524void GlobalMenuBarX11::AddHistoryItemToMenu(HistoryItem* item,
525                                            DbusmenuMenuitem* menu,
526                                            int tag,
527                                            int index) {
528  string16 title = item->title;
529  std::string url_string = item->url.possibly_invalid_spec();
530
531  if (title.empty())
532    title = UTF8ToUTF16(url_string);
533  gfx::ElideString(title, kMaximumMenuWidthInChars, &title);
534
535  DbusmenuMenuitem* menu_item = BuildMenuItem(UTF16ToUTF8(title), tag);
536  g_signal_connect(menu_item, "item-activated",
537                   G_CALLBACK(OnHistoryItemActivatedThunk), this);
538
539  g_object_set_data_full(G_OBJECT(menu_item), kHistoryItem, item,
540                         DeleteHistoryItem);
541  menuitem_child_add_position(menu, menu_item, index);
542  g_object_unref(menu_item);
543}
544
545void GlobalMenuBarX11::GetTopSitesData() {
546  DCHECK(top_sites_);
547
548  top_sites_->GetMostVisitedURLs(
549      base::Bind(&GlobalMenuBarX11::OnTopSitesReceived,
550                 weak_ptr_factory_.GetWeakPtr()));
551}
552
553void GlobalMenuBarX11::OnTopSitesReceived(
554    const history::MostVisitedURLList& visited_list) {
555  ClearMenuSection(history_menu_, TAG_MOST_VISITED);
556
557  int index = GetIndexOfMenuItemWithTag(history_menu_,
558                                        TAG_MOST_VISITED_HEADER) + 1;
559
560  for (size_t i = 0; i < visited_list.size() && i < kMostVisitedCount; ++i) {
561    const history::MostVisitedURL& visited = visited_list[i];
562    if (visited.url.spec().empty())
563      break;  // This is the signal that there are no more real visited sites.
564
565    HistoryItem* item = new HistoryItem();
566    item->title = visited.title;
567    item->url = visited.url;
568
569    AddHistoryItemToMenu(item,
570                         history_menu_,
571                         TAG_MOST_VISITED,
572                         index++);
573  }
574}
575
576void GlobalMenuBarX11::OnBookmarkBarVisibilityChanged() {
577  CommandIDMenuItemMap::iterator it =
578      id_to_menu_item_.find(IDC_SHOW_BOOKMARK_BAR);
579  if (it != id_to_menu_item_.end()) {
580    PrefService* prefs = browser_->profile()->GetPrefs();
581    // Note: Unlike the GTK version, we don't appear to need to do tricks where
582    // we block activation while setting the toggle.
583    menuitem_property_set_int(it->second, kPropertyToggleState,
584                              prefs->GetBoolean(prefs::kShowBookmarkBar));
585  }
586}
587
588int GlobalMenuBarX11::GetIndexOfMenuItemWithTag(DbusmenuMenuitem* menu,
589                                                int tag_id) {
590  GList* childs = menuitem_get_children(menu);
591  int i = 0;
592  for (; childs != NULL; childs = childs->next, i++) {
593    int tag =
594        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
595    if (tag == tag_id)
596      return i;
597  }
598
599  NOTREACHED();
600  return -1;
601}
602
603void GlobalMenuBarX11::ClearMenuSection(DbusmenuMenuitem* menu, int tag_id) {
604  std::vector<DbusmenuMenuitem*> menuitems_to_delete;
605
606  GList* childs = menuitem_get_children(menu);
607  for (; childs != NULL; childs = childs->next) {
608    DbusmenuMenuitem* current_item = reinterpret_cast<DbusmenuMenuitem*>(
609        childs->data);
610    ClearMenuSection(current_item, tag_id);
611
612    int tag =
613        GPOINTER_TO_INT(g_object_get_data(G_OBJECT(childs->data), kTypeTag));
614    if (tag == tag_id)
615      menuitems_to_delete.push_back(current_item);
616  }
617
618  for (std::vector<DbusmenuMenuitem*>::const_iterator it =
619           menuitems_to_delete.begin(); it != menuitems_to_delete.end(); ++it) {
620    menuitem_child_delete(menu, *it);
621  }
622}
623
624// static
625void GlobalMenuBarX11::DeleteHistoryItem(void* void_item) {
626  HistoryItem* item =
627      reinterpret_cast<GlobalMenuBarX11::HistoryItem*>(void_item);
628  delete item;
629}
630
631void GlobalMenuBarX11::EnabledStateChangedForCommand(int id, bool enabled) {
632  CommandIDMenuItemMap::iterator it = id_to_menu_item_.find(id);
633  if (it != id_to_menu_item_.end())
634    menuitem_property_set_bool(it->second, kPropertyEnabled, enabled);
635}
636
637void GlobalMenuBarX11::Observe(int type,
638                               const content::NotificationSource& source,
639                               const content::NotificationDetails& details) {
640  if (type == chrome::NOTIFICATION_TOP_SITES_CHANGED) {
641    GetTopSitesData();
642  } else {
643    NOTREACHED();
644  }
645}
646
647void GlobalMenuBarX11::TabRestoreServiceChanged(TabRestoreService* service) {
648  const TabRestoreService::Entries& entries = service->entries();
649
650  ClearMenuSection(history_menu_, TAG_RECENTLY_CLOSED);
651
652  // We'll get the index the "Recently Closed" header. (This can vary depending
653  // on the number of "Most Visited" items.
654  int index = GetIndexOfMenuItemWithTag(history_menu_,
655                                        TAG_RECENTLY_CLOSED_HEADER) + 1;
656
657  unsigned int added_count = 0;
658  for (TabRestoreService::Entries::const_iterator it = entries.begin();
659       it != entries.end() && added_count < kRecentlyClosedCount; ++it) {
660    TabRestoreService::Entry* entry = *it;
661
662    if (entry->type == TabRestoreService::WINDOW) {
663      TabRestoreService::Window* entry_win =
664          static_cast<TabRestoreService::Window*>(entry);
665      std::vector<TabRestoreService::Tab>& tabs = entry_win->tabs;
666      if (tabs.empty())
667        continue;
668
669      // Create the item for the parent/window.
670      HistoryItem* item = new HistoryItem();
671      item->session_id = entry_win->id;
672
673      std::string title = item->tabs.size() == 1 ?
674          l10n_util::GetStringUTF8(
675              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_SINGLE) :
676          l10n_util::GetStringFUTF8(
677              IDS_NEW_TAB_RECENTLY_CLOSED_WINDOW_MULTIPLE,
678              base::IntToString16(item->tabs.size()));
679      DbusmenuMenuitem* parent_item = BuildMenuItem(
680          title, TAG_RECENTLY_CLOSED);
681      menuitem_child_add_position(history_menu_, parent_item, index++);
682      g_object_unref(parent_item);
683
684      // The mac version of this code allows the user to click on the parent
685      // menu item to have the same effect as clicking the restore window
686      // submenu item. GTK+ helpfully activates a menu item when it shows a
687      // submenu so toss that feature out.
688      DbusmenuMenuitem* restore_item = BuildMenuItem(
689          l10n_util::GetStringUTF8(
690              IDS_HISTORY_CLOSED_RESTORE_WINDOW_LINUX).c_str(),
691          TAG_RECENTLY_CLOSED);
692      g_signal_connect(restore_item, "item-activated",
693                       G_CALLBACK(OnHistoryItemActivatedThunk), this);
694      g_object_set_data_full(G_OBJECT(restore_item), kHistoryItem, item,
695                             DeleteHistoryItem);
696      menuitem_child_append(parent_item, restore_item);
697      g_object_unref(restore_item);
698
699      DbusmenuMenuitem* separator = BuildSeparator();
700      menuitem_child_append(parent_item, separator);
701      g_object_unref(separator);
702
703      // Loop over the window's tabs and add them to the submenu.
704      int subindex = 2;
705      std::vector<TabRestoreService::Tab>::const_iterator iter;
706      for (iter = tabs.begin(); iter != tabs.end(); ++iter) {
707        TabRestoreService::Tab tab = *iter;
708        HistoryItem* tab_item = HistoryItemForTab(tab);
709        item->tabs.push_back(tab_item);
710        AddHistoryItemToMenu(tab_item,
711                             parent_item,
712                             TAG_RECENTLY_CLOSED,
713                             subindex++);
714      }
715
716      ++added_count;
717    } else if (entry->type == TabRestoreService::TAB) {
718      TabRestoreService::Tab* tab = static_cast<TabRestoreService::Tab*>(entry);
719      HistoryItem* item = HistoryItemForTab(*tab);
720      AddHistoryItemToMenu(item,
721                           history_menu_,
722                           TAG_RECENTLY_CLOSED,
723                           index++);
724      ++added_count;
725    }
726  }
727}
728
729void GlobalMenuBarX11::TabRestoreServiceDestroyed(
730    TabRestoreService* service) {
731  tab_restore_service_ = NULL;
732}
733
734void GlobalMenuBarX11::OnWindowMapped(unsigned long xid) {
735  if (!server_)
736    InitServer(xid);
737
738  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowMapped(xid);
739}
740
741void GlobalMenuBarX11::OnWindowUnmapped(unsigned long xid) {
742  GlobalMenuBarRegistrarX11::GetInstance()->OnWindowUnmapped(xid);
743}
744
745void GlobalMenuBarX11::OnItemActivated(DbusmenuMenuitem* item,
746                                       unsigned int timestamp) {
747  int id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(item), "command-id"));
748  chrome::ExecuteCommand(browser_, id);
749}
750
751void GlobalMenuBarX11::OnHistoryItemActivated(DbusmenuMenuitem* sender,
752                                              unsigned int timestamp) {
753  // Note: We don't have access to the event modifiers used to click the menu
754  // item since that happens in a different process.
755  HistoryItem* item = reinterpret_cast<HistoryItem*>(
756      g_object_get_data(G_OBJECT(sender), kHistoryItem));
757
758  // If this item can be restored using TabRestoreService, do so. Otherwise,
759  // just load the URL.
760  TabRestoreService* service =
761      TabRestoreServiceFactory::GetForProfile(profile_);
762  if (item->session_id && service) {
763    service->RestoreEntryById(browser_->tab_restore_service_delegate(),
764                              item->session_id, browser_->host_desktop_type(),
765                              UNKNOWN);
766  } else {
767    DCHECK(item->url.is_valid());
768    browser_->OpenURL(content::OpenURLParams(
769        item->url,
770        content::Referrer(),
771        NEW_FOREGROUND_TAB,
772        content::PAGE_TRANSITION_AUTO_BOOKMARK,
773        false));
774  }
775}
776
777void GlobalMenuBarX11::OnHistoryMenuAboutToShow(DbusmenuMenuitem* item) {
778  if (!tab_restore_service_) {
779    tab_restore_service_ = TabRestoreServiceFactory::GetForProfile(profile_);
780    if (tab_restore_service_) {
781      tab_restore_service_->LoadTabsFromLastSession();
782      tab_restore_service_->AddObserver(this);
783
784      // If LoadTabsFromLastSession doesn't load tabs, it won't call
785      // TabRestoreServiceChanged(). This ensures that all new windows after
786      // the first one will have their menus populated correctly.
787      TabRestoreServiceChanged(tab_restore_service_);
788    }
789  }
790}
791