| Index: chrome/browser/ui/gtk/menu_gtk.cc | 
| =================================================================== | 
| --- chrome/browser/ui/gtk/menu_gtk.cc	(revision 159156) | 
| +++ chrome/browser/ui/gtk/menu_gtk.cc	(working copy) | 
| @@ -507,6 +507,7 @@ | 
| // We will populate the submenu on demand when shown. | 
| g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); | 
| g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); | 
| +      connect_to_activate = false; | 
| } | 
|  | 
| ui::AcceleratorGtk accelerator; | 
| @@ -598,15 +599,6 @@ | 
|  | 
| ui::MenuModel* model = ModelForMenuItem(GTK_MENU_ITEM(menu_item)); | 
|  | 
| -  GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | 
| -  if (submenu) { | 
| -    // We receive activation messages when highlighting a menu that has a | 
| -    // submenu. We build submenus on show, and tear them down on hide, to | 
| -    // allow submenu models to be constructed and destroyed on demand. So, | 
| -    // there's nothing further to do here in that case. | 
| -    return; | 
| -  } | 
| - | 
| // The activate signal is sent to radio items as they get deselected; | 
| // ignore it in this case. | 
| if (GTK_IS_RADIO_MENU_ITEM(menu_item) && | 
| @@ -755,13 +747,25 @@ | 
| void MenuGtk::OnSubMenuShow(GtkWidget* submenu) { | 
| GtkWidget* menu_item = static_cast<GtkWidget*>( | 
| g_object_get_data(G_OBJECT(submenu), "menu-item")); | 
| - | 
| +  // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. | 
| +  CHECK(menu_item); | 
| // Notify the submenu model that the menu will be shown. | 
| ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | 
| g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | 
| -  // TODO(mdm): Figure out why this can sometimes be NULL. See bug 131974. | 
| +  // We're extra cautious here, and bail out if the submenu model is NULL. In | 
| +  // some cases we clear it out from a parent menu; we shouldn't ever show the | 
| +  // menu after that, but we play it safe since we're dealing with wacky | 
| +  // injected libraries that toy with our menus. (See comments below.) | 
| if (!submenu_model) | 
| return; | 
| + | 
| +  // If the submenu is already built, then return right away. This means we | 
| +  // recently showed this submenu, and have not yet processed the fact that it | 
| +  // was hidden before being shown again. | 
| +  if (g_object_get_data(G_OBJECT(submenu), "submenu-built")) | 
| +    return; | 
| +  g_object_set_data(G_OBJECT(submenu), "submenu-built", GINT_TO_POINTER(1)); | 
| + | 
| submenu_model->MenuWillShow(); | 
|  | 
| // Actually build the submenu and attach it to the parent menu item. | 
| @@ -776,33 +780,66 @@ | 
| // Increase the reference count of the old submenu, and schedule it to be | 
| // deleted later. We get this hide notification before we've processed menu | 
| // activations, so if we were to delete the submenu now, we might lose the | 
| -  // activation. Note that we disconnect it from the parent menu item below. | 
| +  // activation. This also lets us reuse the menu if it is shown again before | 
| +  // it gets deleted; in that case, OnSubMenuHiddenCallback() just decrements | 
| +  // the reference count again. Note that the delay is just an optimization; we | 
| +  // could use PostTask() and this would still work correctly. | 
| g_object_ref(G_OBJECT(submenu)); | 
| -  MessageLoop::current()->PostTask( | 
| +  MessageLoop::current()->PostDelayedTask( | 
| FROM_HERE, | 
| -      base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu)); | 
| +      base::Bind(&MenuGtk::OnSubMenuHiddenCallback, submenu), | 
| +      base::TimeDelta::FromSeconds(2)); | 
| +} | 
|  | 
| -  // Build a new submenu, which will be populated when it is next shown. | 
| -  GtkWidget* menu_item = static_cast<GtkWidget*>( | 
| -      g_object_get_data(G_OBJECT(submenu), "menu-item")); | 
| -  submenu = gtk_menu_new(); | 
| -  g_object_set_data(G_OBJECT(submenu), "menu-item", menu_item); | 
| -  gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), submenu); | 
| -  g_signal_connect(submenu, "show", G_CALLBACK(OnSubMenuShowThunk), this); | 
| -  g_signal_connect(submenu, "hide", G_CALLBACK(OnSubMenuHiddenThunk), this); | 
| +namespace { | 
| + | 
| +// Remove all descendant submenu-model data pointers. | 
| +void RemoveSubMenuModels(GtkWidget* menu_item, void* unused) { | 
| +  if (!GTK_IS_MENU_ITEM(menu_item)) | 
| +    return; | 
| +  g_object_steal_data(G_OBJECT(menu_item), "submenu-model"); | 
| +  GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menu_item)); | 
| +  if (submenu) | 
| +    gtk_container_foreach(GTK_CONTAINER(submenu), RemoveSubMenuModels, NULL); | 
| } | 
|  | 
| +}  // namespace | 
| + | 
| // static | 
| void MenuGtk::OnSubMenuHiddenCallback(GtkWidget* submenu) { | 
| -  // Notify the submenu model that the menu has been hidden. | 
| -  GtkWidget* menu_item = static_cast<GtkWidget*>( | 
| -      g_object_get_data(G_OBJECT(submenu), "menu-item")); | 
| -  ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | 
| -      g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | 
| -  // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. | 
| -  if (submenu_model) | 
| -    submenu_model->MenuClosed(); | 
| +  if (!gtk_widget_get_visible(submenu)) { | 
| +    // Remove all the children of this menu, clearing out their submenu-model | 
| +    // pointers in case they have pending calls to OnSubMenuHiddenCallback(). | 
| +    // (Normally that won't happen: we'd have hidden them first, and so they'd | 
| +    // have already been deleted. But in some cases [e.g. on Ubuntu 12.04], | 
| +    // GTK menu operations may be hooked to allow external applications to | 
| +    // mirror the menu structure, and the hooks may show and hide menus in | 
| +    // order to trigger exactly the kind of dynamic menu building we're doing. | 
| +    // The result is that we see show and hide events in strange orders.) | 
| +    GList* children = gtk_container_get_children(GTK_CONTAINER(submenu)); | 
| +    for (GList* child = children; child; child = g_list_next(child)) { | 
| +      RemoveSubMenuModels(GTK_WIDGET(child->data), NULL); | 
| +      gtk_container_remove(GTK_CONTAINER(submenu), GTK_WIDGET(child->data)); | 
| +    } | 
| +    g_list_free(children); | 
|  | 
| +    // Clear out the bit that says the menu is built. | 
| +    // We'll rebuild it next time it is shown. | 
| +    g_object_steal_data(G_OBJECT(submenu), "submenu-built"); | 
| + | 
| +    // Notify the submenu model that the menu has been hidden. This may cause | 
| +    // it to delete descendant submenu models, which is why we cleared those | 
| +    // pointers out above. | 
| +    GtkWidget* menu_item = static_cast<GtkWidget*>( | 
| +        g_object_get_data(G_OBJECT(submenu), "menu-item")); | 
| +    // TODO(mdm): Figure out why this can sometimes be NULL. See bug 124110. | 
| +    CHECK(menu_item); | 
| +    ui::MenuModel* submenu_model = static_cast<ui::MenuModel*>( | 
| +        g_object_get_data(G_OBJECT(menu_item), "submenu-model")); | 
| +    if (submenu_model) | 
| +      submenu_model->MenuClosed(); | 
| +  } | 
| + | 
| // Remove the reference we grabbed in OnSubMenuHidden() above. | 
| g_object_unref(G_OBJECT(submenu)); | 
| } | 
|  |