tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit f5f68709dddcf4abbe3b59b58ab275c3633a8cc9
parent 83b903c46e796318bba0fd29023293bea92fb122
Author: Sandor Molnar <smolnar@mozilla.com>
Date:   Thu, 13 Nov 2025 17:40:59 +0200

Revert "Bug 1998657 [Wayland] Create EGLWindow over offscreen wl_surface and make it always available r=emilio" for causing valgrind failures

This reverts commit de6ac364421c381d9ada823225e57595427ba88a.

Revert "Bug 1998657 [Wayland] Make WaylandSurface ready to draw right after its created r=emilio"

This reverts commit b39b0ef973df2a60d669595203a471330aa9fc7d.

Diffstat:
Mwidget/gtk/MozContainerWayland.cpp | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mwidget/gtk/MozContainerWayland.h | 11+++++++++++
Mwidget/gtk/WaylandSurface.cpp | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mwidget/gtk/WaylandSurface.h | 34+++++++++++++++++++++++++---------
Mwidget/gtk/WindowSurfaceWaylandMultiBuffer.cpp | 25++++++++++++++++++++++++-
Mwidget/gtk/WindowSurfaceWaylandMultiBuffer.h | 4++++
Mwidget/gtk/nsWindow.cpp | 222++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mwidget/gtk/nsWindow.h | 10++++++++--
8 files changed, 410 insertions(+), 70 deletions(-)

diff --git a/widget/gtk/MozContainerWayland.cpp b/widget/gtk/MozContainerWayland.cpp @@ -99,6 +99,11 @@ static void moz_container_wayland_invalidate(MozContainer* container) { gdk_window_invalidate_rect(window, nullptr, true); } +void moz_container_wayland_add_or_fire_initial_draw_callback( + MozContainer* container, const std::function<void(void)>& initial_draw_cb) { + MOZ_WL_SURFACE(container)->AddOrFireReadyToDrawCallback(initial_draw_cb); +} + void moz_container_wayland_unmap(GtkWidget* widget) { g_return_if_fail(IS_MOZ_CONTAINER(widget)); @@ -138,9 +143,23 @@ gboolean moz_container_wayland_map_event(GtkWidget* widget, // below. MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + // Set waiting_to_show flag. It means the mozcontainer is cofigured/mapped + // and it's supposed to be visible. *But* it's really visible when we get + // moz_container_wayland_add_or_fire_initial_draw_callback() which means + // wayland compositor makes it live. + MOZ_WL_CONTAINER(widget)->waiting_to_show = true; + MozContainer* container = MOZ_CONTAINER(widget); + MOZ_WL_SURFACE(container)->AddOrFireReadyToDrawCallback( + [container]() -> void { + LOGCONTAINER( + "[%p] moz_container_wayland_add_or_fire_initial_draw_callback set " + "visible", + moz_container_get_nsWindow(container)); + moz_container_wayland_clear_waiting_to_show_flag(container); + }); + // Don't create wl_subsurface in map_event when it's already created or // if we create it for the first time. - MozContainer* container = MOZ_CONTAINER(widget); if (MOZ_WL_SURFACE(container)->IsMapped() || MOZ_WL_CONTAINER(container)->before_first_size_alloc) { return false; @@ -262,7 +281,40 @@ static bool moz_container_wayland_ensure_surface(MozContainer* container, return true; } +struct wl_egl_window* moz_container_wayland_get_egl_window( + MozContainer* container) { + LOGCONTAINER("%s [%p] mapped %d eglwindow %d", __FUNCTION__, + (void*)moz_container_get_nsWindow(container), + MOZ_WL_SURFACE(container)->IsMapped(), + MOZ_WL_SURFACE(container)->HasEGLWindow()); + + if (!MOZ_WL_SURFACE(container)->IsMapped()) { + return nullptr; + } + + // TODO: Get size from bounds instead of GdkWindow? + // We may be in rendering/compositor thread here. + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container)); + DesktopIntSize size(gdk_window_get_width(window), + gdk_window_get_height(window)); + return MOZ_WL_SURFACE(container)->GetEGLWindow(size); +} + +gboolean moz_container_wayland_can_draw(MozContainer* container) { + return MOZ_WL_SURFACE(container)->IsReadyToDraw(); +} + double moz_container_wayland_get_scale(MozContainer* container) { nsWindow* window = moz_container_get_nsWindow(container); return window ? window->FractionalScaleFactor() : 1.0; } + +bool moz_container_wayland_is_waiting_to_show(MozContainer* container) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + return MOZ_WL_CONTAINER(container)->waiting_to_show; +} + +void moz_container_wayland_clear_waiting_to_show_flag(MozContainer* container) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + MOZ_WL_CONTAINER(container)->waiting_to_show = false; +} diff --git a/widget/gtk/MozContainerWayland.h b/widget/gtk/MozContainerWayland.h @@ -43,13 +43,24 @@ struct MozContainerWayland { RefPtr<mozilla::widget::WaylandSurface> mSurface; gboolean before_first_size_alloc = false; + gboolean waiting_to_show = false; }; void moz_container_wayland_map(GtkWidget*); gboolean moz_container_wayland_map_event(GtkWidget*, GdkEventAny*); void moz_container_wayland_size_allocate(GtkWidget*, GtkAllocation*); void moz_container_wayland_unmap(GtkWidget*); + +struct wl_egl_window* moz_container_wayland_get_egl_window( + MozContainer* container); + +void moz_container_wayland_add_or_fire_initial_draw_callback( + MozContainer* container, const std::function<void(void)>& initial_draw_cb); + wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget); +gboolean moz_container_wayland_can_draw(MozContainer* container); double moz_container_wayland_get_scale(MozContainer* container); +bool moz_container_wayland_is_waiting_to_show(MozContainer* container); +void moz_container_wayland_clear_waiting_to_show_flag(MozContainer* container); #endif /* __MOZ_CONTAINER_WAYLAND_H__ */ diff --git a/widget/gtk/WaylandSurface.cpp b/widget/gtk/WaylandSurface.cpp @@ -117,6 +117,104 @@ WaylandSurface::~WaylandSurface() { "We can't release WaylandSurface with numap callback!"); } +void WaylandSurface::ReadyToDrawFrameCallbackHandler( + struct wl_callback* callback) { + LOGWAYLAND( + "WaylandSurface::ReadyToDrawFrameCallbackHandler() " + "mReadyToDrawFrameCallback %p mIsReadyToDraw %d initial_draw callback " + "%zd\n", + (void*)mReadyToDrawFrameCallback, (bool)mIsReadyToDraw, + mReadyToDrawCallbacks.size()); + + // We're supposed to run on main thread only. + AssertIsOnMainThread(); + + // mReadyToDrawFrameCallback/callback can be nullptr when redering directly + // to GtkWidget and ReadyToDrawFrameCallbackHandler is called by us from main + // thread by WaylandSurface::Map(). + MOZ_RELEASE_ASSERT(mReadyToDrawFrameCallback == callback); + + std::vector<std::function<void(void)>> cbs; + { + WaylandSurfaceLock lock(this); + MozClearPointer(mReadyToDrawFrameCallback, wl_callback_destroy); + // It's possible that we're already unmapped so quit in such case. + if (!mIsMapped) { + LOGWAYLAND(" WaylandSurface is unmapped, quit."); + if (!mReadyToDrawCallbacks.empty()) { + NS_WARNING("Unmapping WaylandSurface with active draw callback!"); + mReadyToDrawCallbacks.clear(); + } + return; + } + if (mIsReadyToDraw) { + return; + } + mIsReadyToDraw = true; + cbs = std::move(mReadyToDrawCallbacks); + + RequestFrameCallbackLocked(lock); + } + + // We can't call the callbacks under lock +#ifdef MOZ_LOGGING + int callbackNum = 0; +#endif + for (auto const& cb : cbs) { + LOGWAYLAND(" initial callback fire [%d]", callbackNum++); + cb(); + } +} + +static void ReadyToDrawFrameCallbackHandler(void* aWaylandSurface, + struct wl_callback* callback, + uint32_t time) { + auto* waylandSurface = static_cast<WaylandSurface*>(aWaylandSurface); + waylandSurface->ReadyToDrawFrameCallbackHandler(callback); +} + +static const struct wl_callback_listener + sWaylandSurfaceReadyToDrawFrameListener = { + ::ReadyToDrawFrameCallbackHandler}; + +void WaylandSurface::AddReadyToDrawCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function<void(void)>& aDrawCB) { + LOGVERBOSE("WaylandSurface::AddReadyToDrawCallbackLocked()"); + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + mReadyToDrawCallbacks.push_back(aDrawCB); +} + +void WaylandSurface::AddOrFireReadyToDrawCallback( + const std::function<void(void)>& aDrawCB) { + { + WaylandSurfaceLock lock(this); + if (!mIsReadyToDraw) { + LOGVERBOSE( + "WaylandSurface::AddOrFireReadyToDrawCallback() callback stored"); + mReadyToDrawCallbacks.push_back(aDrawCB); + return; + } + } + + LOGWAYLAND("WaylandSurface::AddOrFireReadyToDrawCallback() callback fire"); + + // We're ready to draw and we have a surface to draw into. + aDrawCB(); +} + +void WaylandSurface::ClearReadyToDrawCallbacksLocked( + const WaylandSurfaceLock& aProofOfLock) { + MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); + MozClearPointer(mReadyToDrawFrameCallback, wl_callback_destroy); + mReadyToDrawCallbacks.clear(); +} + +void WaylandSurface::ClearReadyToDrawCallbacks() { + WaylandSurfaceLock lock(this); + ClearReadyToDrawCallbacksLocked(lock); +} + bool WaylandSurface::HasEmulatedFrameCallbackLocked( const WaylandSurfaceLock& aProofOfLock) const { return mFrameCallbackHandler.IsSet() && mFrameCallbackHandler.mEmulated; @@ -135,7 +233,7 @@ void WaylandSurface::FrameCallbackHandler(struct wl_callback* aCallback, WaylandSurfaceLock lock(this); // Don't run emulated callbacks on hidden surfaces - if ((emulatedCallback || aRoutedFromChildSurface) && !mIsVisible) { + if ((emulatedCallback || aRoutedFromChildSurface) && !mIsReadyToDraw) { return; } @@ -161,7 +259,6 @@ void WaylandSurface::FrameCallbackHandler(struct wl_callback* aCallback, // We're getting regular frame callback from this surface so we must // have buffer attached. if (!emulatedCallback && !aRoutedFromChildSurface) { - mIsVisible = true; mBufferAttached = true; } @@ -381,7 +478,8 @@ bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, wl_surface* aParentWLSurface, WaylandSurfaceLock* aParentWaylandSurfaceLock, gfx::IntPoint aSubsurfacePosition, - bool aSubsurfaceDesync) { + bool aSubsurfaceDesync, + bool aUseReadyToDrawCallback) { LOGWAYLAND("WaylandSurface::MapLocked()"); MOZ_DIAGNOSTIC_ASSERT(&aProofOfLock == mSurfaceLock); MOZ_DIAGNOSTIC_ASSERT(!mIsMapped, "Already mapped?"); @@ -421,6 +519,14 @@ bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, LOGWAYLAND(" subsurface position [%d,%d]", (int)mSubsurfacePosition.x, (int)mSubsurfacePosition.y); + if (aUseReadyToDrawCallback) { + mReadyToDrawFrameCallback = wl_surface_frame(mParentSurface); + wl_callback_add_listener(mReadyToDrawFrameCallback, + &sWaylandSurfaceReadyToDrawFrameListener, this); + LOGWAYLAND(" created ready to draw frame callback ID %d\n", + wl_proxy_get_id((struct wl_proxy*)mReadyToDrawFrameCallback)); + } + LOGWAYLAND(" register frame callback"); RequestFrameCallbackLocked(aProofOfLock); @@ -450,7 +556,8 @@ bool WaylandSurface::MapLocked(const WaylandSurfaceLock& aProofOfLock, gfx::IntPoint aSubsurfacePosition) { return MapLocked(aProofOfLock, nullptr, aParentWaylandSurfaceLock, aSubsurfacePosition, - /* aSubsurfaceDesync */ false); + /* aSubsurfaceDesync */ false, + /* aUseReadyToDrawCallback */ false); } void WaylandSurface::SetUnmapCallbackLocked( @@ -502,11 +609,11 @@ void WaylandSurface::UnmapLocked(WaylandSurfaceLock& aSurfaceLock) { return; } mIsMapped = false; - mIsVisible = false; LOGWAYLAND("WaylandSurface::UnmapLocked()"); RemoveAttachedBufferLocked(aSurfaceLock); + ClearReadyToDrawCallbacksLocked(aSurfaceLock); ClearFrameCallbackLocked(aSurfaceLock); ClearScaleLocked(aSurfaceLock); @@ -526,6 +633,9 @@ void WaylandSurface::UnmapLocked(WaylandSurfaceLock& aSurfaceLock) { MozClearPointer(mPendingOpaqueRegion, wl_region_destroy); MozClearPointer(mOpaqueRegionFrameCallback, wl_callback_destroy); + mIsReadyToDraw = false; + mBufferAttached = false; + // Remove references to WaylandBuffers attached to mSurface, // we don't want to get any buffer release callback when we're unmapped. ReleaseAllWaylandTransactionsLocked(aSurfaceLock); diff --git a/widget/gtk/WaylandSurface.h b/widget/gtk/WaylandSurface.h @@ -43,6 +43,10 @@ class WaylandSurface final { void SetLoggingWidget(void* aWidget) { mLoggingWidget = aWidget; } #endif + void ReadyToDrawFrameCallbackHandler(struct wl_callback* aCallback); + void AddOrFireReadyToDrawCallback(const std::function<void(void)>& aDrawCB); + void ClearReadyToDrawCallbacks(); + void FrameCallbackHandler(struct wl_callback* aCallback, uint32_t aTime, bool aRoutedFromChildSurface); @@ -74,12 +78,11 @@ class WaylandSurface final { bool SetEGLWindowSize(LayoutDeviceIntSize aSize); bool HasEGLWindow() const { return !!mEGLWindow; } + // Read to draw means we got frame callback from parent surface + // where we attached to. + bool IsReadyToDraw() const { return mIsReadyToDraw; } // Mapped means we have all internals created. bool IsMapped() const { return mIsMapped; } - - // We've got first frame callback so we're really visible now. - bool IsVisible() const { return mIsVisible; } - // Indicate that Wayland surface uses Gdk resources which // need to be released on main thread by GdkCleanUpLocked(). // It may be called after Unmap() to make sure @@ -119,6 +122,10 @@ class WaylandSurface final { bool CreateViewportLocked(const WaylandSurfaceLock& aProofOfLock, bool aFollowsSizeChanges); + void AddReadyToDrawCallbackLocked( + const WaylandSurfaceLock& aProofOfLock, + const std::function<void(void)>& aInitialDrawCB); + // Attach WaylandBuffer which shows WaylandBuffer content // on screen. bool AttachLocked(const WaylandSurfaceLock& aSurfaceLock, @@ -274,7 +281,8 @@ class WaylandSurface final { bool MapLocked(const WaylandSurfaceLock& aProofOfLock, wl_surface* aParentWLSurface, WaylandSurfaceLock* aParentWaylandSurfaceLock, - gfx::IntPoint aSubsurfacePosition, bool aSubsurfaceDesync); + gfx::IntPoint aSubsurfacePosition, bool aSubsurfaceDesync, + bool aUseReadyToDrawCallback = true); void SetSizeLocked(const WaylandSurfaceLock& aProofOfLock, gfx::IntSize aSizeScaled, gfx::IntSize aUnscaledSize); @@ -296,18 +304,21 @@ class WaylandSurface final { bool HasEmulatedFrameCallbackLocked( const WaylandSurfaceLock& aProofOfLock) const; + void ClearReadyToDrawCallbacksLocked(const WaylandSurfaceLock& aProofOfLock); + void ClearScaleLocked(const WaylandSurfaceLock& aProofOfLock); // Weak ref to owning widget (nsWindow or NativeLayerWayland), // used for diagnostics/logging only. void* mLoggingWidget = nullptr; - // mIsMapped means we're supposed to be visible - // (or not if Wayland compositor decides so). + // WaylandSurface mapped - we have valid wl_surface where we can paint to. mozilla::Atomic<bool, mozilla::Relaxed> mIsMapped{false}; - // mIsVisible means we're really visible as we've got frame callback. - mozilla::Atomic<bool, mozilla::Relaxed> mIsVisible{false}; + // Wayland shows only subsurfaces of visible parent surfaces. + // mIsReadyToDraw means our parent wl_surface has content so + // this WaylandSurface can be visible on screen and get get frame callback. + mozilla::Atomic<bool, mozilla::Relaxed> mIsReadyToDraw{false}; // We used Gdk functions which needs clean up in main thread. mozilla::Atomic<bool, mozilla::Relaxed> mIsPendingGdkCleanup{false}; @@ -374,6 +385,11 @@ class WaylandSurface final { bool mBufferTransformFlippedX = false; bool mBufferTransformFlippedY = false; + // Frame callback registered to parent surface. When we get it we know + // parent surface is ready and we can paint. + wl_callback* mReadyToDrawFrameCallback = nullptr; + std::vector<std::function<void(void)>> mReadyToDrawCallbacks; + // Frame callbacks of this surface wl_callback* mFrameCallback = nullptr; diff --git a/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp @@ -156,7 +156,9 @@ WindowSurfaceWaylandMB::WindowSurfaceWaylandMB( RefPtr<nsWindow> aWindow, GtkCompositorWidget* aCompositorWidget) : mSurfaceLock("WindowSurfaceWayland lock"), mWindow(std::move(aWindow)), - mCompositorWidget(aCompositorWidget) {} + mCompositorWidget(aCompositorWidget), + mFrameInProcess(false), + mCallbackRequested(false) {} bool WindowSurfaceWaylandMB::MaybeUpdateWindowSize() { // We want to get window size from compositor widget as it matches window @@ -187,6 +189,7 @@ already_AddRefed<DrawTarget> WindowSurfaceWaylandMB::Lock( if (mWindow->GetWindowType() == WindowType::Invisible) { return nullptr; } + mFrameInProcess = true; CollectPendingSurfaces(lock); @@ -278,10 +281,30 @@ void WindowSurfaceWaylandMB::Commit( // invisible window return; } + mFrameInProcess = false; MozContainer* container = mWindow->GetMozContainer(); WaylandSurface* waylandSurface = MOZ_WL_SURFACE(container); WaylandSurfaceLock lock(waylandSurface); + if (!waylandSurface->IsMapped()) { + LOGWAYLAND( + "WindowSurfaceWaylandMB::Commit [%p] frame queued: can't lock " + "wl_surface\n", + (void*)mWindow.get()); + if (!mCallbackRequested) { + RefPtr<WindowSurfaceWaylandMB> self(this); + waylandSurface->AddReadyToDrawCallbackLocked( + lock, [self, aInvalidRegion]() -> void { + MutexAutoLock lock(self->mSurfaceLock); + if (!self->mFrameInProcess) { + self->Commit(lock, aInvalidRegion); + } + self->mCallbackRequested = false; + }); + mCallbackRequested = true; + } + return; + } waylandSurface->InvalidateRegionLocked(lock, aInvalidRegion.ToUnknownRegion()); diff --git a/widget/gtk/WindowSurfaceWaylandMultiBuffer.h b/widget/gtk/WindowSurfaceWaylandMultiBuffer.h @@ -73,6 +73,10 @@ class WindowSurfaceWaylandMB : public WindowSurface { nsTArray<RefPtr<WaylandBufferSHM>> mInUseBuffers; nsTArray<RefPtr<WaylandBufferSHM>> mPendingBuffers; nsTArray<RefPtr<WaylandBufferSHM>> mAvailableBuffers; + + // delayed commits + bool mFrameInProcess; + bool mCallbackRequested; }; } // namespace mozilla::widget diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp @@ -411,7 +411,8 @@ static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) { } nsWindow::nsWindow() - : mIsMapped(false), + : mWindowVisibilityMutex("nsWindow::mWindowVisibilityMutex"), + mIsMapped(false), mIsDestroyed(false), mIsShown(false), mNeedsShow(false), @@ -734,14 +735,6 @@ void nsWindow::Destroy() { gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr); } - // Owned by WaylandSurface or it's X11 ID, - // just drop the reference here. - mEGLWindow = nullptr; - - // mGdkWindow is owned by mContainer, will be deleted with mShell/mContainer. - g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr); - mGdkWindow = nullptr; - gtk_widget_destroy(mShell); mShell = nullptr; mContainer = nullptr; @@ -749,6 +742,9 @@ void nsWindow::Destroy() { mSurface = nullptr; #endif + MOZ_ASSERT(!mGdkWindow, + "mGdkWindow should be NULL when mContainer is destroyed"); + #ifdef ACCESSIBILITY if (mRootAccessible) { mRootAccessible = nullptr; @@ -1391,6 +1387,15 @@ void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide, if (mPopupClosed) { LOG(" Clearing mMoveToRectPopupSize\n"); mMoveToRectPopupSize = {}; +#ifdef MOZ_WAYLAND + if (moz_container_wayland_is_waiting_to_show(mContainer)) { + // We need to clear rendering queue, see Bug 1782948. + LOG(" popup failed to show by Wayland compositor, clear rendering " + "queue."); + moz_container_wayland_clear_waiting_to_show_flag(mContainer); + ClearRenderingQueue(); + } +#endif } } @@ -1451,7 +1456,8 @@ void nsWindow::WaylandPopupCloseOrphanedPopups() { nsWindow* popup = mWaylandPopupNext; bool dangling = false; while (popup) { - if (!dangling && !MOZ_WL_SURFACE(popup->GetMozContainer())->IsVisible()) { + if (!dangling && + moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) { LOG(" popup [%p] is waiting to show, close all child popups", popup); dangling = true; } else if (dangling) { @@ -3760,8 +3766,41 @@ void* nsWindow::GetNativeData(uint32_t aDataType) { } case NS_NATIVE_OPENGL_CONTEXT: return nullptr; - case NS_NATIVE_EGL_WINDOW: - return mIsDestroyed ? nullptr : mEGLWindow; + case NS_NATIVE_EGL_WINDOW: { + // On X11 we call it: + // 1) If window is mapped on OnMap() by nsWindow::ResumeCompositorImpl(), + // new EGLSurface/XWindow is created. + // 2) If window is hidden on OnUnmap(), we replace EGLSurface/XWindow + // by offline surface and release XWindow. + + // On Wayland it: + // 1) If window is mapped on OnMap(), we request frame callback + // at MozContainer. If we get frame callback at MozContainer, + // nsWindow::ResumeCompositorImpl() is called from it + // and EGLSurface/wl_surface is created. + // 2) If window is hidden on OnUnmap(), we replace EGLSurface/wl_surface + // by offline surface and release XWindow. + + // If nsWindow is already destroyed, don't try to get EGL window at all, + // we're going to be deleted anyway. + MutexAutoLock lock(mWindowVisibilityMutex); + void* eglWindow = nullptr; + if (mIsMapped && !mIsDestroyed) { +#ifdef MOZ_X11 + if (GdkIsX11Display()) { + eglWindow = (void*)GDK_WINDOW_XID(mGdkWindow); + } +#endif +#ifdef MOZ_WAYLAND + if (GdkIsWaylandDisplay()) { + eglWindow = moz_container_wayland_get_egl_window(mContainer); + } +#endif + } + LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p", + mGdkWindow, eglWindow); + return eglWindow; + } default: NS_WARNING("nsWindow::GetNativeData called with bad value"); return nullptr; @@ -4090,10 +4129,17 @@ gboolean nsWindow::OnExposeEvent(cairo_t* cr) { } // Windows that are not visible will be painted after they become visible. - if (!mHasMappedToplevel) { - LOG("quit, !mHasMappedToplevel"); + if (!mGdkWindow || !mHasMappedToplevel) { + LOG("quit, !mGdkWindow || !mHasMappedToplevel"); return FALSE; } +#ifdef MOZ_WAYLAND + if (!mIsDragPopup && GdkIsWaylandDisplay() && + !moz_container_wayland_can_draw(mContainer)) { + LOG("quit, !moz_container_wayland_can_draw()"); + return FALSE; + } +#endif if (!GetListener()) { LOG("quit, !GetListener()"); @@ -4125,8 +4171,8 @@ gboolean nsWindow::OnExposeEvent(cairo_t* cr) { // If the window has been destroyed during the will paint notification, // there is nothing left to do. - if (mIsDestroyed) { - LOG("quit, mIsDestroyed"); + if (!mGdkWindow || mIsDestroyed) { + LOG("quit, !mGdkWindow || mIsDestroyed"); return TRUE; } @@ -4279,7 +4325,7 @@ gboolean nsWindow::OnShellConfigureEvent(GdkEventConfigure* aEvent) { // Don't fire configure event for scale changes, we handle that // OnScaleEvent event. Skip that for toplevel windows only. - if (IsTopLevelWidget() && + if (mGdkWindow && IsTopLevelWidget() && mCeiledScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) { LOG(" scale factor changed to %d, return early", gdk_window_get_scale_factor(mGdkWindow)); @@ -4305,6 +4351,10 @@ void nsWindow::OnContainerSizeAllocate(GtkAllocation* aAllocation) { mReceivedClientArea = DesktopIntRect(aAllocation->x, aAllocation->y, aAllocation->width, aAllocation->height); + if (!mGdkWindow) { + return; + } + // Bounds will get updated on the main configure. // Gecko permits running nested event loops during processing of events, // GtkWindow callers of gtk_widget_size_allocate expect the signal handlers @@ -4575,6 +4625,10 @@ void nsWindow::EmulateResizeDrag(GdkEventMotion* aEvent) { void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) { mLastMouseCoordinates.Set(aEvent); + if (!mGdkWindow) { + return; + } + // Emulate gdk_window_begin_resize_drag() for windows // with fixed aspect ratio on Wayland. if (mAspectResizer && mAspectRatio != 0.0f) { @@ -4591,8 +4645,10 @@ void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) { GdkWindow* dragWindow = nullptr; // find the top-level window - dragWindow = gdk_window_get_toplevel(mGdkWindow); - MOZ_ASSERT(dragWindow, "gdk_window_get_toplevel should not return null"); + if (mGdkWindow) { + dragWindow = gdk_window_get_toplevel(mGdkWindow); + MOZ_ASSERT(dragWindow, "gdk_window_get_toplevel should not return null"); + } #ifdef MOZ_X11 if (dragWindow && GdkIsX11Display()) { @@ -5027,6 +5083,10 @@ void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) { SetLastPointerDownEvent(nullptr); mLastMouseCoordinates.Set(aEvent); + if (!mGdkWindow) { + return; + } + if (mAspectResizer) { mAspectResizer = Nothing(); return; @@ -5206,7 +5266,7 @@ WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) { } TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) { - if (MOZ_UNLIKELY(mIsDestroyed)) { + if (MOZ_UNLIKELY(!mGdkWindow)) { // nsWindow has been Destroy()ed. return TimeStamp::Now(); } @@ -5242,6 +5302,7 @@ TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) { #ifdef MOZ_X11 mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() { + MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set"); if (MOZ_UNLIKELY(!mCurrentTimeGetter)) { mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow); } @@ -5697,7 +5758,7 @@ void nsWindow::OnCompositedChanged() { // Let's follow the working scenario for now to avoid complexity // and maybe fix that later. void nsWindow::OnScaleEvent() { - if (!IsTopLevelWidget()) { + if (!mGdkWindow || !IsTopLevelWidget()) { return; } @@ -5715,6 +5776,7 @@ void nsWindow::RefreshScale(bool aRefreshScreen, bool aForceRefresh) { LOG("nsWindow::RefreshScale() GdkWindow scale %d refresh %d", gdk_window_get_scale_factor(mGdkWindow), aRefreshScreen); + MOZ_DIAGNOSTIC_ASSERT(mIsMapped && mGdkWindow); int ceiledScale = gdk_window_get_scale_factor(mGdkWindow); const bool scaleChanged = aForceRefresh || GdkCeiledScaleFactor() != ceiledScale; @@ -5761,7 +5823,7 @@ void nsWindow::SetDragPopupSurface( mDragPopupSurface = aDragPopupSurface; mDragPopupSurfaceRegion = aInvalidRegion; - if (!mIsDestroyed) { + if (mGdkWindow) { gdk_window_invalidate_rect(mGdkWindow, nullptr, false); } } @@ -6172,26 +6234,51 @@ nsCString nsWindow::GetPopupTypeName() { Window nsWindow::GetX11Window() { #ifdef MOZ_X11 if (GdkIsX11Display()) { - return gdk_x11_window_get_xid(mGdkWindow); + return mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow) : X11None; } #endif return (Window) nullptr; } +void nsWindow::EnsureGdkWindow() { + MOZ_DIAGNOSTIC_ASSERT(mIsMapped); + if (!mGdkWindow) { + mGdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer)); + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this); + } +} + void nsWindow::ConfigureCompositor() { + MOZ_DIAGNOSTIC_ASSERT(mIsMapped); + LOG("nsWindow::ConfigureCompositor()"); + auto startCompositing = [self = RefPtr{this}, this]() -> void { + LOG(" moz_container_wayland_add_or_fire_initial_draw_callback " + "ConfigureCompositor"); + + // too late + if (mIsDestroyed || !mIsMapped) { + LOG(" quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed, + !!mIsMapped); + return; + } + // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate(). + if (!mCompositorWidgetDelegate) { + LOG(" quit, missing mCompositorWidgetDelegate"); + return; + } - if (mIsDestroyed) { - LOG(" quit, mIsDestroyed = %d", !!mIsDestroyed); - return; - } - // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate(). - if (!mCompositorWidgetDelegate) { - LOG(" quit, missing mCompositorWidgetDelegate"); - return; - } + ResumeCompositorImpl(); + }; - ResumeCompositorImpl(); + if (GdkIsWaylandDisplay()) { +#ifdef MOZ_WAYLAND + moz_container_wayland_add_or_fire_initial_draw_callback(mContainer, + startCompositing); +#endif + } else { + startCompositing(); + } } nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect, @@ -6445,24 +6532,6 @@ nsresult nsWindow::Create(nsIWidget* aParent, const LayoutDeviceIntRect& aRect, gtk_widget_realize(container); - mGdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer)); - g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this); - -#ifdef MOZ_X11 - if (GdkIsX11Display()) { - mEGLWindow = (void*)GDK_WINDOW_XID(mGdkWindow); - } -#endif -#ifdef MOZ_WAYLAND - if (GdkIsWaylandDisplay() && mIsAccelerated) { - mEGLWindow = MOZ_WL_SURFACE(container)->GetEGLWindow(mClientArea.Size()); - } -#endif - if (mEGLWindow) { - LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned mEGLWindow %p", - mGdkWindow, mEGLWindow); - } - // make sure this is the focus widget in the container gtk_widget_show(container); @@ -9902,7 +9971,7 @@ bool nsWindow::SetEGLNativeWindowSize( // SetEGLNativeWindowSize() is Wayland only call. MOZ_ASSERT(GdkIsWaylandDisplay()); - if (mIsDestroyed) { + if (!mIsMapped) { return true; } @@ -9931,6 +10000,15 @@ nsWindow* nsWindow::GetWindow(GdkWindow* window) { return get_window_for_gdk_window(window); } +void nsWindow::ClearRenderingQueue() { + LOG("nsWindow::ClearRenderingQueue()"); + + if (mWidgetListener) { + mWidgetListener->RequestWindowClose(this); + } + DestroyLayerManager(); +} + // nsWindow::OnMap() / nsWindow::OnUnmap() is called from map/unmap mContainer // handlers directly as we paint to mContainer. void nsWindow::OnMap() { @@ -9939,8 +10017,10 @@ void nsWindow::OnMap() { MaybeCreatePipResources(); { + MutexAutoLock lock(mWindowVisibilityMutex); mIsMapped = true; + EnsureGdkWindow(); RefreshScale(/* aRefreshScreen */ false); if (mIsAlert) { @@ -9992,6 +10072,7 @@ void nsWindow::OnUnmap() { ClearPipResources(); { + MutexAutoLock lock(mWindowVisibilityMutex); mIsMapped = false; mHasReceivedSizeAllocate = false; @@ -10005,8 +10086,23 @@ void nsWindow::OnUnmap() { } } + if (mGdkWindow) { + g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr); + mGdkWindow = nullptr; + } + // Reset scale for hidden windows mCeiledScaleFactor = sNoScale; + + // Clear resources (mainly XWindow) stored at GtkCompositorWidget. + // It makes sure we don't paint to it when nsWindow becomes hiden/deleted + // and XWindow is released. + if (mCompositorWidgetDelegate) { + mCompositorWidgetDelegate->CleanupResources(); + } + + // Clear nsWindow resources used for old (in-thread) rendering. + mSurfaceProvider.CleanupResources(); } // Until bug 1654938 is fixed we delete layer manager for hidden popups, @@ -10017,6 +10113,28 @@ void nsWindow::OnUnmap() { // see bug 1958695. if (mWindowType == WindowType::Popup && !mPopupTemporaryHidden) { DestroyLayerManager(); + } else { + // Widget is backed by OpenGL EGLSurface created over wl_surface/XWindow. + // + // RenderCompositorEGL::Resume() deletes recent EGLSurface, + // calls nsWindow::GetNativeData(NS_NATIVE_EGL_WINDOW) from compositor + // thread to get new native rendering surface. + // + // For hidden/unmapped windows we return nullptr NS_NATIVE_EGL_WINDOW at + // nsWindow::GetNativeData() so RenderCompositorEGL::Resume() creates + // offscreen fallback EGLSurface to avoid compositor pause. + // + // We don't want to pause compositor as it may lead to whole + // browser freeze (Bug 1777664). + // + // If RenderCompositorSWGL compositor is used (SW fallback) + // RenderCompositorSWGL::Resume() only requests full render for next paint + // as wl_surface/XWindow is managed by WindowSurfaceProvider owned + // directly by GtkCompositorWidget and that's covered by + // mCompositorWidgetDelegate->CleanupResources() call above. + if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) { + remoteRenderer->SendResume(); + } } } diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h @@ -501,6 +501,10 @@ class nsWindow final : public nsIWidget { void ResumeCompositorImpl(); + // Force hide this window, remove compositor etc. to avoid + // rendering queue blocking (see Bug 1782948). + void ClearRenderingQueue(); + bool ApplyEnterLeaveMutterWorkaround(); void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override; @@ -551,6 +555,7 @@ class nsWindow final : public nsIWidget { GtkWidget* GetToplevelWidget() const; nsWindow* GetContainerWindow() const; Window GetX11Window(); + void EnsureGdkWindow(); void SetUrgencyHint(GtkWidget* top_window, bool state); void SetDefaultIcon(void); void SetWindowDecoration(BorderStyle aStyle); @@ -595,8 +600,6 @@ class nsWindow final : public nsIWidget { GtkWidget* mShell = nullptr; MozContainer* mContainer = nullptr; GdkWindow* mGdkWindow = nullptr; - // mEGLWindow is owned by WaylandSurface or it's X11 ID. - void* mEGLWindow = nullptr; #ifdef MOZ_WAYLAND RefPtr<mozilla::widget::WaylandSurface> mSurface; #endif @@ -705,6 +708,9 @@ class nsWindow final : public nsIWidget { // If true, draw our own window titlebar. bool mDrawInTitlebar = false; + // This mutex protect window visibility changes. + mozilla::Mutex mWindowVisibilityMutex; + // This track real window visibility from OS perspective. // It's set by OnMap/OnUnmap which is based on Gtk events. mozilla::Atomic<bool, mozilla::Relaxed> mIsMapped;