From a60b90eae70704ae43cb89413d86e4c984074622 Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 15 May 2009 14:14:45 +1200 Subject: [PATCH] b=485125 nsIWidget::SetParent(nsnull) for gtk. r=roc --- widget/src/gtk2/mozcontainer.c | 32 ++++- widget/src/gtk2/mozcontainer.h | 31 +++++ widget/src/gtk2/mozdrawingarea.c | 26 ---- widget/src/gtk2/mozdrawingarea.h | 2 + widget/src/gtk2/nsWindow.cpp | 211 ++++++++++++++++++++++++++----- widget/src/gtk2/nsWindow.h | 1 + 6 files changed, 245 insertions(+), 58 deletions(-) diff --git a/widget/src/gtk2/mozcontainer.c b/widget/src/gtk2/mozcontainer.c index 380cd3d5c11f..9fd7435252a5 100644 --- a/widget/src/gtk2/mozcontainer.c +++ b/widget/src/gtk2/mozcontainer.c @@ -375,7 +375,7 @@ moz_container_remove (GtkContainer *container, GtkWidget *child_widget) { MozContainerChild *child; MozContainer *moz_container; - GList *tmp_list; + GdkWindow* parent_window; g_return_if_fail (IS_MOZ_CONTAINER(container)); g_return_if_fail (GTK_IS_WIDGET(child_widget)); @@ -385,8 +385,34 @@ moz_container_remove (GtkContainer *container, GtkWidget *child_widget) child = moz_container_get_child (moz_container, child_widget); g_return_if_fail (child); - if(child->widget == child_widget) { - gtk_widget_unparent(child_widget); + /* gtk_widget_unparent will remove the parent window (as well as the + * parent widget), but, in Mozilla's window hierarchy, the parent window + * may need to be kept because it may be part of a GdkWindow sub-hierarchy + * that is being moved to another MozContainer. + * + * (In a conventional GtkWidget hierarchy, GdkWindows being reparented + * would have their own GtkWidget and that widget would be the one being + * reparented. In Mozilla's hierarchy, the parent_window needs to be + * retained so that the GdkWindow sub-hierarchy is maintained.) + */ + parent_window = gtk_widget_get_parent_window(child_widget); + if (parent_window) + g_object_ref(parent_window); + + gtk_widget_unparent(child_widget); + + if (parent_window) { + /* The child_widget will always still exist because g_signal_emit, + * which invokes this function, holds a reference. + * + * If parent_window is the container's root window then it will not be + * the parent_window if the child_widget is placed in another + * container. + */ + if (parent_window != GTK_WIDGET(container)->window) + gtk_widget_set_parent_window(child_widget, parent_window); + + g_object_unref(parent_window); } moz_container->children = g_list_remove(moz_container->children, child); diff --git a/widget/src/gtk2/mozcontainer.h b/widget/src/gtk2/mozcontainer.h index b6cc29dd37c7..e96754d56698 100644 --- a/widget/src/gtk2/mozcontainer.h +++ b/widget/src/gtk2/mozcontainer.h @@ -45,6 +45,37 @@ extern "C" { #endif /* __cplusplus */ +/* + * MozContainer + * + * This class serves two purposes in the nsIWidget implementation. + * + * - It provides objects to receive signals from GTK for events on native + * windows. + * + * - It provides a container parent for GtkWidgets. The only GtkWidgets + * that need this in Mozilla are the GtkSockets for windowed plugins (Xt + * and XEmbed). + * + * Note that the window hierarchy in Mozilla differs from conventional + * GtkWidget hierarchies. + * + * Mozilla's hierarchy exists through the GdkWindow hierarchy, and all child + * GdkWindows (within a child nsIWidget hierarchy) belong to one MozContainer + * GtkWidget. If the MozContainer is unrealized or its GdkWindows are + * destroyed for some other reason, then the hierarchy no longer exists. (In + * conventional GTK clients, the hierarchy is recorded by the GtkWidgets, and + * so can be re-established after destruction of the GdkWindows.) + * + * One consequence of this is that the MozContainer does not know which of its + * GdkWindows should parent child GtkWidgets. (Conventional GtkContainers + * determine which GdkWindow to assign child GtkWidgets.) + * + * Therefore, when adding a child GtkWidget to a MozContainer, + * gtk_widget_set_parent_window should be called on the child GtkWidget before + * it is realized. + */ + #define MOZ_CONTAINER_TYPE (moz_container_get_type()) #define MOZ_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MOZ_CONTAINER_TYPE, MozContainer)) #define MOZ_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOZ_CONTAINER_TYPE, MozContainerClass)) diff --git a/widget/src/gtk2/mozdrawingarea.c b/widget/src/gtk2/mozdrawingarea.c index 99f6c4e276b6..01f18d5aaa9a 100644 --- a/widget/src/gtk2/mozdrawingarea.c +++ b/widget/src/gtk2/mozdrawingarea.c @@ -124,24 +124,6 @@ moz_drawingarea_reparent (MozDrawingarea *drawingarea, GdkWindow *aNewParent) aNewParent, 0, 0); } -static void -nullify_widget_pointers (gpointer data, GObject *widget) -{ - MozDrawingarea *drawingarea = data; - -#ifdef DEBUG - gpointer user_data; - /* This function may get called twice before widget is destroyed, - so the user_data may have already been nullified. */ - gdk_window_get_user_data(drawingarea->inner_window, &user_data); - if (user_data && widget && user_data != widget) - g_critical("user_data does not match widget"); -#endif - - gdk_window_set_user_data(drawingarea->inner_window, NULL); - gdk_window_set_user_data(drawingarea->clip_window, NULL); -} - void moz_drawingarea_create_windows (MozDrawingarea *drawingarea, GdkWindow *parent, GtkWidget *widget, GdkVisual *visual) @@ -189,8 +171,6 @@ moz_drawingarea_create_windows (MozDrawingarea *drawingarea, GdkWindow *parent, &attributes, attributes_mask); gdk_window_set_user_data(drawingarea->inner_window, widget); - g_object_weak_ref(G_OBJECT(widget), nullify_widget_pointers, drawingarea); - /* set the default pixmap to None so that you don't end up with the gtk default which is BlackPixel. */ gdk_window_set_back_pixmap(drawingarea->inner_window, NULL, FALSE); @@ -210,12 +190,6 @@ moz_drawingarea_finalize (GObject *object) drawingarea = MOZ_DRAWINGAREA(object); - gdk_window_get_user_data(drawingarea->inner_window, &user_data); - if (user_data) { - g_object_weak_unref(user_data, nullify_widget_pointers, drawingarea); - nullify_widget_pointers(drawingarea, NULL); - } - gdk_window_destroy(drawingarea->inner_window); gdk_window_destroy(drawingarea->clip_window); diff --git a/widget/src/gtk2/mozdrawingarea.h b/widget/src/gtk2/mozdrawingarea.h index 187c4a05e8ec..616618e52d62 100644 --- a/widget/src/gtk2/mozdrawingarea.h +++ b/widget/src/gtk2/mozdrawingarea.h @@ -65,6 +65,8 @@ typedef struct _MozDrawingareaClass MozDrawingareaClass; struct _MozDrawingarea { GObject parent_instance; + /* AFAIK this clip_window (and thus this whole class) exists solely to + * make gdk_window_scroll() smooth for nsIWidget::Scroll(). */ GdkWindow *clip_window; GdkWindow *inner_window; }; diff --git a/widget/src/gtk2/nsWindow.cpp b/widget/src/gtk2/nsWindow.cpp index 9fda721729e4..19d9b1d2a675 100644 --- a/widget/src/gtk2/nsWindow.cpp +++ b/widget/src/gtk2/nsWindow.cpp @@ -177,6 +177,7 @@ static gboolean expose_event_cb (GtkWidget *widget, GdkEventExpose *event); static gboolean configure_event_cb (GtkWidget *widget, GdkEventConfigure *event); +static void container_unrealize_cb (GtkWidget *widget); static void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation); static gboolean delete_event_cb (GtkWidget *widget, @@ -355,6 +356,8 @@ PRBool gDisableNativeTheme = PR_FALSE; // created for exposes, even if the display has a different depth static PRBool gForce24bpp = PR_FALSE; +static GtkWidget *gInvisibleContainer = NULL; + nsWindow::nsWindow() { mIsTopLevel = PR_FALSE; @@ -642,6 +645,79 @@ nsWindow::Create(nsNativeWidget aParent, return rv; } +static GtkWidget* +EnsureInvisibleContainer() +{ + if (!gInvisibleContainer) { + // GtkWidgets need to be anchored to a GtkWindow to be realized (to + // have a window). Using GTK_WINDOW_POPUP rather than + // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less + // initialization and window manager interaction. + GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP); + gInvisibleContainer = moz_container_new(); + gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer); + gtk_widget_realize(gInvisibleContainer); + + } + return gInvisibleContainer; +} + +static void +CheckDestroyInvisibleContainer() +{ + NS_PRECONDITION(gInvisibleContainer, "oh, no"); + + if (!gdk_window_peek_children(gInvisibleContainer->window)) { + // No children, so not in use. + // Make sure to destroy the GtkWindow also. + gtk_widget_destroy(gInvisibleContainer->parent); + gInvisibleContainer = NULL; + } +} + +// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging +// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of +// the GdkWindow hierarchy. If aNewWidget is NULL, the reference to +// aOldWidget is removed from its GdkWindows, and child GtkWidgets are +// destroyed. +static void +SetWidgetForHierarchy(GdkWindow *aWindow, + GtkWidget *aOldWidget, + GtkWidget *aNewWidget) +{ + gpointer data; + gdk_window_get_user_data(aWindow, &data); + + if (data != aOldWidget) { + if (!GTK_IS_WIDGET(data)) + return; + + GtkWidget* widget = static_cast(data); + if (widget->parent != aOldWidget) + return; + + // This window belongs to a child widget, which will no longer be a + // child of aOldWidget. + if (aNewWidget) { + gtk_widget_reparent(widget, aNewWidget); + } else { + // aNewWidget == NULL indicates that the window is about to be + // destroyed. + gtk_widget_destroy(widget); + } + + return; + } + + for (GList *list = gdk_window_peek_children(aWindow); + list; + list = list->next) { + SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget); + } + + gdk_window_set_user_data(aWindow, aNewWidget); +} + NS_IMETHODIMP nsWindow::Destroy(void) { @@ -724,6 +800,24 @@ nsWindow::Destroy(void) mDragLeaveTimer = nsnull; } + GtkWidget *owningWidget = GetMozContainerWidget(); + if (mShell) { + gtk_widget_destroy(mShell); + mShell = nsnull; + mContainer = nsnull; + } + else if (mContainer) { + gtk_widget_destroy(GTK_WIDGET(mContainer)); + mContainer = nsnull; + } + else if (owningWidget) { + // Remove references from GdkWindows back to their container + // widget while the GdkWindow hierarchy is still available. + // (OnContainerUnrealize does this when the MozContainer widget is + // destroyed.) + SetWidgetForHierarchy(mDrawingarea->clip_window, owningWidget, NULL); + } + if (mDrawingarea) { g_object_set_data(G_OBJECT(mDrawingarea->clip_window), "nsWindow", NULL); @@ -735,18 +829,15 @@ nsWindow::Destroy(void) g_object_set_data(G_OBJECT(mDrawingarea->inner_window), "mozdrawingarea", NULL); + NS_ASSERTION(!get_gtk_widget_for_gdk_window(mDrawingarea->inner_window), + "widget reference not removed"); + g_object_unref(mDrawingarea); mDrawingarea = nsnull; } - if (mShell) { - gtk_widget_destroy(mShell); - mShell = nsnull; - mContainer = nsnull; - } - else if (mContainer) { - gtk_widget_destroy(GTK_WIDGET(mContainer)); - mContainer = nsnull; + if (gInvisibleContainer && owningWidget == gInvisibleContainer) { + CheckDestroyInvisibleContainer(); } OnDestroy(); @@ -769,29 +860,63 @@ nsWindow::GetParent(void) NS_IMETHODIMP nsWindow::SetParent(nsIWidget *aNewParent) { - NS_ENSURE_ARG_POINTER(aNewParent); - - GdkWindow* newParentWindow = - static_cast(aNewParent->GetNativeData(NS_NATIVE_WINDOW)); - NS_ASSERTION(newParentWindow, "Parent widget has a null native window handle"); - - if (!mShell && mDrawingarea) { -#ifdef DEBUG - if (!mContainer) { - // Check that the new Parent window has the same MozContainer - gpointer old_container; - gdk_window_get_user_data(mDrawingarea->inner_window, - &old_container); - gpointer new_container; - gdk_window_get_user_data(newParentWindow, &new_container); - NS_ASSERTION(old_container == new_container, - "FIXME: Wrong MozContainer on MozDrawingarea"); - } -#endif - moz_drawingarea_reparent(mDrawingarea, newParentWindow); - } else { + if (mContainer || !mDrawingarea || !mParent) { NS_NOTREACHED("nsWindow::SetParent - reparenting a non-child window"); + return NS_ERROR_NOT_IMPLEMENTED; } + + // nsBaseWidget::SetZIndex adds child widgets to the parent's list. + nsCOMPtr kungFuDeathGrip = this; + mParent->RemoveChild(this); + + mParent = aNewParent; + + GtkWidget* oldContainer = GetMozContainerWidget(); + if (!oldContainer) { + // The GdkWindows have been destroyed so there is nothing else to + // reparent. + NS_ABORT_IF_FALSE(GDK_WINDOW_OBJECT(mDrawingarea->inner_window)->destroyed, + "live GdkWindow with no widget"); + return NS_OK; + } + + NS_ABORT_IF_FALSE(!GDK_WINDOW_OBJECT(mDrawingarea->inner_window)->destroyed, + "destroyed GdkWindow with widget"); + + GdkWindow* newParentWindow = NULL; + GtkWidget* newContainer = NULL; + if (aNewParent) { + newParentWindow = static_cast + (aNewParent->GetNativeData(NS_NATIVE_WINDOW)); + if (newParentWindow) { + newContainer = get_gtk_widget_for_gdk_window(newParentWindow); + } + } else { + // aNewParent is NULL, but reparent to a hidden window to avoid + // destroying the GdkWindow and its descendants. + // An invisible container widget is needed to hold descendant + // GtkWidgets. + newContainer = EnsureInvisibleContainer(); + newParentWindow = newContainer->window; + } + + if (!newContainer) { + // The new parent GdkWindow has been destroyed. + NS_ABORT_IF_FALSE(!newParentWindow || + GDK_WINDOW_OBJECT(newParentWindow)->destroyed, + "live GdkWindow with no widget"); + Destroy(); + } else { + if (newContainer != oldContainer) { + NS_ABORT_IF_FALSE(!GDK_WINDOW_OBJECT(newParentWindow)->destroyed, + "destroyed GdkWindow with widget"); + SetWidgetForHierarchy(mDrawingarea->clip_window, oldContainer, + newContainer); + } + + moz_drawingarea_reparent(mDrawingarea, newParentWindow); + } + return NS_OK; } @@ -2336,6 +2461,21 @@ nsWindow::OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent) return FALSE; } +void +nsWindow::OnContainerUnrealize(GtkWidget *aWidget) +{ + // The GdkWindows are about to be destroyed (but not deleted), so remove + // their references back to their container widget while the GdkWindow + // hierarchy is still available. + + NS_ASSERTION(mContainer == MOZ_CONTAINER(aWidget), + "unexpected \"unrealize\" signal"); + + if (mDrawingarea) { + SetWidgetForHierarchy(mDrawingarea->clip_window, aWidget, NULL); + } +} + void nsWindow::OnSizeAllocate(GtkWidget *aWidget, GtkAllocation *aAllocation) { @@ -3907,6 +4047,8 @@ nsWindow::NativeCreate(nsIWidget *aParent, } if (mContainer) { + g_signal_connect(G_OBJECT(mContainer), "unrealize", + G_CALLBACK(container_unrealize_cb), NULL); g_signal_connect_after(G_OBJECT(mContainer), "size_allocate", G_CALLBACK(size_allocate_cb), NULL); g_signal_connect(G_OBJECT(mContainer), "expose_event", @@ -5224,6 +5366,17 @@ configure_event_cb(GtkWidget *widget, return window->OnConfigureEvent(widget, event); } +/* static */ +void +container_unrealize_cb (GtkWidget *widget) +{ + nsRefPtr window = get_window_for_gtk_widget(widget); + if (!window) + return; + + window->OnContainerUnrealize(widget); +} + /* static */ void size_allocate_cb (GtkWidget *widget, GtkAllocation *allocation) diff --git a/widget/src/gtk2/nsWindow.h b/widget/src/gtk2/nsWindow.h index 492a8e3d9608..2d5a69118774 100644 --- a/widget/src/gtk2/nsWindow.h +++ b/widget/src/gtk2/nsWindow.h @@ -219,6 +219,7 @@ public: GdkEventExpose *aEvent); gboolean OnConfigureEvent(GtkWidget *aWidget, GdkEventConfigure *aEvent); + void OnContainerUnrealize(GtkWidget *aWidget); void OnSizeAllocate(GtkWidget *aWidget, GtkAllocation *aAllocation); void OnDeleteEvent(GtkWidget *aWidget,