b=509278 suppress painting while the window is invisible and detect viewability changes through signals r=roc

This commit is contained in:
Karl Tomlinson 2009-11-02 15:03:12 +13:00
Родитель d7bf04ddc4
Коммит 46584e05b2
2 изменённых файлов: 168 добавлений и 42 удалений

Просмотреть файл

@ -134,8 +134,6 @@ D_DEBUG_DOMAIN( ns_Window, "nsWindow", "nsWindow" );
}
#include "gfxDirectFBSurface.h"
#define GDK_WINDOW_XWINDOW(_win) _win
#else
#define D_DEBUG_AT(x,y...) do {} while (0)
#endif
// Don't put more than this many rects in the dirty region, just fluff
@ -200,6 +198,8 @@ static gboolean scroll_event_cb (GtkWidget *widget,
GdkEventScroll *event);
static gboolean visibility_notify_event_cb(GtkWidget *widget,
GdkEventVisibility *event);
static void hierarchy_changed_cb (GtkWidget *widget,
GtkWidget *previous_toplevel);
static gboolean window_state_event_cb (GtkWidget *widget,
GdkEventWindowState *event);
static void theme_changed_cb (GtkSettings *settings,
@ -364,6 +364,17 @@ static PRBool gForce24bpp = PR_FALSE;
static GtkWidget *gInvisibleContainer = NULL;
// Some gobject functions expect functions for gpointer arguments.
// gpointer is void* but C++ doesn't like casting functions to void*.
template<class T> gpointer
FuncToGpointer(T aFunction)
{
return reinterpret_cast<gpointer>
(reinterpret_cast<uintptr_t>
// This cast just provides a warning if T is not a function.
(reinterpret_cast<void (*)()>(aFunction)));
}
nsWindow::nsWindow()
{
mIsTopLevel = PR_FALSE;
@ -384,7 +395,8 @@ nsWindow::nsWindow()
mContainerGotFocus = PR_FALSE;
mContainerLostFocus = PR_FALSE;
mContainerBlockFocus = PR_FALSE;
mIsVisible = PR_FALSE;
mHasMappedToplevel = PR_FALSE;
mIsFullyObscured = PR_FALSE;
mRetryPointerGrab = PR_FALSE;
mRetryKeyboardGrab = PR_FALSE;
mTransientParent = nsnull;
@ -710,7 +722,7 @@ nsWindow::Destroy(void)
}
g_signal_handlers_disconnect_by_func(gtk_settings_get_default(),
(gpointer)G_CALLBACK(theme_changed_cb),
FuncToGpointer(theme_changed_cb),
this);
// ungrab if required
@ -846,11 +858,11 @@ nsWindow::SetParent(nsIWidget *aNewParent)
NS_ABORT_IF_FALSE(!GDK_WINDOW_OBJECT(mGdkWindow)->destroyed,
"destroyed GdkWindow with widget");
nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
GdkWindow* newParentWindow = NULL;
GtkWidget* newContainer = NULL;
if (aNewParent) {
newParentWindow = static_cast<GdkWindow*>
(aNewParent->GetNativeData(NS_NATIVE_WINDOW));
newParentWindow = newParent->mGdkWindow;
if (newParentWindow) {
newContainer = get_gtk_widget_for_gdk_window(newParentWindow);
}
@ -879,6 +891,12 @@ nsWindow::SetParent(nsIWidget *aNewParent)
gdk_window_reparent(mGdkWindow, newParentWindow, 0, 0);
}
PRBool parentHasMappedToplevel =
newParent && newParent->mHasMappedToplevel;
if (mHasMappedToplevel != parentHasMappedToplevel) {
SetHasMappedToplevel(parentHasMappedToplevel);
}
return NS_OK;
}
@ -921,33 +939,6 @@ nsWindow::IsVisible(PRBool& aState)
return NS_OK;
}
// Internal method, which returns false when no part of the window can be seen.
PRBool
nsWindow::CanBeSeen()
{
// mIsVisible keeps track of whether any part of the window is unobscured,
// but does not get updated when the window changes state from viewable to
// not viewable (when any ancestor window is unmapped) because
// VisibilityNotifty events do not get sent on such a change in state.
if (!mIsShown || !mIsVisible)
return PR_FALSE;
GtkWidget *topWidget = nsnull;
GetToplevelWidget(&topWidget);
// If the toplevel widget has been unmapped, then record this in
// mIsVisible. mIsVisible will be updated in OnVisibilityNotifyEvent()
// when its window becomes viewable again.
// (gdk_window_is_viewable() is not suitable here as it does not
// check GDK_WINDOW_STATE_ICONIFIED.)
mIsVisible =
topWidget &&
!(gdk_window_get_state(topWidget->window) &
(GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN));
return mIsVisible;
}
NS_IMETHODIMP
nsWindow::ConstrainPosition(PRBool aAllowSlop, PRInt32 *aX, PRInt32 *aY)
{
@ -980,10 +971,19 @@ nsWindow::ConstrainPosition(PRBool aAllowSlop, PRInt32 *aX, PRInt32 *aY)
NS_IMETHODIMP
nsWindow::Show(PRBool aState)
{
if (aState == mIsShown)
return NS_OK;
mIsShown = aState;
LOG(("nsWindow::Show [%p] state %d\n", (void *)this, aState));
if (aState) {
// Now that this window is shown, mHasMappedToplevel needs to be
// tracked on viewable descendants.
SetHasMappedToplevel(mHasMappedToplevel);
}
// Ok, someone called show on a window that isn't sized to a sane
// value. Mark this window as needing to have Show() called on it
// and return.
@ -1661,7 +1661,7 @@ NS_IMETHODIMP
nsWindow::Invalidate(const nsIntRect &aRect,
PRBool aIsSynchronous)
{
if (!mGdkWindow || !CanBeSeen())
if (!mGdkWindow)
return NS_OK;
GdkRectangle rect;
@ -1686,6 +1686,8 @@ nsWindow::Update()
if (!mGdkWindow)
return NS_OK;
LOGDRAW(("Update [%p] %p\n", this, mGdkWindow));
gdk_window_process_updates(mGdkWindow, FALSE);
return NS_OK;
}
@ -2062,7 +2064,8 @@ nsWindow::OnExposeEvent(GtkWidget *aWidget, GdkEventExpose *aEvent)
return FALSE;
}
if (!mGdkWindow)
// Windows that are not visible will be painted after they become visible.
if (!mGdkWindow || mIsFullyObscured || !mHasMappedToplevel)
return FALSE;
static NS_DEFINE_CID(kRegionCID, NS_REGION_CID);
@ -3215,10 +3218,23 @@ void
nsWindow::OnVisibilityNotifyEvent(GtkWidget *aWidget,
GdkEventVisibility *aEvent)
{
LOGDRAW(("Visibility event %i on [%p] %p\n",
aEvent->state, this, aEvent->window));
if (!mGdkWindow)
return;
switch (aEvent->state) {
case GDK_VISIBILITY_UNOBSCURED:
case GDK_VISIBILITY_PARTIAL:
mIsVisible = PR_TRUE;
if (mIsFullyObscured && mHasMappedToplevel) {
// GDK_EXPOSE events have been ignored, so make sure GDK
// doesn't think that the window has already been painted.
gdk_window_invalidate_rect(mGdkWindow, NULL, FALSE);
}
mIsFullyObscured = PR_FALSE;
#ifdef MOZ_PLATFORM_HILDON
#ifdef USE_XIM
// In Hildon/Maemo, a browser window will get into 'patially visible' state wheneven an
@ -3234,7 +3250,7 @@ nsWindow::OnVisibilityNotifyEvent(GtkWidget *aWidget,
EnsureGrabs();
break;
default: // includes GDK_VISIBILITY_FULLY_OBSCURED
mIsVisible = PR_FALSE;
mIsFullyObscured = PR_TRUE;
break;
}
}
@ -3245,6 +3261,27 @@ nsWindow::OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent)
LOG(("nsWindow::OnWindowStateEvent [%p] changed %d new_window_state %d\n",
(void *)this, aEvent->changed_mask, aEvent->new_window_state));
if (IS_MOZ_CONTAINER(aWidget)) {
// This event is notifying the container widget of changes to the
// toplevel window. Just detect changes affecting whether windows are
// viewable.
//
// (A visibility notify event is sent to each window that becomes
// viewable when the toplevel is mapped, but we can't rely on that for
// setting mHasMappedToplevel because these toplevel window state
// events are asynchronous. The windows in the hierarchy now may not
// be the same windows as when the toplevel was mapped, so they may
// not get VisibilityNotify events.)
PRBool mapped =
!(aEvent->new_window_state &
(GDK_WINDOW_STATE_ICONIFIED|GDK_WINDOW_STATE_WITHDRAWN));
if (mHasMappedToplevel != mapped) {
SetHasMappedToplevel(mapped);
}
return;
}
// else the widget is a shell widget.
nsSizeModeEvent event(PR_TRUE, NS_SIZEMODE, this);
// We don't care about anything but changes in the maximized/icon
@ -3872,6 +3909,10 @@ nsWindow::Create(nsIWidget *aParent,
case eWindowType_child: {
if (parentMozContainer) {
mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer);
nsWindow *parentnsWindow =
get_window_for_gdk_window(parentGdkWindow);
if (parentnsWindow)
mHasMappedToplevel = parentnsWindow->mHasMappedToplevel;
}
else if (parentGtkContainer) {
GtkWidget *container = moz_container_new();
@ -3959,6 +4000,10 @@ nsWindow::Create(nsIWidget *aParent,
G_CALLBACK(scroll_event_cb), NULL);
g_signal_connect(G_OBJECT(mContainer), "visibility_notify_event",
G_CALLBACK(visibility_notify_event_cb), NULL);
g_signal_connect(G_OBJECT(mContainer), "hierarchy_changed",
G_CALLBACK(hierarchy_changed_cb), NULL);
// Initialize mHasMappedToplevel.
hierarchy_changed_cb(GTK_WIDGET(mContainer), NULL);
gtk_drag_dest_set((GtkWidget *)mContainer,
(GtkDestDefaults)0,
@ -4253,6 +4298,48 @@ nsWindow::NativeShow (PRBool aAction)
}
}
void
nsWindow::SetHasMappedToplevel(PRBool aState)
{
// Even when aState == mHasMappedToplevel (as when this method is called
// from Show()), child windows need to have their state checked, so don't
// return early.
PRBool oldState = mHasMappedToplevel;
mHasMappedToplevel = aState;
// mHasMappedToplevel is not updated for children of windows that are
// hidden; GDK knows not to send expose events for these windows. The
// state is recorded on the hidden window itself, but, for child trees of
// hidden windows, their state essentially becomes disconnected from their
// hidden parent. When the hidden parent gets shown, the child trees are
// reconnected, and the state of the window being shown can be easily
// propagated.
if (!mIsShown || !mGdkWindow)
return;
if (aState && !oldState && !mIsFullyObscured) {
// GDK_EXPOSE events have been ignored but the window is now visible,
// so make sure GDK doesn't think that the window has already been
// painted.
gdk_window_invalidate_rect(mGdkWindow, NULL, FALSE);
// Check that a grab didn't fail due to the window not being
// viewable.
EnsureGrabs();
}
for (GList *children = gdk_window_peek_children(mGdkWindow);
children;
children = children->next) {
GdkWindow *gdkWin = GDK_WINDOW(children->data);
nsWindow *child = get_window_for_gdk_window(gdkWin);
if (child && child->mHasMappedToplevel != aState) {
child->SetHasMappedToplevel(aState);
}
}
}
nsIntSize
nsWindow::GetSafeWindowSize(nsIntSize aSize)
{
@ -4546,7 +4633,7 @@ nsWindow::GrabPointer(void)
// If the window isn't visible, just set the flag to retry the
// grab. When this window becomes visible, the grab will be
// retried.
if (!CanBeSeen()) {
if (!mHasMappedToplevel || mIsFullyObscured) {
LOG(("GrabPointer: window not visible\n"));
mRetryPointerGrab = PR_TRUE;
return;
@ -4583,7 +4670,7 @@ nsWindow::GrabKeyboard(void)
// If the window isn't visible, just set the flag to retry the
// grab. When this window becomes visible, the grab will be
// retried.
if (!CanBeSeen()) {
if (!mHasMappedToplevel || mIsFullyObscured) {
LOG(("GrabKeyboard: window not visible\n"));
mRetryKeyboardGrab = PR_TRUE;
return;
@ -5654,6 +5741,44 @@ visibility_notify_event_cb (GtkWidget *widget, GdkEventVisibility *event)
return TRUE;
}
static void
hierarchy_changed_cb (GtkWidget *widget,
GtkWidget *previous_toplevel)
{
GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
GdkEventWindowState event;
event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
if (GTK_IS_WINDOW(previous_toplevel)) {
g_signal_handlers_disconnect_by_func(previous_toplevel,
FuncToGpointer(window_state_event_cb),
widget);
if (widget->window) {
old_window_state = gdk_window_get_state(widget->window);
}
}
if (GTK_IS_WINDOW(toplevel)) {
g_signal_connect_swapped(toplevel, "window-state-event",
G_CALLBACK(window_state_event_cb), widget);
if (widget->window) {
event.new_window_state = gdk_window_get_state(widget->window);
}
}
event.changed_mask = static_cast<GdkWindowState>
(old_window_state ^ event.new_window_state);
if (event.changed_mask) {
event.type = GDK_WINDOW_STATE;
event.window = NULL;
event.send_event = TRUE;
window_state_event_cb(widget, &event);
}
}
/* static */
gboolean
window_state_event_cb (GtkWidget *widget, GdkEventWindowState *event)

Просмотреть файл

@ -273,7 +273,8 @@ public:
PRBool aRepaint);
virtual void NativeShow (PRBool aAction);
virtual nsIntSize GetSafeWindowSize(nsIntSize aSize);
void SetHasMappedToplevel(PRBool aState);
nsIntSize GetSafeWindowSize(nsIntSize aSize);
void EnsureGrabs (void);
void GrabPointer (void);
@ -431,7 +432,6 @@ protected:
PRPackedBool mPlaced;
private:
PRBool CanBeSeen();
void GetToplevelWidget(GtkWidget **aWidget);
GtkWidget *GetMozContainerWidget();
nsWindow *GetContainerWindow();
@ -452,7 +452,8 @@ private:
PRUint32 mContainerGotFocus : 1,
mContainerLostFocus : 1,
mContainerBlockFocus : 1,
mIsVisible : 1,
mHasMappedToplevel : 1,
mIsFullyObscured : 1,
mRetryPointerGrab : 1,
mRetryKeyboardGrab : 1;
GtkWindow *mTransientParent;