/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsWaylandDisplay.h"
#include "DMABufFormats.h"

#include "base/message_loop.h"    // for MessageLoop
#include "base/task.h"            // for NewRunnableMethod, etc
#include "mozilla/gfx/Logging.h"  // for gfxCriticalNote
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/StaticPrefs_general.h"
#include "mozilla/Sprintf.h"
#include "WidgetUtilsGtk.h"
#include "nsGtkKeyUtils.h"
#include "nsGtkUtils.h"
#include "nsLayoutUtils.h"
#include "nsWindow.h"
#include "wayland-proxy.h"
#include "ScreenHelperGTK.h"
#include "nsIAppStartup.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"

#include <dlfcn.h>

#undef LOG
#undef LOG_VERBOSE
#ifdef MOZ_LOGGING
#  include "mozilla/Logging.h"
#  include "nsTArray.h"
#  include "Units.h"
extern mozilla::LazyLogModule gWidgetWaylandLog;
#  define LOG(...) \
    MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
#  define LOG_VERBOSE(...) \
    MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Verbose, (__VA_ARGS__))
#else
#  define LOG(...)
#  define LOG_VERBOSE(...)
#endif /* MOZ_LOGGING */

namespace mozilla::widget {

static nsWaylandDisplay* gWaylandDisplay;

void WaylandDisplayRelease() {
  MOZ_RELEASE_ASSERT(NS_IsMainThread(),
                     "WaylandDisplay can be released in main thread only!");
  if (!gWaylandDisplay) {
    return;
  }
  LOG("WaylandDisplayRelease()");
  delete gWaylandDisplay;
  gWaylandDisplay = nullptr;
}

wl_display* WaylandDisplayGetWLDisplay() {
  GdkDisplay* disp = gdk_display_get_default();
  if (!GdkIsWaylandDisplay(disp)) {
    return nullptr;
  }
  return gdk_wayland_display_get_wl_display(disp);
}

nsWaylandDisplay* WaylandDisplayGet() {
  if (!gWaylandDisplay) {
    MOZ_RELEASE_ASSERT(NS_IsMainThread(),
                       "WaylandDisplay can be created in main thread only!");
    wl_display* waylandDisplay = WaylandDisplayGetWLDisplay();
    if (!waylandDisplay) {
      return nullptr;
    }
    // We're setting Wayland client buffer size here (i.e. our write buffer).
    // Server buffer size is set by compositor and we may use the same buffer
    // sizes on both sides. Mutter uses 1024 * 1024 (1M) so let's use the same
    // value.
    wl_display_set_max_buffer_size(waylandDisplay, 1024 * 1024);
    gWaylandDisplay = new nsWaylandDisplay(waylandDisplay);
    gWaylandDisplay->Init();
  }
  return gWaylandDisplay;
}

void nsWaylandDisplay::SetShm(wl_shm* aShm) { mShm = aShm; }

class WaylandPointerEvent {
 public:
  constexpr WaylandPointerEvent() = default;

  RefPtr<nsWindow> TakeWindow(wl_surface* aSurface) {
    if (!aSurface) {
      mWindow = nullptr;
    } else {
      GdkWindow* window =
          static_cast<GdkWindow*>(wl_surface_get_user_data(aSurface));
      mWindow = window ? static_cast<nsWindow*>(
                             g_object_get_data(G_OBJECT(window), "nsWindow"))
                       : nullptr;
    }
    return mWindow;
  }
  already_AddRefed<nsWindow> GetAndClearWindow() { return mWindow.forget(); }
  RefPtr<nsWindow> GetWindow() { return mWindow; }

  void SetSource(int32_t aSource) { mSource = aSource; }

  void SetDelta120(uint32_t aAxis, int32_t aDelta) {
    switch (aAxis) {
      case WL_POINTER_AXIS_VERTICAL_SCROLL:
        mDeltaY = aDelta / 120.0f;
        break;
      case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
        mDeltaX = aDelta / 120.0f;
        break;
      default:
        NS_WARNING("WaylandPointerEvent::SetDelta120(): wrong axis!");
        break;
    }
  }

  void SetTime(uint32_t aTime) { mTime = aTime; }

  void SendScrollEvent() {
    if (!mWindow || !nsLayoutUtils::IsSmoothScrollingEnabled()) {
      return;
    }

    // nsWindow::OnSmoothScrollEvent() may spin event loop so
    // mWindow/mSource/delta may be replaced.
    int32_t source = mSource;
    float deltaX = mDeltaX;
    float deltaY = mDeltaY;

    mSource = -1;
    mDeltaX = mDeltaY = 0.0f;

    // We process wheel events only now.
    if (source != WL_POINTER_AXIS_SOURCE_WHEEL) {
      return;
    }

    RefPtr<nsWindow> win = mWindow;
    uint32_t eventTime = mTime;
    win->OnSmoothScrollEvent(eventTime, deltaX, deltaY);
  }

  void Clear() { mWindow = nullptr; }

 private:
  StaticRefPtr<nsWindow> mWindow;
  uint32_t mTime = 0;
  int32_t mSource = 0;
  float mDeltaX = 0;
  float mDeltaY = 0;
};

static WaylandPointerEvent sHoldGesture;

static void gesture_hold_begin(void* data,
                               struct zwp_pointer_gesture_hold_v1* hold,
                               uint32_t serial, uint32_t time,
                               struct wl_surface* surface, uint32_t fingers) {
  RefPtr<nsWindow> window = sHoldGesture.TakeWindow(surface);
  if (!window) {
    return;
  }
  window->OnTouchpadHoldEvent(GDK_TOUCHPAD_GESTURE_PHASE_BEGIN, time, fingers);
}

static void gesture_hold_end(void* data,
                             struct zwp_pointer_gesture_hold_v1* hold,
                             uint32_t serial, uint32_t time,
                             int32_t cancelled) {
  RefPtr<nsWindow> window = sHoldGesture.GetAndClearWindow();
  if (!window) {
    return;
  }
  window->OnTouchpadHoldEvent(cancelled ? GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
                                        : GDK_TOUCHPAD_GESTURE_PHASE_END,
                              time, 0);
}

static const struct zwp_pointer_gesture_hold_v1_listener gesture_hold_listener =
    {gesture_hold_begin, gesture_hold_end};

static WaylandPointerEvent sScrollEvent;

uint32_t gLastSerial = 0;
uint32_t nsWaylandDisplay::GetLastEventSerial() { return gLastSerial; }

static void pointer_handle_enter(void* data, struct wl_pointer* pointer,
                                 uint32_t serial, struct wl_surface* surface,
                                 wl_fixed_t sx, wl_fixed_t sy) {
  sScrollEvent.TakeWindow(surface);
}

static void pointer_handle_leave(void* data, struct wl_pointer* pointer,
                                 uint32_t serial, struct wl_surface* surface) {
  sScrollEvent.Clear();
}

static void pointer_handle_motion(void* data, struct wl_pointer* pointer,
                                  uint32_t time, wl_fixed_t sx, wl_fixed_t sy) {
}

static void pointer_handle_button(void* data, struct wl_pointer* pointer,
                                  uint32_t serial, uint32_t time,
                                  uint32_t button, uint32_t state) {
  if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
    gLastSerial = serial;
  }
}

static void pointer_handle_axis(void* data, struct wl_pointer* pointer,
                                uint32_t time, uint32_t axis,
                                wl_fixed_t value) {
  sScrollEvent.SetTime(time);
}

static void pointer_handle_frame(void* data, struct wl_pointer* pointer) {
  sScrollEvent.SendScrollEvent();
}

static void pointer_handle_axis_source(
    void* data, struct wl_pointer* pointer,
    /*enum wl_pointer_axis_source */ uint32_t source) {
  sScrollEvent.SetSource(source);
}

static void pointer_handle_axis_stop(void* data, struct wl_pointer* pointer,
                                     uint32_t time, uint32_t axis) {}

static void pointer_handle_axis_discrete(void* data, struct wl_pointer* pointer,
                                         uint32_t axis, int32_t value) {}

static void pointer_handle_axis_value120(void* data, struct wl_pointer* pointer,
                                         uint32_t axis, int32_t value) {
  sScrollEvent.SetDelta120(axis, value);
}

/*
 * Example of scroll events we get for various devices. Note that
 * even three different devices has the same wl_pointer.
 *
 * Standard mouse wheel:
 *
 *  pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 0
 *  pointer_handle_axis_value120 pointer 0x7fd14fd4bac0 value 120
 *  pointer_handle_axis pointer 0x7fd14fd4bac0 time 9470441 value 10.000000
 *  pointer_handle_frame
 *
 * Hi-res mouse wheel:
 *
 * pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 0
 * pointer_handle_axis_value120 pointer 0x7fd14fd4bac0 value -24
 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9593205 value -1.992188
 * pointer_handle_frame
 *
 * Touchpad:
 *
 * pointer_handle_axis_source pointer 0x7fd14fd4bac0 source 1
 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9431830 value 0.312500
 * pointer_handle_axis pointer 0x7fd14fd4bac0 time 9431830 value -1.015625
 * pointer_handle_frame
 */

static const struct moz_wl_pointer_listener pointer_listener = {
    pointer_handle_enter,         pointer_handle_leave,
    pointer_handle_motion,        pointer_handle_button,
    pointer_handle_axis,          pointer_handle_frame,
    pointer_handle_axis_source,   pointer_handle_axis_stop,
    pointer_handle_axis_discrete, pointer_handle_axis_value120,
};

void nsWaylandDisplay::SetPointer(wl_pointer* aPointer) {
  // Don't even try on such old interface
  if (wl_proxy_get_version((struct wl_proxy*)aPointer) <
      WL_POINTER_RELEASE_SINCE_VERSION) {
    return;
  }

  MOZ_DIAGNOSTIC_ASSERT(!mPointer);
  mPointer = aPointer;

  // We're interested in pointer_handle_axis_value120() only for now.
  if (wl_proxy_get_version((struct wl_proxy*)aPointer) >=
      WL_POINTER_AXIS_VALUE120_SINCE_VERSION) {
    wl_pointer_add_listener(
        mPointer, (const wl_pointer_listener*)&pointer_listener, this);
  }

  // mPointerGestures is set by zwp_pointer_gestures_v1 if we have it.
  if (mPointerGestures) {
    mPointerGestureHold =
        zwp_pointer_gestures_v1_get_hold_gesture(mPointerGestures, mPointer);
    zwp_pointer_gesture_hold_v1_set_user_data(mPointerGestureHold, this);
    zwp_pointer_gesture_hold_v1_add_listener(mPointerGestureHold,
                                             &gesture_hold_listener, this);
  }
}

void nsWaylandDisplay::RemovePointer() {
  wl_pointer_release(mPointer);
  mPointer = nullptr;
}

static void seat_handle_capabilities(void* data, struct wl_seat* seat,
                                     unsigned int caps) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  if (!display) {
    return;
  }

  if ((caps & WL_SEAT_CAPABILITY_POINTER) && !display->GetPointer()) {
    display->SetPointer(wl_seat_get_pointer(seat));
  } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && display->GetPointer()) {
    display->RemovePointer();
  }

  wl_keyboard* keyboard = display->GetKeyboard();
  if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
    display->SetKeyboard(wl_seat_get_keyboard(seat));
  } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
    display->ClearKeyboard();
  }

  wl_touch* touch = display->GetTouch();
  if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !touch) {
    display->SetTouch(wl_seat_get_touch(seat));
  } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && touch) {
    display->ClearTouch();
  }
}

static void seat_handle_name(void* data, struct wl_seat* seat,
                             const char* name) {
  /* We don't care about the name. */
}

static const struct wl_seat_listener seat_listener = {
    seat_handle_capabilities,
    seat_handle_name,
};

void nsWaylandDisplay::SetSeat(wl_seat* aSeat, int aSeatId) {
  mSeat = aSeat;
  mSeatId = aSeatId;
  wl_seat_add_listener(aSeat, &seat_listener, this);
}

void nsWaylandDisplay::RemoveSeat(int aSeatId) {
  if (mSeatId == aSeatId) {
    mSeat = nullptr;
    mSeatId = -1;
  }
}

/* This keymap routine is derived from weston-2.0.0/clients/simple-im.c
 */
static void keyboard_handle_keymap(void* data, struct wl_keyboard* wl_keyboard,
                                   uint32_t format, int fd, uint32_t size) {
  KeymapWrapper::HandleKeymap(format, fd, size);
}

static void keyboard_handle_enter(void* data, struct wl_keyboard* keyboard,
                                  uint32_t serial, struct wl_surface* surface,
                                  struct wl_array* keys) {
  gLastSerial = serial;
}

static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard,
                                  uint32_t serial, struct wl_surface* surface) {
  KeymapWrapper::ResetRepeatState();
}

static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard,
                                uint32_t serial, uint32_t time, uint32_t key,
                                uint32_t state) {
  if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
    gLastSerial = serial;
  }
  // hardware key code is +8.
  // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.41/gdk/wayland/gdkdevice-wayland.c#L2341
  KeymapWrapper::KeyboardHandlerForWayland(serial, key + 8, state);
}

static void keyboard_handle_modifiers(void* data, struct wl_keyboard* keyboard,
                                      uint32_t serial, uint32_t mods_depressed,
                                      uint32_t mods_latched,
                                      uint32_t mods_locked, uint32_t group) {}
static void keyboard_handle_repeat_info(void* data,
                                        struct wl_keyboard* keyboard,
                                        int32_t rate, int32_t delay) {}

static const struct wl_keyboard_listener keyboard_listener = {
    keyboard_handle_keymap,    keyboard_handle_enter,
    keyboard_handle_leave,     keyboard_handle_key,
    keyboard_handle_modifiers, keyboard_handle_repeat_info};

void nsWaylandDisplay::SetKeyboard(wl_keyboard* aKeyboard) {
  MOZ_ASSERT(aKeyboard);
  MOZ_DIAGNOSTIC_ASSERT(!mKeyboard);
  mKeyboard = aKeyboard;
  wl_keyboard_add_listener(mKeyboard, &keyboard_listener, nullptr);
}

void nsWaylandDisplay::ClearKeyboard() {
  if (mKeyboard) {
    wl_keyboard_release(mKeyboard);
    mKeyboard = nullptr;
    KeymapWrapper::ClearKeymap();
  }
}

static void touch_handle_down(void* data, struct wl_touch* touch,
                              uint32_t serial, uint32_t time,
                              struct wl_surface* surface, int32_t id,
                              wl_fixed_t x, wl_fixed_t y) {
  gLastSerial = serial;
}

static void touch_handle_up(void* data, struct wl_touch* touch, uint32_t serial,
                            uint32_t time, int32_t id) {}

static void touch_handle_motion(void* data, struct wl_touch* touch,
                                uint32_t time, int32_t id, wl_fixed_t x,
                                wl_fixed_t y) {}

static void touch_handle_frame(void* data, struct wl_touch* touch) {}

static void touch_handle_cancel(void* data, struct wl_touch* touch) {}

static const struct wl_touch_listener touch_listener = {
    touch_handle_down,  touch_handle_up,     touch_handle_motion,
    touch_handle_frame, touch_handle_cancel,
};

void nsWaylandDisplay::SetTouch(wl_touch* aTouch) {
  MOZ_ASSERT(aTouch);
  MOZ_DIAGNOSTIC_ASSERT(!mTouch);
  mTouch = aTouch;
  wl_touch_add_listener(mTouch, &touch_listener, nullptr);
}

void nsWaylandDisplay::ClearTouch() {
  MozClearPointer(mTouch, wl_touch_release);
}

void nsWaylandDisplay::SetCompositor(wl_compositor* aCompositor) {
  mCompositor = aCompositor;
}

void nsWaylandDisplay::SetSubcompositor(wl_subcompositor* aSubcompositor) {
  mSubcompositor = aSubcompositor;
}

void nsWaylandDisplay::SetDataDeviceManager(
    wl_data_device_manager* aDataDeviceManager) {
  mDataDeviceManager = aDataDeviceManager;
}

void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
    gtk_primary_selection_device_manager* aPrimarySelectionDeviceManager) {
  mPrimarySelectionDeviceManagerGtk = aPrimarySelectionDeviceManager;
  mIsPrimarySelectionEnabled = !!mPrimarySelectionDeviceManagerGtk;
}

void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
    zwp_primary_selection_device_manager_v1* aPrimarySelectionDeviceManager) {
  mPrimarySelectionDeviceManagerZwpV1 = aPrimarySelectionDeviceManager;
  mIsPrimarySelectionEnabled = !!mPrimarySelectionDeviceManagerZwpV1;
}

void nsWaylandDisplay::SetIdleInhibitManager(
    zwp_idle_inhibit_manager_v1* aIdleInhibitManager) {
  mIdleInhibitManager = aIdleInhibitManager;
}

void nsWaylandDisplay::SetViewporter(wp_viewporter* aViewporter) {
  mViewporter = aViewporter;
}

void nsWaylandDisplay::SetRelativePointerManager(
    zwp_relative_pointer_manager_v1* aRelativePointerManager) {
  mRelativePointerManager = aRelativePointerManager;
}

void nsWaylandDisplay::SetPointerConstraints(
    zwp_pointer_constraints_v1* aPointerConstraints) {
  mPointerConstraints = aPointerConstraints;
}

void nsWaylandDisplay::SetPointerGestures(
    zwp_pointer_gestures_v1* aPointerGestures) {
  mPointerGestures = aPointerGestures;
}

void nsWaylandDisplay::SetDmabuf(zwp_linux_dmabuf_v1* aDmabuf, int aVersion) {
  if (!aDmabuf || aVersion < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
    return;
  }
  mDmabuf = aDmabuf;
  mDmabufIsFeedback =
      (aVersion >= ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION);
  if (mDmabufIsFeedback) {
    mFormats->InitFeedback(mDmabuf, nullptr);
  } else {
    mFormats->InitV3(mDmabuf);
  }
}

void nsWaylandDisplay::EnsureDMABufFormats() {
  if (mDmabuf && !mDmabufIsFeedback) {
    mFormats->InitV3Done();
  }
  mFormats->EnsureBasicFormats();
}

void nsWaylandDisplay::SetXdgActivation(xdg_activation_v1* aXdgActivation) {
  mXdgActivation = aXdgActivation;
}

void nsWaylandDisplay::SetAppMenuManager(
    org_kde_kwin_appmenu_manager* aAppMenuManager) {
  mAppMenuManager = aAppMenuManager;
}

void nsWaylandDisplay::SetFixes(wl_fixes* aFixes) { mFixes = aFixes; }

void nsWaylandDisplay::SetCMSupportedFeature(uint32_t aFeature) {
  LOG("nsWaylandDisplay::SetCMSupportedFeature() [%d]", aFeature);
  switch (aFeature) {
    case WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4:
      mColorManagerSupportedFeature.mICC = true;
      break;
    case WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC:
      mColorManagerSupportedFeature.mParametric = true;
      break;
    case WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES:
      mColorManagerSupportedFeature.mPrimaries = true;
      break;
    case WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER:
      mColorManagerSupportedFeature.mFTPower = true;
      break;
    case WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES:
      mColorManagerSupportedFeature.mLuminances = true;
      break;
    case WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES:
      mColorManagerSupportedFeature.mDisplayPrimaries = true;
      break;
  }
}

void nsWaylandDisplay::SetCMSupportedTFNamed(uint32_t aTF) {
  if (aTF < sColorTransfersNum) {
    LOG("nsWaylandDisplay::SetCMSupportedTFNamed() [%d]", aTF);
    mSupportedTransfer[aTF] = aTF;
  } else {
    NS_WARNING("Unknow color transfer function!");
  }
}

void nsWaylandDisplay::SetCMSupportedPrimariesNamed(uint32_t aPrimaries) {
  if (aPrimaries < sColorPrimariesNum) {
    LOG("nsWaylandDisplay::SetCMSupportedPrimariesNamed() [%u]", aPrimaries);
    mSupportedPrimaries[aPrimaries] = aPrimaries;
  } else {
    NS_WARNING("Unknown color primaries!");
  }
}

static void supported_intent(void* data,
                             struct wp_color_manager_v1* color_manager,
                             uint32_t render_intent) {}

static void supported_feature(void* data,
                              struct wp_color_manager_v1* color_manager,
                              uint32_t feature) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  display->SetCMSupportedFeature(feature);
}

static void supported_tf_named(void* data,
                               struct wp_color_manager_v1* color_manager,
                               uint32_t tf) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  display->SetCMSupportedTFNamed(tf);
}

static void supported_primaries_named(void* data,
                                      struct wp_color_manager_v1* color_manager,
                                      uint32_t primaries) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  display->SetCMSupportedPrimariesNamed(primaries);
}

static void supported_done(void* data,
                           struct wp_color_manager_v1* wp_color_manager_v1) {}

static const struct wp_color_manager_v1_listener color_manager_listener = {
    supported_intent,          supported_feature, supported_tf_named,
    supported_primaries_named, supported_done,
};

void nsWaylandDisplay::SetColorManager(wp_color_manager_v1* aColorManager) {
  mColorManager = aColorManager;
  if (mColorManager) {
    LOG("nsWaylandDisplay::SetColorManager()");
    wp_color_manager_v1_add_listener(mColorManager, &color_manager_listener,
                                     this);
  }
}

void nsWaylandDisplay::SetSupportedCoefficientsAndRanges(uint32_t aCoefficients,
                                                         uint32_t aRange) {
  if (aCoefficients < sSupportedRangesNum) {
    LOG("nsWaylandDisplay::SetSupportedCoefficientsAndRanges(): coefficients "
        "%d range %d",
        aCoefficients, aRange);
    mSupportedRanges[aCoefficients] += aRange;
  }
}

uint32_t nsWaylandDisplay::GetColorRange(uint32_t aCoefficients,
                                         bool aFullRange) {
  if (aCoefficients >= sSupportedRangesNum) {
    return 0;
  }
  auto range = mSupportedRanges[aCoefficients];
  if (aFullRange) {
    return range == sSupportedRangeBoth || range == sSupportedRangeFull
               ? WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_FULL
               : 0;
  } else {
    return range == sSupportedRangeBoth || range == sSupportedRangeLimited
               ? WP_COLOR_REPRESENTATION_SURFACE_V1_RANGE_LIMITED
               : 0;
  }
}

static void supported_alpha_mode(
    void* data,
    struct wp_color_representation_manager_v1* color_representation_manager,
    uint32_t alpha_mode) {}

static void supported_coefficients_and_ranges(
    void* data,
    struct wp_color_representation_manager_v1* color_representation_manager,
    uint32_t coefficients, uint32_t range) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  display->SetSupportedCoefficientsAndRanges(coefficients, range);
}

static void color_representation_done(
    void* data,
    struct wp_color_representation_manager_v1* color_representation_manager) {}

static const struct wp_color_representation_manager_v1_listener
    color_representation_listener = {
        supported_alpha_mode,
        supported_coefficients_and_ranges,
        color_representation_done,
};

void nsWaylandDisplay::SetColorRepresentationManager(
    wp_color_representation_manager_v1* aColorRepresentationManager) {
  mColorRepresentationManager = aColorRepresentationManager;
  if (mColorRepresentationManager) {
    LOG("nsWaylandDisplay::SetColorRepresentationManager()");
    wp_color_representation_manager_v1_add_listener(
        mColorRepresentationManager, &color_representation_listener, this);
  }
}

static void output_handle_geometry(void* data, struct wl_output* wl_output,
                                   int x, int y, int physical_width,
                                   int physical_height, int subpixel,
                                   const char* make, const char* model,
                                   int32_t transform) {
  auto* monitor = static_cast<nsWaylandDisplay::MonitorConfig*>(data);
  LOG("nsWaylandDisplay ID %d geometry position %d %d physical size %d %d "
      "subpixel %d transform %d",
      monitor->id, x, y, physical_width, physical_height, subpixel, transform);
  monitor->x = x;
  monitor->y = y;
  monitor->pendingChanges = true;
}

static void output_handle_mode(void* data, struct wl_output* wl_output,
                               uint32_t flags, int width, int height,
                               int refresh) {
  auto* monitor = static_cast<nsWaylandDisplay::MonitorConfig*>(data);
  if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
    return;
  }
  LOG("nsWaylandDisplay ID %d mode output size %d x %d", monitor->id, width,
      height);
  monitor->pixelWidth = width;
  monitor->pixelHeight = height;
  monitor->pendingChanges = true;
}

static void output_handle_scale(void* data, struct wl_output* wl_output,
                                int32_t scale) {
  auto* monitor = static_cast<nsWaylandDisplay::MonitorConfig*>(data);
  LOG("nsWaylandDisplay ID %d Scale change [%d]", monitor->id, scale);
  monitor->pendingChanges = true;
}

static void output_handle_done(void* data, struct wl_output* wl_output) {
  auto* monitor = static_cast<nsWaylandDisplay::MonitorConfig*>(data);
  LOG("nsWaylandDisplay ID %d Done", monitor->id);
  monitor->pendingChanges = false;

  WaylandDisplayGet()->RefreshScreens();
}

static const struct wl_output_listener output_listener = {
    output_handle_geometry,
    output_handle_mode,
    output_handle_done,
    output_handle_scale,
};

void nsWaylandDisplay::AddMonitorConfig(int aId, wl_output* aWlOutput) {
  LOG("nsWaylandDisplay add monitor ID %d num %zu", aId, mMonitors.Length());
  mMonitors.AppendElement(MakeUnique<MonitorConfig>(aId, aWlOutput));
}

nsWaylandDisplay::MonitorConfig::MonitorConfig(int aId,
                                               struct wl_output* aWlOutput)
    : id(aId), wlOutput(aWlOutput) {
  LOG("MonitorConfig() add ID %d", id);
  wl_output_add_listener(wlOutput, &output_listener, this);
}

nsWaylandDisplay::MonitorConfig::~MonitorConfig() {
  LOG("~MonitorConfig() delete ID %d", id);
  MozClearPointer(wlOutput, wl_output_release);
}

bool nsWaylandDisplay::RemoveMonitorConfig(int aId) {
  for (unsigned int i = 0; i < mMonitors.Length(); i++) {
    if (mMonitors[i]->id == aId) {
      LOG("nsWaylandDisplay remove Monitor ID %d num %d", aId, i);
      mMonitors.RemoveElementAt(i);
      return true;
    }
  }
  return false;
}

nsWaylandDisplay::MonitorConfig* nsWaylandDisplay::GetMonitorConfig(int x,
                                                                    int y) {
  for (const auto& monitor : mMonitors) {
    if (monitor->x == x && monitor->y == y) {
      LOG("nsWaylandDisplay::GetMonitorConfig() %d, %d matches", x, y);
      return monitor.get();
    }
  }
  LOG("nsWaylandDisplay::GetMonitorConfig() %d, %d missing!", x, y);
  return nullptr;
}

void nsWaylandDisplay::RefreshScreens() {
  LOG("nsWaylandDisplay::RefreshScreens()");
  for (unsigned int i = 0; i < mMonitors.Length(); i++) {
    if (mMonitors[i]->pendingChanges) {
      LOG("  monitor ID %d is not complete", mMonitors[i]->id);
      return;
    }
  }
  ScreenHelperGTK::RequestRefreshScreens();
}

static void global_registry_handler(void* data, wl_registry* registry,
                                    uint32_t id, const char* interface,
                                    uint32_t version) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  if (!display) {
    return;
  }

  nsDependentCString iface(interface);
  if (iface.EqualsLiteral("wl_shm")) {
    auto* shm = WaylandRegistryBind<wl_shm>(registry, id, &wl_shm_interface, 1);
    display->SetShm(shm);
  } else if (strcmp(interface, "wl_data_device_manager") == 0) {
    int data_device_manager_version = MIN(version, 3);
    auto* data_device_manager = WaylandRegistryBind<wl_data_device_manager>(
        registry, id, &wl_data_device_manager_interface,
        data_device_manager_version);
    display->SetDataDeviceManager(data_device_manager);
  } else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) {
    auto* primary_selection_device_manager =
        WaylandRegistryBind<gtk_primary_selection_device_manager>(
            registry, id, &gtk_primary_selection_device_manager_interface, 1);
    display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
  } else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") ==
             0) {
    auto* primary_selection_device_manager =
        WaylandRegistryBind<zwp_primary_selection_device_manager_v1>(
            registry, id, &zwp_primary_selection_device_manager_v1_interface,
            1);
    display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
  } else if (iface.EqualsLiteral("zwp_idle_inhibit_manager_v1")) {
    auto* idle_inhibit_manager =
        WaylandRegistryBind<zwp_idle_inhibit_manager_v1>(
            registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
    display->SetIdleInhibitManager(idle_inhibit_manager);
  } else if (iface.EqualsLiteral("zwp_relative_pointer_manager_v1")) {
    auto* relative_pointer_manager =
        WaylandRegistryBind<zwp_relative_pointer_manager_v1>(
            registry, id, &zwp_relative_pointer_manager_v1_interface, 1);
    display->SetRelativePointerManager(relative_pointer_manager);
  } else if (iface.EqualsLiteral("zwp_pointer_constraints_v1")) {
    auto* pointer_constraints = WaylandRegistryBind<zwp_pointer_constraints_v1>(
        registry, id, &zwp_pointer_constraints_v1_interface, 1);
    display->SetPointerConstraints(pointer_constraints);
  } else if (iface.EqualsLiteral("wl_compositor") &&
             version >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION) {
    auto* compositor = WaylandRegistryBind<wl_compositor>(
        registry, id, &wl_compositor_interface,
        WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION);
    display->SetCompositor(compositor);
  } else if (iface.EqualsLiteral("wl_subcompositor")) {
    auto* subcompositor = WaylandRegistryBind<wl_subcompositor>(
        registry, id, &wl_subcompositor_interface, 1);
    display->SetSubcompositor(subcompositor);
  } else if (iface.EqualsLiteral("wp_viewporter")) {
    auto* viewporter = WaylandRegistryBind<wp_viewporter>(
        registry, id, &wp_viewporter_interface, 1);
    display->SetViewporter(viewporter);
  } else if (iface.EqualsLiteral("zwp_linux_dmabuf_v1")) {
    if (version < ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION) {
      return;
    }
    int vers =
        MIN(version, ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK_SINCE_VERSION);
    auto* dmabuf = WaylandRegistryBind<zwp_linux_dmabuf_v1>(
        registry, id, &zwp_linux_dmabuf_v1_interface, vers);
    display->SetDmabuf(dmabuf, vers);
  } else if (iface.EqualsLiteral("xdg_session_manager_v1")) {
    auto* sessionManager = WaylandRegistryBind<xdg_session_manager_v1>(
        registry, id, &xdg_session_manager_v1_interface, 1);
    display->SetSessionManager(sessionManager);
  } else if (iface.EqualsLiteral("xdg_activation_v1")) {
    auto* activation = WaylandRegistryBind<xdg_activation_v1>(
        registry, id, &xdg_activation_v1_interface, 1);
    display->SetXdgActivation(activation);
  } else if (iface.EqualsLiteral("org_kde_kwin_appmenu_manager")) {
    auto* appMenuManager = WaylandRegistryBind<org_kde_kwin_appmenu_manager>(
        registry, id, &org_kde_kwin_appmenu_manager_interface, MIN(version, 2));
    display->SetAppMenuManager(appMenuManager);
  } else if (iface.EqualsLiteral("wl_seat") &&
             version >= WL_POINTER_RELEASE_SINCE_VERSION) {
    auto* seat = WaylandRegistryBind<wl_seat>(
        registry, id, &wl_seat_interface,
        MIN(version, WL_POINTER_AXIS_VALUE120_SINCE_VERSION));
    display->SetSeat(seat, id);
  } else if (iface.EqualsLiteral("wp_fractional_scale_manager_v1")) {
    auto* manager = WaylandRegistryBind<wp_fractional_scale_manager_v1>(
        registry, id, &wp_fractional_scale_manager_v1_interface, 1);
    display->SetFractionalScaleManager(manager);
  } else if (iface.EqualsLiteral("xx_fractional_scale_manager_v2")) {
    auto* manager = WaylandRegistryBind<xx_fractional_scale_manager_v2>(
        registry, id, &xx_fractional_scale_manager_v2_interface, 1);
    display->SetFractionalScaleManagerV2(manager);
  } else if (iface.EqualsLiteral("zwp_pointer_gestures_v1") &&
             version >=
                 ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION) {
    auto* gestures = WaylandRegistryBind<zwp_pointer_gestures_v1>(
        registry, id, &zwp_pointer_gestures_v1_interface,
        ZWP_POINTER_GESTURES_V1_GET_HOLD_GESTURE_SINCE_VERSION);
    display->SetPointerGestures(gestures);
  } else if (iface.EqualsLiteral("wp_color_manager_v1")) {
    auto* colorManager = WaylandRegistryBind<wp_color_manager_v1>(
        registry, id, &wp_color_manager_v1_interface, 1);
    display->SetColorManager(colorManager);
  } else if (iface.EqualsLiteral("wp_color_representation_manager_v1")) {
    auto* colorRepresentationManager =
        WaylandRegistryBind<wp_color_representation_manager_v1>(
            registry, id, &wp_color_representation_manager_v1_interface, 1);
    display->SetColorRepresentationManager(colorRepresentationManager);
  } else if (iface.EqualsLiteral("xx_pip_shell_v1")) {
    auto* pipShell = WaylandRegistryBind<xx_pip_shell_v1>(
        registry, id, &xx_pip_shell_v1_interface, 1);
    display->SetPipShell(pipShell);
  } else if (iface.EqualsLiteral("xdg_wm_base")) {
    uint32_t vers = MIN(version, (uint32_t)xdg_wm_base_interface.version);
    auto* xdgWm = WaylandRegistryBind<xdg_wm_base>(
        registry, id, &xdg_wm_base_interface, vers);
    display->SetXdgWm(xdgWm);
  } else if (iface.EqualsLiteral("wl_output") &&
             version >= WL_OUTPUT_RELEASE_SINCE_VERSION) {
    auto* output = WaylandRegistryBind<wl_output>(
        registry, id, &wl_output_interface, WL_OUTPUT_RELEASE_SINCE_VERSION);
    display->AddMonitorConfig(id, output);
  } else if (iface.EqualsLiteral("wl_fixes")) {
    // wl_fixes_interface was introduced in libwayland-client 1.24, but
    // Ubuntu 22.04 still ships 1.20.
    // We force wl_fixes v.1 interface.
    static auto* sWlFixesInterface =
        (wl_interface*)dlsym(RTLD_DEFAULT, "wl_fixes_interface");
    if (sWlFixesInterface) {
      auto* fixes =
          WaylandRegistryBind<wl_fixes>(registry, id, sWlFixesInterface, 1);
      display->SetFixes(fixes);
    } else {
      LOG("wl_fixes_interface is missing!");
    }
  }
}

static void global_registry_remover(void* data, wl_registry* registry,
                                    uint32_t id) {
  auto* display = static_cast<nsWaylandDisplay*>(data);
  if (!display) {
    return;
  }

  if (!display->RemoveMonitorConfig(id)) {
    display->RemoveSeat(id);
  }

  if (wl_fixes* fixes = display->GetFixes()) {
    if (wl_fixes_get_version(fixes) >=
        WL_FIXES_ACK_GLOBAL_REMOVE_SINCE_VERSION) {
      wl_fixes_ack_global_remove(fixes, registry, id);
    }
  }
}

static const struct wl_registry_listener registry_listener = {
    global_registry_handler, global_registry_remover};

nsWaylandDisplay::~nsWaylandDisplay() {
  g_list_free_full(mAsyncRoundtrips, (GDestroyNotify)wl_callback_destroy);
  MozClearPointer(mColorManager, wp_color_manager_v1_destroy);
  MozClearPointer(mColorRepresentationManager,
                  wp_color_representation_manager_v1_destroy);
  MozClearPointer(mWaylandSession, xdg_session_v1_destroy);
  MozClearPointer(mSessionManager, xdg_session_manager_v1_destroy);
}

void nsWaylandDisplay::AsyncRoundtripCallback(void* aData,
                                              wl_callback* aCallback,
                                              uint32_t aTime) {
  auto* display = static_cast<nsWaylandDisplay*>(aData);
  display->mAsyncRoundtrips =
      g_list_remove(display->mAsyncRoundtrips, aCallback);
  wl_callback_destroy(aCallback);
}

static const struct wl_callback_listener async_roundtrip_listener = {
    nsWaylandDisplay::AsyncRoundtripCallback};

void nsWaylandDisplay::RequestAsyncRoundtrip() {
  LOG("nsWaylandDisplay::RequestAsyncRoundtrip()");
  wl_callback* callback = wl_display_sync(mDisplay);
  wl_callback_add_listener(callback, &async_roundtrip_listener, this);
  mAsyncRoundtrips = g_list_append(mAsyncRoundtrips, callback);
  wl_display_flush(mDisplay);
}

void nsWaylandDisplay::WaitForAsyncRoundtrips() {
  LOG("nsWaylandDisplay::WaitForAsyncRoundtrips()");
  while (g_list_length(mAsyncRoundtrips) > 0) {
    if (wl_display_dispatch(mDisplay) < 0) {
      NS_WARNING("Failed to get events from Wayland display!");
      return;
    }
  }
}

void nsWaylandDisplay::RequestRoundtrip() {
  LOG("nsWaylandDisplay::RequestRoundtrip()");
  wl_display_roundtrip(mDisplay);
}

void nsWaylandDisplay::SessionCreate(void* aData, xdg_session_v1* aSession,
                                     const char* aSessionId) {
  LOG("nsWaylandDisplay::SessionCreate() %s", aSessionId);

  auto* display = static_cast<nsWaylandDisplay*>(aData);
  display->mWaylandSessionId = aSessionId;
  Preferences::SetCString("widget.wayland.session-id", aSessionId);
}

void nsWaylandDisplay::SessionRestore(void* aData, xdg_session_v1* aSession) {
#ifdef MOZ_LOGGING
  auto* display = static_cast<nsWaylandDisplay*>(aData);
  LOG("nsWaylandDisplay::SessionRestore() %s",
      display->mWaylandSessionId.get());
#endif
}

void nsWaylandDisplay::SessionReplace(void* aData, xdg_session_v1* aSession) {
  LOG("nsWaylandDisplay::SessionReplace()");
  auto* display = static_cast<nsWaylandDisplay*>(aData);
  display->CreateSession();
}

static const xdg_session_v1_listener sSessionListener = {
    nsWaylandDisplay::SessionCreate,
    nsWaylandDisplay::SessionRestore,
    nsWaylandDisplay::SessionReplace,
};

void nsWaylandDisplay::CreateSession(const char* aSessionId) {
  LOG("nsWaylandDisplay::CreateSession() ID %s", aSessionId);

  // TODO: WUniquePtr
  MozClearPointer(mWaylandSession, xdg_session_v1_destroy);

  mWaylandSession = xdg_session_manager_v1_get_session(
      mSessionManager,
      aSessionId ? XDG_SESSION_MANAGER_V1_REASON_SESSION_RESTORE
                 : XDG_SESSION_MANAGER_V1_REASON_LAUNCH,
      aSessionId);

  if (!mWaylandSession) {
    LOG("  failed to create session %s", aSessionId);
    return;
  }

  xdg_session_v1_add_listener(mWaylandSession, &sSessionListener, this);
}

void nsWaylandDisplay::SetSessionManager(
    xdg_session_manager_v1* aSessionManager) {
  LOG("nsWaylandDisplay::SetSessionManager()");
  mSessionManager = aSessionManager;
}

void nsWaylandDisplay::SessionManagerInit() {
  if (!mSessionManager ||
      !StaticPrefs::widget_wayland_session_management_enabled_AtStartup()) {
    return;
  }

  nsAutoCString prevSessionId;
  Preferences::GetCString("widget.wayland.session-id", prevSessionId);

  LOG("nsWaylandDisplay::SessionManagerInit() session ID '%s'",
      prevSessionId.get());
  CreateSession(prevSessionId.IsEmpty() ? nullptr : prevSessionId.get());
}

// Separate crash functions for different Wayland protocol error patterns.
// These functions are marked MOZ_NEVER_INLINE to ensure distinct crash
// signatures for different error types, making them easier to track and fix.
//
// Pattern analysis is based on 25 recent crash reports with the
// mozilla::widget::WlLogHandler signature. Frequencies are noted in comments
// below.

// 32% of crashes - Example: "unknown object (4278190083), message error(ous)"
MOZ_NEVER_INLINE static void WlLogHandler_UnknownObject(const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 20% of crashes - Example: "wp_viewport#296: error 2: source rectangle out
// of buffer bounds"
MOZ_NEVER_INLINE static void WlLogHandler_ViewportBufferBounds(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 20% of crashes - Example: "wl_display#1: error 1: invalid arguments for
// wl_surface#730.frame"
MOZ_NEVER_INLINE static void WlLogHandler_InvalidSurfaceFrame(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 12% of crashes - Example: "wp_color_manager_v1#44: error -1: Invalid output
// (2)"
MOZ_NEVER_INLINE static void WlLogHandler_ColorManagerInvalidOutput(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 8% of crashes - Example: "Message length 22222 exceeds limit 4096"
MOZ_NEVER_INLINE static void WlLogHandler_MessageLengthExceeded(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 4% of crashes - Example: "wl_display#1: error 0: invalid object 61246"
MOZ_NEVER_INLINE static void WlLogHandler_InvalidDisplayObject(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 4% of crashes - Example: "failed to import supplied dmabufs: EGL failed to
// allocate resources"
MOZ_NEVER_INLINE static void WlLogHandler_DmabufImportFailed(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 4% of crashes - Example: "wl_registry@36: error 0: invalid global
// wl_output (55)"
MOZ_NEVER_INLINE static void WlLogHandler_RegistryError(const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 4% of crashes - Example: "invalid object (277), type (wl_callback), message
// begin(3uuou)"
MOZ_NEVER_INLINE static void WlLogHandler_CallbackInvalid(const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// 4% of crashes - Example: "error marshalling arguments for set_surface
// (signature o): null value passed for arg 0"
MOZ_NEVER_INLINE static void WlLogHandler_MarshallingError(const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// xdg_surface buffer mismatch - Example: "dg_wm_base@17: error 4: xdg_surface
// buffer (1 x 1) is larger than the configured fullscreen state (0 x 0)"
MOZ_NEVER_INLINE static void WlLogHandler_XdgSurfaceBufferMismatch(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

// Timestamp of the last "still attached" message ignored by WlLogHandler.
// Written on the main thread (libwayland event dispatch) with release ordering
// after writing sStillAttachedMessage, so any thread that observes the
// timestamp with acquire ordering is guaranteed to see the message too.
static std::atomic<clock_t> sStillAttachedTime{0};
static char sStillAttachedMessage[128];

static void WlLogHandler(const char* format, va_list args) {
  char error[1000];
  VsprintfLiteral(error, format, args);
  gfxCriticalNote << "(" << GetDesktopEnvironmentIdentifier().get()
                  << ") Wayland protocol error: " << error;

  // See Bug 1826583 and Bug 1844653 for reference.
  // "warning: queue %p destroyed while proxies still attached" and variants
  // like "zwp_linux_dmabuf_feedback_v1@%d still attached" are exceptions on
  // Wayland and non-fatal. They are triggered in certain versions of Mesa or
  // the proprietary Nvidia driver and we don't want to crash because of them.
  // Store the message and timestamp so ProcessFailure() can correlate this
  // event with a subsequent silent compositor disconnect.
  if (strstr(error, "still attached")) {
    // Sentinel: ensures a concurrent reader on the proxy thread never scans
    // past the end of the array if a longer new string overwrites the old
    // null terminator before placing its own.
    sStillAttachedMessage[sizeof(sStillAttachedMessage) - 1] = '\0';
    SprintfLiteral(sStillAttachedMessage, "%s", error);
    sStillAttachedTime.store(clock(), std::memory_order_release);
    return;
  }

  // Pattern matching to dispatch to specific crash functions.
  // Patterns ordered by frequency (most common first) for performance.

  // Pattern 1: Unknown object errors (32% of crashes)
  if (strstr(error, "unknown object") && strstr(error, "message error")) {
    WlLogHandler_UnknownObject(error);
  }

  // Pattern 2: wp_viewport buffer bounds errors (20% of crashes)
  if (strstr(error, "wp_viewport") &&
      strstr(error, "source rectangle out of buffer bounds")) {
    WlLogHandler_ViewportBufferBounds(error);
  }

  // Pattern 3: Invalid wl_surface.frame arguments (20% of crashes)
  if (strstr(error, "wl_display") && strstr(error, "invalid arguments") &&
      strstr(error, "wl_surface") && strstr(error, ".frame")) {
    WlLogHandler_InvalidSurfaceFrame(error);
  }

  // Pattern 4: wp_color_manager_v1 invalid output (12% of crashes)
  if (strstr(error, "wp_color_manager_v1") && strstr(error, "Invalid output")) {
    WlLogHandler_ColorManagerInvalidOutput(error);
  }

  // Pattern 5: Message length exceeded (8% of crashes)
  if ((strstr(error, "message length") || strstr(error, "Message length")) &&
      strstr(error, "exceeds")) {
    WlLogHandler_MessageLengthExceeded(error);
  }

  // Pattern 6: Invalid wl_display object (4% of crashes)
  if (strstr(error, "wl_display") && strstr(error, "invalid object") &&
      !strstr(error, "unknown object")) {
    WlLogHandler_InvalidDisplayObject(error);
  }

  // Pattern 7: dmabuf import failures (4% of crashes)
  if (strstr(error, "dmabuf") && strstr(error, "failed") &&
      strstr(error, "EGL")) {
    WlLogHandler_DmabufImportFailed(error);
  }

  // Pattern 8: wl_registry errors (4% of crashes)
  if (strstr(error, "wl_registry") && strstr(error, "wl_output")) {
    WlLogHandler_RegistryError(error);
  }

  // Pattern 9: wl_callback invalid (4% of crashes)
  if (strstr(error, "wl_callback") && strstr(error, "invalid object")) {
    WlLogHandler_CallbackInvalid(error);
  }

  // Pattern 10: Marshalling errors (4% of crashes)
  if (strstr(error, "error marshalling arguments")) {
    WlLogHandler_MarshallingError(error);
  }

  // Pattern 11: xdg_surface buffer mismatch with fullscreen state
  if (strstr(error, "xdg_surface") && strstr(error, "buffer") &&
      strstr(error, "fullscreen state")) {
    WlLogHandler_XdgSurfaceBufferMismatch(error);
  }

  // Fallback for unmatched patterns - use original inline code
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

void WlCompositorUnavailableHandler() {
  gfxCriticalNote << "Wayland compositor unavailable ("
                  << GetDesktopEnvironmentIdentifier().get()
                  << "), proxy: " << WaylandProxy::GetState()
                  << " - scheduling graceful shutdown";
  // Called from the WaylandProxy thread. Dispatch to the main thread to
  // trigger a clean Firefox shutdown instead of crashing.
  NS_DispatchToMainThread(
      NS_NewRunnableFunction("WlCompositorUnavailableHandler", []() {
        nsCOMPtr<nsIAppStartup> appStartup =
            do_GetService("@mozilla.org/toolkit/app-startup;1");
        if (appStartup) {
          bool userAllowedQuit = true;
          appStartup->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit);
        }
      }));
}

MOZ_NEVER_INLINE static void WlLogHandler_StillAttachedDisconnect(
    const char* error) {
  MOZ_CRASH_UNSAFE_PRINTF("(%s) %s Proxy: %s",
                          GetDesktopEnvironmentIdentifier().get(), error,
                          WaylandProxy::GetState());
}

void WlCompositorSilentDisconnectHandler(clock_t aFailureTime) {
  clock_t t = sStillAttachedTime.load(std::memory_order_acquire);
  if (t <= aFailureTime) {
    return;  // no still-attached event in this failure window
  }
  nsCString reason(sStillAttachedMessage);
  NS_DispatchToMainThread(NS_NewRunnableFunction(
      "WlCompositorSilentDisconnectHandler", [reason = std::move(reason)]() {
        WlLogHandler_StillAttachedDisconnect(reason.get());
      }));
}

nsWaylandDisplay::nsWaylandDisplay(wl_display* aDisplay)
    : mThreadId(PR_GetCurrentThread()), mDisplay(aDisplay) {
  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
  for (auto& e : mSupportedTransfer) {
    e = -1;
  };
  for (auto& e : mSupportedPrimaries) {
    e = -1;
  };
}

void nsWaylandDisplay::Init() {
  // GTK sets the log handler on display creation, thus we overwrite it here
  // in a similar fashion
  wl_log_set_handler_client(WlLogHandler);

  LOG("nsWaylandDisplay::Init()");

  mFormats = new DMABufFormats();
  mRegistry = wl_display_get_registry(mDisplay);
  wl_registry_add_listener(mRegistry, &registry_listener, this);
  wl_display_roundtrip(mDisplay);
  RequestAsyncRoundtrip();
  WaitForAsyncRoundtrips();
  EnsureDMABufFormats();

  LOG("  init finished");

  // Check we have critical Wayland interfaces.
  // Missing ones indicates a compositor bug/missing feature and
  // we can't continue.
  MOZ_RELEASE_ASSERT(GetShm(), "We're missing shm interface!");
  MOZ_RELEASE_ASSERT(GetCompositor(), "We're missing compositor interface!");
  MOZ_RELEASE_ASSERT(GetSubcompositor(),
                     "We're missing subcompositor interface!");
  if (!GetViewporter()) {
    NS_WARNING("Missing viewporter wayland protocol!");
  }
  if (!GetFractionalScaleManager()) {
    NS_WARNING("Missing wp_fractional_scale_v1 wayland protocol!");
  }
}

}  // namespace mozilla::widget
