Commit 07529434 authored by stransky's avatar stransky
Browse files

Bug 1904424 [Linux/X11] Don't steal focus on X11 r=emilio a=RyanVM

The patch comes from https://gitlab.com/redhat/centos-stream/rpms/firefox/-/merge_requests/94

Firefox isn't currently handling window focusing right on X11... There's a few problems:

If no startup id is provided, it tries to get user time from g_get_monotonic_time when there's no guarantee that timestamp is synchronized or even in a compatible unit. This merge request addressed that by dropping the code that uses g_get_monotonic_time entirely, and instead adding a heuristic to synthesize a startup id based on the time of the last user interaction on the display. This makes an assumption that firefox was started as a result of user interaction, which might not always hold true, but it's more likely than not, and is basically the same assumption firefox is already making by trying to use a timestamp of "now" (g_get_monotonic_time) when focusing the window.

If a startup id is provided, it fails to tell gtk to use it when focusing the window because it calls gtk_window_present_with_time with some other random timestamp instead of passing GDK_CURRENT_TIME (which is the magic value that means "use startup id for timestamp"). This commit tries to detect when a startup id is available, and the display is X11, and if so use a timestamp of GDK_CURRENT_TIME.

Differential Revision: https://phabricator.services.mozilla.com/D217174
parent 1a694701
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -47,11 +47,7 @@ bool nsDBusRemoteServer::HandleOpenURL(const gchar* aInterfaceName,
    return false;
  }

  guint32 timestamp = gtk_get_current_event_time();
  if (timestamp == GDK_CURRENT_TIME) {
    timestamp = guint32(g_get_monotonic_time() / 1000);
  }
  HandleCommandLine(aParam, timestamp);
  HandleCommandLine(aParam, gtk_get_current_event_time());
  return true;
}

+5 −1
Original line number Diff line number Diff line
@@ -4943,8 +4943,12 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
      // Try to remote the entire command line. If this fails, start up
      // normally.
#  ifdef MOZ_WIDGET_GTK
      const auto& startupToken =
      auto& startupToken =
          GdkIsWaylandDisplay() ? mXDGActivationToken : mDesktopStartupID;
#    ifdef MOZ_X11
      if (GdkIsX11Display() && startupToken.IsEmpty())
        startupToken = SynthesizeStartupToken();
#    endif /* MOZ_X11 */
#  else
      const nsCString startupToken;
#  endif
+71 −0
Original line number Diff line number Diff line
@@ -28,6 +28,11 @@
#include <dlfcn.h>
#include <glib.h>

#ifdef MOZ_X11
#  include <X11/Xlib.h>
#  include <X11/Xatom.h>
#endif /* MOZ_X11 */

#undef LOGW
#ifdef MOZ_LOGGING
#  include "mozilla/Logging.h"
@@ -498,4 +503,70 @@ bool IsCancelledGError(GError* aGError) {
  return g_error_matches(aGError, G_IO_ERROR, G_IO_ERROR_CANCELLED);
}

#if defined(MOZ_X11)
static unsigned long GetWindowUserTime(GdkDisplay* aDisplay,
                                       uintptr_t aWindow) {
  Atom actualType;
  int actualFormat;
  unsigned long numberOfItems;
  unsigned long bytesAfter;
  unsigned char* property = nullptr;
  unsigned long userTime = 0;

  Display* xDisplay = GDK_DISPLAY_XDISPLAY(aDisplay);
  Atom atom =
      gdk_x11_get_xatom_by_name_for_display(aDisplay, "_NET_WM_USER_TIME");

  if (XGetWindowProperty(xDisplay, aWindow, atom, 0, 1, false, XA_CARDINAL,
                         &actualType, &actualFormat, &numberOfItems,
                         &bytesAfter, &property) == Success &&
      property) {
    if (numberOfItems == 1) {
      userTime = *((unsigned long*)property);
    }
    XFree(property);
  }

  return userTime;
}

void FindLatestUserTime(GdkDisplay* aDisplay, uintptr_t aWindow,
                        unsigned long* aLatestTime) {
  Window rootReturn;
  Window parentReturn;
  Window* children;
  unsigned int numberOfChildren;
  unsigned long userTime;

  Display* xDisplay = GDK_DISPLAY_XDISPLAY(aDisplay);

  if (XQueryTree(xDisplay, aWindow, &rootReturn, &parentReturn, &children,
                 &numberOfChildren)) {
    for (unsigned int i = 0; i < numberOfChildren; i++) {
      userTime = GetWindowUserTime(aDisplay, children[i]);
      if (userTime > *aLatestTime) {
        *aLatestTime = userTime;
      }
      FindLatestUserTime(aDisplay, children[i], aLatestTime);
    }

    XFree(children);
  }
}

// Assume we're started from user interaction and infer user time if its missing
nsCString SynthesizeStartupToken() {
  unsigned long latestUserTime = 0;
  FindLatestUserTime(gdk_display_get_default(),
                     GDK_WINDOW_XID(gdk_get_default_root_window()),
                     &latestUserTime);

  if (latestUserTime == 0) {
    return nsCString();
  }

  return nsPrintfCString("%s_TIME%lu", g_get_host_name(), latestUserTime);
}
#endif

}  // namespace mozilla::widget
+7 −1
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@
#include "nsString.h"
#include "nsTArray.h"
#include "mozilla/MozPromise.h"

#include <stdint.h>

typedef struct _GdkDisplay GdkDisplay;
@@ -78,6 +77,13 @@ RefPtr<FocusRequestPromise> RequestWaylandFocusPromise();

bool IsCancelledGError(GError* aGError);

#if defined(MOZ_X11)
// Used by startup notifications
nsCString SynthesizeStartupToken();
void FindLatestUserTime(GdkDisplay* aDisplay, uintptr_t aWindow,
                        unsigned long* aLatestTime);
#endif

}  // namespace mozilla::widget

#endif  // WidgetUtilsGtk_h__
+12 −0
Original line number Diff line number Diff line
@@ -3102,6 +3102,18 @@ void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
            return t;
          }
        }
#if defined(MOZ_X11)
        // If it's X11 and there's a startup token, use GDK_CURRENT_TIME, so
        // gtk_window_present_with_time will pull the timestamp from the startup
        // token.
        if (GdkIsX11Display()) {
          nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
          const auto& startupToken = toolkit->GetStartupToken();
          if (!startupToken.IsEmpty()) {
            return static_cast<uint32_t>(GDK_CURRENT_TIME);
          }
        }
#endif
        return GetLastUserInputTime();
      }();