Global Metrics

path: .metrics.cognitive.sum
old: 23.0
new: 135.0

path: .metrics.cognitive.average
old: 4.6
new: 3.0

path: .metrics.cyclomatic.sum
old: 17.0
new: 181.0

path: .metrics.cyclomatic.average
old: 2.8333333333333335
new: 2.7846153846153845

path: .metrics.loc.blank
old: 22.0
new: 147.0

path: .metrics.loc.ploc
old: 74.0
new: 811.0

path: .metrics.loc.sloc
old: 105.0
new: 1135.0

path: .metrics.loc.cloc
old: 9.0
new: 177.0

path: .metrics.loc.lloc
old: 46.0
new: 358.0

path: .metrics.halstead.bugs
old: 0.8364714690130914
new: 5.982265756442015

path: .metrics.halstead.level
old: 0.02350061199510404
new: 0.01396981396981397

path: .metrics.halstead.vocabulary
old: 67.0
new: 435.0

path: .metrics.halstead.purity_ratio
old: 0.71619881476395
new: 0.9473189909980804

path: .metrics.halstead.difficulty
old: 42.552083333333336
new: 71.58291457286433

path: .metrics.halstead.N2
old: 215.0
new: 1540.0

path: .metrics.halstead.estimated_program_length
old: 348.78882279004364
new: 3630.126373504644

path: .metrics.halstead.N1
old: 272.0
new: 2292.0

path: .metrics.halstead.effort
old: 125706.74484427854
new: 2404254.488161088

path: .metrics.halstead.n1
old: 19.0
new: 37.0

path: .metrics.halstead.time
old: 6983.708046904363
new: 133569.69378672712

path: .metrics.halstead.volume
old: 2954.185435752935
new: 33586.9879357007

path: .metrics.halstead.n2
old: 48.0
new: 398.0

path: .metrics.halstead.length
old: 487.0
new: 3832.0

path: .metrics.nargs.sum
old: 11.0
new: 38.0

path: .metrics.nargs.average
old: 2.2
new: 0.8444444444444444

path: .metrics.nexits.sum
old: 3.0
new: 59.0

path: .metrics.nexits.average
old: 0.6
new: 1.3111111111111111

path: .metrics.mi.mi_visual_studio
old: 29.323248831830306
new: 0.0

path: .metrics.mi.mi_sei
old: 20.279090142754683
new: -84.5042962160302

path: .metrics.mi.mi_original
old: 50.14275550242982
new: -38.78093330169085

path: .metrics.nom.closures
old: 0.0
new: 2.0

path: .metrics.nom.functions
old: 5.0
new: 43.0

path: .metrics.nom.total
old: 5.0
new: 45.0

Spaces Data

Minimal test - lines (39, 1135)

path: .spaces[0].metrics.nom.closures
old: 0.0
new: 2.0

path: .spaces[0].metrics.nom.functions
old: 1.0
new: 43.0

path: .spaces[0].metrics.nom.total
old: 1.0
new: 45.0

path: .spaces[0].metrics.nargs.average
old: 3.0
new: 0.8444444444444444

path: .spaces[0].metrics.nargs.sum
old: 3.0
new: 38.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 169.0

path: .spaces[0].metrics.loc.blank
old: 9.0
new: 143.0

path: .spaces[0].metrics.loc.ploc
old: 25.0
new: 785.0

path: .spaces[0].metrics.loc.sloc
old: 34.0
new: 1097.0

path: .spaces[0].metrics.loc.lloc
old: 19.0
new: 358.0

path: .spaces[0].metrics.mi.mi_sei
old: 35.31161717139865
new: -83.53336970362933

path: .spaces[0].metrics.mi.mi_original
old: 76.59509926526042
new: -37.9316900484734

path: .spaces[0].metrics.mi.mi_visual_studio
old: 44.7924557106786
new: 0.0

path: .spaces[0].metrics.nexits.sum
old: 1.0
new: 59.0

path: .spaces[0].metrics.nexits.average
old: 1.0
new: 1.3111111111111111

path: .spaces[0].metrics.cognitive.sum
old: 8.0
new: 135.0

path: .spaces[0].metrics.cognitive.average
old: 8.0
new: 3.0

path: .spaces[0].metrics.halstead.effort
old: 25150.667179410604
new: 2437290.435490223

path: .spaces[0].metrics.halstead.N2
old: 87.0
new: 1518.0

path: .spaces[0].metrics.halstead.time
old: 1397.2592877450336
new: 135405.02419390128

path: .spaces[0].metrics.halstead.volume
old: 1040.7172625963008
new: 33153.32928665973

path: .spaces[0].metrics.halstead.estimated_program_length
old: 186.98532149254143
new: 3469.327586837927

path: .spaces[0].metrics.halstead.n2
old: 27.0
new: 382.0

path: .spaces[0].metrics.halstead.purity_ratio
old: 0.9688358626556552
new: 0.9115416675874742

path: .spaces[0].metrics.halstead.vocabulary
old: 42.0
new: 419.0

path: .spaces[0].metrics.halstead.length
old: 193.0
new: 3806.0

path: .spaces[0].metrics.halstead.n1
old: 15.0
new: 37.0

path: .spaces[0].metrics.halstead.N1
old: 106.0
new: 2288.0

path: .spaces[0].metrics.halstead.bugs
old: 0.2861398984918436
new: 6.036941046076076

path: .spaces[0].metrics.halstead.difficulty
old: 24.166666666666668
new: 73.51570680628272

path: .spaces[0].metrics.halstead.level
old: 0.041379310344827586
new: 0.013602535341665776

path: .spaces[0].metrics.cyclomatic.average
old: 5.0
new: 2.8125

path: .spaces[0].metrics.cyclomatic.sum
old: 5.0
new: 180.0

Code

namespace mozilla {
namespace widget {

/*
  Wayland multi-thread rendering scheme

  Every rendering thread (main thread, compositor thread) contains its own
  nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)

  WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
  WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.

  ----------------------
  | Wayland compositor |
  ----------------------
             ^
             |
  ----------------------
  |  nsWaylandDisplay  |
  ----------------------
        ^          ^
        |          |
        |          |
        |       ---------------------------------        ------------------
        |       | WindowSurfaceWayland          |<------>| nsWindow       |
        |       |                               |        ------------------
        |       |  -----------------------      |
        |       |  | WindowBackBuffer    |      |
        |       |  |                     |      |
        |       |  | ------------------- |      |
        |       |  | |  WaylandShmPool | |      |
        |       |  | ------------------- |      |
        |       |  -----------------------      |
        |       |                               |
        |       |  -----------------------      |
        |       |  | WindowBackBuffer    |      |
        |       |  |                     |      |
        |       |  | ------------------- |      |
        |       |  | |  WaylandShmPool | |      |
        |       |  | ------------------- |      |
        |       |  -----------------------      |
        |       ---------------------------------
        |
        |
  ---------------------------------        ------------------
  | WindowSurfaceWayland          |<------>| nsWindow       |
  |                               |        ------------------
  |  -----------------------      |
  |  | WindowBackBuffer    |      |
  |  |                     |      |
  |  | ------------------- |      |
  |  | |  WaylandShmPool | |      |
  |  | ------------------- |      |
  |  -----------------------      |
  |                               |
  |  -----------------------      |
  |  | WindowBackBuffer    |      |
  |  |                     |      |
  |  | ------------------- |      |
  |  | |  WaylandShmPool | |      |
  |  | ------------------- |      |
  |  -----------------------      |
  ---------------------------------


nsWaylandDisplay

Is our connection to Wayland display server,
holds our display connection (wl_display) and event queue (wl_event_queue).

nsWaylandDisplay is created for every thread which sends data to Wayland
compositor. Wayland events for main thread is served by default Gtk+ loop,
for other threads (compositor) we must create wl_event_queue and run event loop.


WindowSurfaceWayland

Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
we implement Lock() and Commit() interfaces from WindowSurface
for actual drawing.

One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
At Wayland level it holds one wl_surface object.

To perform visualiation of nsWindow, WindowSurfaceWayland contains one
wl_surface and two wl_buffer objects (owned by WindowBackBuffer)
as we use double buffering. When nsWindow drawing is finished to wl_buffer,
the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.

When there's no wl_buffer available for drawing (all wl_buffers are locked in
compositor for instance) we store the drawing to WindowImageSurface object
and draw later when wl_buffer becomes available or discard the
WindowImageSurface cache when whole screen is invalidated.

WindowBackBuffer

Is a class which provides a wl_buffer for drawing.
Wl_buffer is a main Wayland object with actual graphics data.
Wl_buffer basically represent one complete window screen.
When double buffering is involved every window (GdkWindow for instance)
utilises two wl_buffers which are cycled. One is filed with data by application
and one is rendered by compositor.

WindowBackBuffer is implemented by shared memory (shm).
It owns wl_buffer object, owns WaylandShmPool
(which provides the shared memory) and ties them together.

WaylandShmPool

WaylandShmPool acts as a manager of shared memory for WindowBackBuffer.
Allocates it, holds reference to it and releases it.

We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
between us and wayland compositor. We draw our graphics data to the shm and
handle to wayland compositor by WindowBackBuffer/WindowSurfaceWayland
(wl_buffer/wl_surface).
*/

#define EVENT_LOOP_DELAY (1000 / 240)

#define BUFFER_BPP 4
gfx::SurfaceFormat WindowBackBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;

int WindowBackBuffer::mDumpSerial =
    PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
char* WindowBackBuffer::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");

RefPtr WindowBackBuffer::GetWaylandDisplay() {
  return mWindowSurfaceWayland->GetWaylandDisplay();
}

static int WaylandAllocateShmMemory(int aSize) {
  int fd = -1;
  do {
    static int counter = 0;
    nsPrintfCString shmName("/wayland.mozilla.ipc.%d", counter++);
    fd = shm_open(shmName.get(), O_CREAT | O_RDWR | O_EXCL, 0600);
    if (fd >= 0) {
      // We don't want to use leaked file
      if (shm_unlink(shmName.get()) != 0) {
        NS_WARNING("shm_unlink failed");
        return -1;
      }
    }
  } while (fd < 0 && errno == EEXIST);

  if (fd < 0) {
    NS_WARNING(nsPrintfCString("shm_open failed: %s", strerror(errno)).get());
    return -1;
  }

  int ret = 0;
#ifdef HAVE_POSIX_FALLOCATE
  do {
    ret = posix_fallocate(fd, 0, aSize);
  } while (ret == EINTR);
  if (ret != 0) {
    NS_WARNING(
        nsPrintfCString("posix_fallocate() fails to allocate shm memory: %s",
                        strerror(ret))
            .get());
    close(fd);
    return -1;
  }
#else
  do {
    ret = ftruncate(fd, aSize);
  } while (ret < 0 && errno == EINTR);
  if (ret < 0) {
    NS_WARNING(nsPrintfCString("ftruncate() fails to allocate shm memory: %s",
                               strerror(ret))
                   .get());
    close(fd);
    fd = -1;
  }
#endif

  return fd;
}

static bool WaylandReAllocateShmMemory(int aFd, int aSize) {
  if (ftruncate(aFd, aSize) < 0) {
    return false;
  }
#ifdef HAVE_POSIX_FALLOCATE
  do {
    errno = posix_fallocate(aFd, 0, aSize);
  } while (errno == EINTR);
  if (errno != 0) {
    return false;
  }
#endif
  return true;
}

WaylandShmPool::WaylandShmPool()
    : mShmPool(nullptr),
      mShmPoolFd(-1),
      mAllocatedSize(0),
      mImageData(MAP_FAILED){};

void WaylandShmPool::Release() {
  if (mImageData != MAP_FAILED) {
    munmap(mImageData, mAllocatedSize);
    mImageData = MAP_FAILED;
  }
  if (mShmPool) {
    wl_shm_pool_destroy(mShmPool);
    mShmPool = 0;
  }
  if (mShmPoolFd >= 0) {
    close(mShmPoolFd);
    mShmPoolFd = -1;
  }
}

bool WaylandShmPool::Create(RefPtr aWaylandDisplay,
                            int aSize) {
  // We do size increase only
  if (aSize <= mAllocatedSize) {
    return true;
  }

  if (mShmPoolFd < 0) {
    mShmPoolFd = WaylandAllocateShmMemory(aSize);
    if (mShmPoolFd < 0) {
      return false;
    }
  } else {
    if (!WaylandReAllocateShmMemory(mShmPoolFd, aSize)) {
      Release();
      return false;
    }
  }

  if (mImageData != MAP_FAILED) {
    munmap(mImageData, mAllocatedSize);
  }
  mImageData =
      mmap(nullptr, aSize, PROT_READ | PROT_WRITE, MAP_SHARED, mShmPoolFd, 0);
  if (mImageData == MAP_FAILED) {
    NS_WARNING("Unable to map drawing surface!");
    Release();
    return false;
  }

  if (mShmPool) {
    wl_shm_pool_resize(mShmPool, aSize);
  } else {
    mShmPool = wl_shm_create_pool(aWaylandDisplay->GetShm(), mShmPoolFd, aSize);
    // We set our queue to get mShmPool events at compositor thread.
    wl_proxy_set_queue((struct wl_proxy*)mShmPool,
                       aWaylandDisplay->GetEventQueue());
  }

  mAllocatedSize = aSize;
  return true;
}

void WaylandShmPool::SetImageDataFromPool(class WaylandShmPool* aSourcePool,
                                          int aImageDataSize) {
  MOZ_ASSERT(mAllocatedSize >= aImageDataSize, "WaylandShmPool overflows!");
  memcpy(mImageData, aSourcePool->GetImageData(), aImageDataSize);
}

WaylandShmPool::~WaylandShmPool() { Release(); }

static void buffer_release(void* data, wl_buffer* buffer) {
  auto surface = reinterpret_cast(data);
  surface->Detach(buffer);
}

static const struct wl_buffer_listener buffer_listener = {buffer_release};

bool WindowBackBuffer::Create(int aWidth, int aHeight) {
  MOZ_ASSERT(!IsAttached(), "We can't create attached buffers.");

  ReleaseWLBuffer();

  int size = aWidth * aHeight * BUFFER_BPP;
  if (!mShmPool.Create(GetWaylandDisplay(), size)) {
    return false;
  }

  mWLBuffer =
      wl_shm_pool_create_buffer(mShmPool.GetShmPool(), 0, aWidth, aHeight,
                                aWidth * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
  wl_proxy_set_queue((struct wl_proxy*)mWLBuffer,
                     GetWaylandDisplay()->GetEventQueue());
  wl_buffer_add_listener(mWLBuffer, &buffer_listener, this);

  mWidth = aWidth;
  mHeight = aHeight;

  LOGWAYLAND(("WindowBackBuffer::Create [%p] wl_buffer %p ID %d\n", (void*)this,
              (void*)mWLBuffer,
              mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
  return true;
}

void WindowBackBuffer::ReleaseWLBuffer() {
  LOGWAYLAND(("WindowBackBuffer::Release [%p]\n", (void*)this));
  if (mWLBuffer) {
    wl_buffer_destroy(mWLBuffer);
    mWLBuffer = nullptr;
  }
  mWidth = mHeight = 0;
}

void WindowBackBuffer::Clear() {
  memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
}

WindowBackBuffer::WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland)
    : mWindowSurfaceWayland(aWindowSurfaceWayland),
      mShmPool(),
      mWLBuffer(nullptr),
      mWidth(0),
      mHeight(0),
      mAttached(false) {
  LOGWAYLAND(("WindowBackBuffer Created [%p] WindowSurfaceWayland [%p]\n",
              (void*)this, mWindowSurfaceWayland));
}

WindowBackBuffer::~WindowBackBuffer() { ReleaseWLBuffer(); }

bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
  if (aWidth == mWidth && aHeight == mHeight) {
    return true;
  }
  LOGWAYLAND(
      ("WindowBackBuffer::Resize [%p] %d %d\n", (void*)this, aWidth, aHeight));
  Create(aWidth, aHeight);
  return (mWLBuffer != nullptr);
}

void WindowBackBuffer::Attach(wl_surface* aSurface) {
  LOGWAYLAND(
      ("WindowBackBuffer::Attach [%p] wl_surface %p ID %d wl_buffer %p ID %d "
       "WindowSurfaceWayland [%p]\n",
       (void*)this, (void*)aSurface,
       aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
       (void*)GetWlBuffer(),
       GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1,
       mWindowSurfaceWayland));

  wl_buffer* buffer = GetWlBuffer();
  if (buffer) {
    mAttached = true;
    wl_surface_attach(aSurface, buffer, 0, 0);
    wl_surface_commit(aSurface);
    wl_display_flush(WaylandDisplayGetWLDisplay());
  }
}

void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
  LOGWAYLAND(
      ("WindowBackBuffer::Detach [%p] wl_buffer %p ID %d WindowSurfaceWayland "
       "[%p]\n",
       (void*)this, (void*)aBuffer,
       aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1,
       mWindowSurfaceWayland));
  mAttached = false;

  // Commit any potential cached drawings from latest Lock()/Commit() cycle.
  mWindowSurfaceWayland->FlushPendingCommits();
}

bool WindowBackBuffer::SetImageDataFromBuffer(
    class WindowBackBuffer* aSourceBuffer) {
  auto sourceBuffer = static_cast(aSourceBuffer);
  if (!IsMatchingSize(sourceBuffer)) {
    if (!Resize(sourceBuffer->mWidth, sourceBuffer->mHeight)) {
      return false;
    }
  }

  mShmPool.SetImageDataFromPool(
      &sourceBuffer->mShmPool,
      sourceBuffer->mWidth * sourceBuffer->mHeight * BUFFER_BPP);
  return true;
}

already_AddRefed WindowBackBuffer::Lock() {
  LOGWAYLAND(("WindowBackBuffer::Lock [%p] [%d x %d] wl_buffer %p ID %d\n",
              (void*)this, mWidth, mHeight, (void*)mWLBuffer,
              mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));

  gfx::IntSize lockSize(mWidth, mHeight);
  return gfxPlatform::CreateDrawTargetForData(
      static_cast(mShmPool.GetImageData()), lockSize,
      BUFFER_BPP * mWidth, GetSurfaceFormat());
}

#ifdef MOZ_LOGGING
void WindowBackBuffer::DumpToFile(const char* aHint) {
  if (!mDumpSerial) {
    return;
  }

  cairo_surface_t* surface = nullptr;
  auto unmap = MakeScopeExit([&] {
    if (surface) {
      cairo_surface_destroy(surface);
    }
  });
  surface = cairo_image_surface_create_for_data(
      (unsigned char*)mShmPool.GetImageData(), CAIRO_FORMAT_ARGB32, mWidth,
      mHeight, BUFFER_BPP * mWidth);
  if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
    nsCString filename;
    if (mDumpDir) {
      filename.Append(mDumpDir);
      filename.Append('/');
    }
    filename.Append(
        nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint));
    cairo_surface_write_to_png(surface, filename.get());
    LOGWAYLAND(("Dumped wl_buffer to %s\n", filename.get()));
  }
}
#endif

static void frame_callback_handler(void* data, struct wl_callback* callback,
                                   uint32_t time) {
  auto surface = reinterpret_cast(data);
  surface->FrameCallbackHandler();
}

static const struct wl_callback_listener frame_listener = {
    frame_callback_handler};

WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
    : mWindow(aWindow),
      mWaylandDisplay(WaylandDisplayGet()),
      mWaylandBuffer(nullptr),
      mWaylandFullscreenDamage(false),
      mFrameCallback(nullptr),
      mLastCommittedSurfaceID(-1),
      mLastCommitTime(0),
      mDrawToWaylandBufferDirectly(true),
      mCanSwitchWaylandBuffer(true),
      mWLBufferIsDirty(false),
      mBufferCommitAllowed(false),
      mBufferNeedsClear(false),
      mSmoothRendering(StaticPrefs::widget_wayland_smooth_rendering()),
      mSurfaceReadyTimerID(),
      mSurfaceLock("WindowSurfaceWayland lock") {
  for (int i = 0; i < BACK_BUFFER_NUM; i++) {
    mShmBackupBuffer[i] = nullptr;
  }
}

WindowSurfaceWayland::~WindowSurfaceWayland() {
  MutexAutoLock lock(mSurfaceLock);

  if (mSurfaceReadyTimerID) {
    g_source_remove(mSurfaceReadyTimerID);
    mSurfaceReadyTimerID = 0;
  }

  if (mWLBufferIsDirty) {
    NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
  }

  if (mFrameCallback) {
    wl_callback_destroy(mFrameCallback);
  }

  mWaylandBuffer = nullptr;

  for (int i = 0; i < BACK_BUFFER_NUM; i++) {
    if (mShmBackupBuffer[i]) {
      delete mShmBackupBuffer[i];
    }
  }
}

WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
                                                            int aHeight) {
  int availableBuffer;

  LOGWAYLAND(
      ("WindowSurfaceWayland::CreateWaylandBuffer %d x %d\n", aWidth, aHeight));

  for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
       availableBuffer++) {
    if (!mShmBackupBuffer[availableBuffer]) {
      break;
    }
  }

  // There isn't any free slot for additional buffer.
  if (availableBuffer == BACK_BUFFER_NUM) {
    LOGWAYLAND(("    no free buffer slot!\n"));
    return nullptr;
  }

  WindowBackBuffer* buffer = new WindowBackBuffer(this);
  if (!buffer->Create(aWidth, aHeight)) {
    delete buffer;
    LOGWAYLAND(("    failed to create back buffer!\n"));
    return nullptr;
  }

  mShmBackupBuffer[availableBuffer] = buffer;
  LOGWAYLAND(("    created new buffer %p at %d!\n", buffer, availableBuffer));
  return buffer;
}

WindowBackBuffer* WindowSurfaceWayland::WaylandBufferFindAvailable(
    int aWidth, int aHeight) {
  int availableBuffer;

  LOGWAYLAND(("WindowSurfaceWayland::WaylandBufferFindAvailable %d x %d\n",
              aWidth, aHeight));

  // Try to find a buffer which matches the size
  for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
       availableBuffer++) {
    WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
    if (buffer && !buffer->IsAttached() &&
        buffer->IsMatchingSize(aWidth, aHeight)) {
      LOGWAYLAND(("    found match %d [%p]\n", availableBuffer, buffer));
      return buffer;
    }
  }

  // Try to find any buffer
  for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
       availableBuffer++) {
    WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
    if (buffer && !buffer->IsAttached()) {
      LOGWAYLAND(
          ("    found any free buffer %d [%p]\n", availableBuffer, buffer));
      return buffer;
    }
  }

  LOGWAYLAND(("    no buffer available!\n"));
  return nullptr;
}

WindowBackBuffer* WindowSurfaceWayland::SetNewWaylandBuffer() {
  LOGWAYLAND(
      ("WindowSurfaceWayland::NewWaylandBuffer [%p] Requested buffer [%d "
       "x %d]\n",
       (void*)this, mWLBufferRect.width, mWLBufferRect.height));

  mWaylandBuffer =
      WaylandBufferFindAvailable(mWLBufferRect.width, mWLBufferRect.height);
  if (mWaylandBuffer) {
    if (!mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
      return nullptr;
    }
    return mWaylandBuffer;
  }

  mWaylandBuffer =
      CreateWaylandBuffer(mWLBufferRect.width, mWLBufferRect.height);
  return mWaylandBuffer;
}

// Recent
WindowBackBuffer* WindowSurfaceWayland::GetWaylandBuffer() {
  LOGWAYLAND(
      ("WindowSurfaceWayland::GetWaylandBuffer [%p] Requested buffer [%d "
       "x %d] can switch %d\n",
       (void*)this, mWLBufferRect.width, mWLBufferRect.height,
       mCanSwitchWaylandBuffer));

#if MOZ_LOGGING
  LOGWAYLAND(("    Recent WindowBackBuffer [%p]\n", mWaylandBuffer));
  for (int i = 0; i < BACK_BUFFER_NUM; i++) {
    if (!mShmBackupBuffer[i]) {
      LOGWAYLAND(("        WindowBackBuffer [%d] null\n", i));
    } else {
      LOGWAYLAND((
          "        WindowBackBuffer [%d][%p] width %d height %d attached %d\n",
          i, mShmBackupBuffer[i], mShmBackupBuffer[i]->GetWidth(),
          mShmBackupBuffer[i]->GetHeight(), mShmBackupBuffer[i]->IsAttached()));
    }
  }
#endif

  // There's no buffer created yet, create a new one for partial screen updates.
  if (!mWaylandBuffer) {
    return SetNewWaylandBuffer();
  }

  if (mWaylandBuffer->IsAttached()) {
    if (mCanSwitchWaylandBuffer) {
      return SetNewWaylandBuffer();
    }
    LOGWAYLAND(("    Buffer is attached and we can't switch, return null\n"));
    return nullptr;
  }

  if (mWaylandBuffer->IsMatchingSize(mWLBufferRect.width,
                                     mWLBufferRect.height)) {
    LOGWAYLAND(("    Size is ok, use the buffer [%d x %d]\n",
                mWLBufferRect.width, mWLBufferRect.height));
    return mWaylandBuffer;
  }

  if (mCanSwitchWaylandBuffer) {
    // Reuse existing buffer
    LOGWAYLAND(("    Reuse buffer with resize [%d x %d]\n", mWLBufferRect.width,
                mWLBufferRect.height));
    if (mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
      return mWaylandBuffer;
    }
    // OOM here, just return null to skip this frame.
    return nullptr;
  }

  LOGWAYLAND(
      ("    Buffer size does not match, requested %d x %d got %d x%d, return "
       "null.\n",
       mWaylandBuffer->GetWidth(), mWaylandBuffer->GetHeight(),
       mWLBufferRect.width, mWLBufferRect.height));
  return nullptr;
}

already_AddRefed WindowSurfaceWayland::LockWaylandBuffer() {
  // Allocated wayland buffer must match mozcontainer widget size.
  mWLBufferRect = mWindow->GetMozContainerSize();

  LOGWAYLAND(
      ("WindowSurfaceWayland::LockWaylandBuffer [%p] Requesting buffer %d x "
       "%d\n",
       (void*)this, mWLBufferRect.width, mWLBufferRect.height));

  WindowBackBuffer* buffer = GetWaylandBuffer();
  LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
              (void*)this, (void*)buffer));

  if (!buffer) {
    if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
                               COMPOSITING_TIMEOUT) {
      NS_WARNING(
          "Slow response from Wayland compositor, visual glitches ahead.");
    }
    return nullptr;
  }

  mCanSwitchWaylandBuffer = false;

  if (mBufferNeedsClear) {
    buffer->Clear();
    mBufferNeedsClear = false;
  }

  return buffer->Lock();
}

already_AddRefed WindowSurfaceWayland::LockImageSurface(
    const gfx::IntSize& aLockSize) {
  if (!mImageSurface || !(aLockSize <= mImageSurface->GetSize())) {
    mImageSurface = gfx::Factory::CreateDataSourceSurface(
        aLockSize, WindowBackBuffer::GetSurfaceFormat());
  }
  gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
  if (!mImageSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
    return nullptr;
  }
  return gfxPlatform::CreateDrawTargetForData(
      map.mData, mImageSurface->GetSize(), map.mStride,
      WindowBackBuffer::GetSurfaceFormat());
}

static bool IsWindowFullScreenUpdate(
    LayoutDeviceIntRect& aScreenRect,
    const LayoutDeviceIntRegion& aUpdatedRegion) {
  if (aUpdatedRegion.GetNumRects() > 1) return false;

  gfx::IntRect rect = aUpdatedRegion.RectIter().Get().ToUnknownRect();
  return (rect.x == 0 && rect.y == 0 && aScreenRect.width == rect.width &&
          aScreenRect.height == rect.height);
}

static bool IsPopupFullScreenUpdate(
    LayoutDeviceIntRect& aScreenRect,
    const LayoutDeviceIntRegion& aUpdatedRegion) {
  // We know that popups can be drawn from two parts; a panel and an arrow.
  // Assume we redraw whole popups when we have two rects and bounding
  // box is equal to window borders.
  if (aUpdatedRegion.GetNumRects() > 2) return false;

  gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
  return (lockSize.x == 0 && lockSize.y == 0 &&
          aScreenRect.width == lockSize.width &&
          aScreenRect.height == lockSize.height);
}

already_AddRefed WindowSurfaceWayland::Lock(
    const LayoutDeviceIntRegion& aRegion) {
  if (mWindow->WindowType() == eWindowType_invisible) {
    return nullptr;
  }

  // Wait until all pending events are processed. There may be queued
  // wl_buffer release event which releases our wl_buffer for further rendering.
  mWaylandDisplay->WaitForSyncEnd();

  // Lock the surface *after* WaitForSyncEnd() call as is can fire
  // FlushPendingCommits().
  MutexAutoLock lock(mSurfaceLock);

  // Disable all commits (from potential frame callback/delayed handlers)
  // until next WindowSurfaceWayland::Commit() call.
  mBufferCommitAllowed = false;

  LayoutDeviceIntRect mozContainerSize = mWindow->GetMozContainerSize();
  // The window bounds of popup windows contains relative position to
  // the transient window. We need to remove that effect because by changing
  // position of the popup window the buffer has not changed its size.
  mozContainerSize.x = mozContainerSize.y = 0;
  gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();

  bool isTransparentPopup =
      mWindow->IsWaylandPopup() &&
      (eTransparencyTransparent == mWindow->GetTransparencyMode());

  bool windowRedraw = isTransparentPopup
                          ? IsPopupFullScreenUpdate(mozContainerSize, aRegion)
                          : IsWindowFullScreenUpdate(mozContainerSize, aRegion);
  if (windowRedraw) {
    // Clear buffer when we (re)draw new transparent popup window,
    // otherwise leave it as-is, mBufferNeedsClear can be set from previous
    // (already pending) commits which are cached now.
    mBufferNeedsClear =
        mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;

    // We do full buffer repaint so clear our cached drawings.
    mDelayedImageCommits.Clear();
    mWaylandBufferDamage.SetEmpty();
    mCanSwitchWaylandBuffer = true;
    mWLBufferIsDirty = false;

    // Store info that we can safely invalidate whole screen.
    mWaylandFullscreenDamage = true;
  } else {
    // We can switch buffer if there isn't any content committed
    // to active buffer.
    mCanSwitchWaylandBuffer = !mWLBufferIsDirty;
  }

  LOGWAYLAND(
      ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
       "MozContainer size [%d x %d]\n",
       (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
       aRegion.GetNumRects(), mozContainerSize.width, mozContainerSize.height));
  LOGWAYLAND(("   nsWindow = %p\n", mWindow));
  LOGWAYLAND(("   isPopup = %d\n", mWindow->IsWaylandPopup()));
  LOGWAYLAND(("   isTransparentPopup = %d\n", isTransparentPopup));
  LOGWAYLAND(("   IsPopupFullScreenUpdate = %d\n",
              IsPopupFullScreenUpdate(mozContainerSize, aRegion)));
  LOGWAYLAND(("   IsWindowFullScreenUpdate = %d\n",
              IsWindowFullScreenUpdate(mozContainerSize, aRegion)));
  LOGWAYLAND(("   mBufferNeedsClear = %d\n", mBufferNeedsClear));
  LOGWAYLAND(("   mWLBufferIsDirty = %d\n", mWLBufferIsDirty));
  LOGWAYLAND(("   mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
  LOGWAYLAND(("   windowRedraw = %d\n", windowRedraw));

  if (!(mMozContainerRect == mozContainerSize)) {
    LOGWAYLAND(("   screen size changed\n"));
    if (!windowRedraw) {
      LOGWAYLAND(("   screen size changed without redraw!\n"));
      // Screen (window) size changed and we still have some painting pending
      // for the last window size. That can happen when window is resized.
      // We won't draw it but wait for new content.
      mDelayedImageCommits.Clear();
      mWaylandBufferDamage.SetEmpty();
      mCanSwitchWaylandBuffer = true;
      mWLBufferIsDirty = false;
      mBufferNeedsClear = true;
    }
    mMozContainerRect = mozContainerSize;
  }

  // We can draw directly only when we redraw significant part of the window
  // to avoid flickering or do only fullscreen updates in smooth mode.
  mDrawToWaylandBufferDirectly =
      mSmoothRendering
          ? windowRedraw
          : (windowRedraw || (lockSize.width * 2 > mozContainerSize.width &&
                              lockSize.height * 2 > mozContainerSize.height));

  if (!mDrawToWaylandBufferDirectly) {
    // Don't switch wl_buffers when we cache drawings.
    mCanSwitchWaylandBuffer = false;
    LOGWAYLAND(("   Indirect drawing, mCanSwitchWaylandBuffer = %d\n",
                mCanSwitchWaylandBuffer));
  }

  if (mDrawToWaylandBufferDirectly) {
    LOGWAYLAND(("   Direct drawing\n"));
    RefPtr dt = LockWaylandBuffer();
    if (dt) {
#if MOZ_LOGGING
      mWaylandBuffer->DumpToFile("Lock");
#endif
      if (!windowRedraw) {
        DrawDelayedImageCommits(dt, mWaylandBufferDamage);
#if MOZ_LOGGING
        mWaylandBuffer->DumpToFile("Lock-after-commit");
#endif
      }
      mWLBufferIsDirty = true;
      return dt.forget();
    }
  }

  // We do indirect drawing because there isn't any front buffer available.
  // Do indirect drawing to mImageSurface which is commited to wayland
  // wl_buffer by DrawDelayedImageCommits() later.
  mDrawToWaylandBufferDirectly = false;

  LOGWAYLAND(("   Indirect drawing.\n"));
  return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
}

bool WindowImageSurface::OverlapsSurface(
    class WindowImageSurface& aBottomSurface) {
  return mUpdateRegion.Contains(aBottomSurface.mUpdateRegion);
}

void WindowImageSurface::DrawToTarget(
    gfx::DrawTarget* aDest, LayoutDeviceIntRegion& aWaylandBufferDamage) {
#ifdef MOZ_LOGGING
  gfx::IntRect bounds = mUpdateRegion.GetBounds().ToUnknownRect();
  LOGWAYLAND(("WindowImageSurface::DrawToTarget\n"));
  LOGWAYLAND(("    rects num %d\n", mUpdateRegion.GetNumRects()));
  LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
              bounds.width, bounds.height));
#endif
  for (auto iter = mUpdateRegion.RectIter(); !iter.Done(); iter.Next()) {
    gfx::IntRect r(iter.Get().ToUnknownRect());
    LOGWAYLAND(
        ("    draw rect [%d,%d] -> [%d x %d]\n", r.x, r.y, r.width, r.height));
    aDest->CopySurface(mImageSurface, r, gfx::IntPoint(r.x, r.y));
  }
  aWaylandBufferDamage.OrWith(mUpdateRegion);
}

WindowImageSurface::WindowImageSurface(
    gfx::DataSourceSurface* aImageSurface,
    const LayoutDeviceIntRegion& aUpdateRegion)
    : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {}

bool WindowSurfaceWayland::DrawDelayedImageCommits(
    gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
  unsigned int imagesNum = mDelayedImageCommits.Length();
  LOGWAYLAND(("WindowSurfaceWayland::DrawDelayedImageCommits [%p] len %d\n",
              (void*)this, imagesNum));
  for (unsigned int i = 0; i < imagesNum; i++) {
    mDelayedImageCommits[i].DrawToTarget(aDrawTarget, aWaylandBufferDamage);
  }
  mDelayedImageCommits.Clear();

  return (imagesNum != 0);
}

void WindowSurfaceWayland::CacheImageSurface(
    const LayoutDeviceIntRegion& aRegion) {
#ifdef MOZ_LOGGING
  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
  LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
  LOGWAYLAND(("    rects num %d\n", aRegion.GetNumRects()));
  LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
              bounds.width, bounds.height));
#endif

  mImageSurface->Unmap();
  WindowImageSurface surf = WindowImageSurface(mImageSurface, aRegion);

  if (mDelayedImageCommits.Length()) {
    auto lastSurf = mDelayedImageCommits.PopLastElement();
    if (surf.OverlapsSurface(lastSurf)) {
#ifdef MOZ_LOGGING
      {
        gfx::IntRect size =
            lastSurf.GetUpdateRegion()->GetBounds().ToUnknownRect();
        LOGWAYLAND(("    removing [ %d, %d] -> [%d x %d]\n", size.x, size.y,
                    size.width, size.height));
      }
#endif
    } else {
      mDelayedImageCommits.AppendElement(lastSurf);
    }
  }

  mDelayedImageCommits.AppendElement(surf);
  // mImageSurface is owned by mDelayedImageCommits
  mImageSurface = nullptr;

  LOGWAYLAND(
      ("    There's %d cached images\n", int(mDelayedImageCommits.Length())));
}

bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
  if (!mDelayedImageCommits.Length()) {
    return false;
  }

  MOZ_ASSERT(!mDrawToWaylandBufferDirectly);

  RefPtr dt = LockWaylandBuffer();
  if (!dt) {
    return false;
  }

  LOGWAYLAND(("   Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
              long(mDelayedImageCommits.Length())));

  return DrawDelayedImageCommits(dt, mWaylandBufferDamage);
}

void WindowSurfaceWayland::FlushPendingCommits() {
  MutexAutoLock lock(mSurfaceLock);
  if (FlushPendingCommitsLocked()) {
    mWaylandDisplay->QueueSyncBegin();
  }
}

// When a new window is created we may not have a valid wl_surface
// for drawing (Gtk haven't created it yet). All commits are queued
// and FlushPendingCommitsLocked() is called by timer when wl_surface is ready
// for drawing.
static int WaylandBufferFlushPendingCommits(void* data) {
  WindowSurfaceWayland* aSurface = static_cast(data);
  aSurface->FlushPendingCommits();
  return true;
}

bool WindowSurfaceWayland::FlushPendingCommitsLocked() {
  LOGWAYLAND(
      ("WindowSurfaceWayland::FlushPendingCommitsLocked [%p]\n", (void*)this));
  LOGWAYLAND(("    mDrawToWaylandBufferDirectly = %d\n",
              mDrawToWaylandBufferDirectly));
  LOGWAYLAND(("    mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
  LOGWAYLAND(("    mFrameCallback = %p\n", mFrameCallback));
  LOGWAYLAND(("    mLastCommittedSurfaceID = %d\n", mLastCommittedSurfaceID));
  LOGWAYLAND(("    mWLBufferIsDirty = %d\n", mWLBufferIsDirty));
  LOGWAYLAND(("    mBufferCommitAllowed = %d\n", mBufferCommitAllowed));

  if (!mBufferCommitAllowed) {
    LOGWAYLAND(("    Quit - buffer commit is not allowed.\n"));
    return false;
  }

  if (CommitImageCacheToWaylandBuffer()) {
    mWLBufferIsDirty = true;
  }

  // There's nothing to do here
  if (!mWLBufferIsDirty) {
    LOGWAYLAND(("    Quit - no pending commit.\n"));
    return false;
  }

  MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
             "We can't draw to attached wayland buffer!");

  LOGWAYLAND(("    Drawing pending commits.\n"));
  MozContainer* container = mWindow->GetMozContainer();
  wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
  if (!waylandSurface) {
    LOGWAYLAND(
        ("    moz_container_wayland_surface_lock() failed, delay commit.\n"));

    if (!mSurfaceReadyTimerID) {
      mSurfaceReadyTimerID = g_timeout_add(
          EVENT_LOOP_DELAY, &WaylandBufferFlushPendingCommits, this);
    }
    return true;
  }
  if (mSurfaceReadyTimerID) {
    g_source_remove(mSurfaceReadyTimerID);
    mSurfaceReadyTimerID = 0;
  }

  LOGWAYLAND(("    We have wl_surface %p ID [%d] to commit in.\n",
              waylandSurface,
              wl_proxy_get_id((struct wl_proxy*)waylandSurface)));

  auto unlockContainer = MakeScopeExit([&] {
    moz_container_wayland_surface_unlock(container, &waylandSurface);
  });

  wl_proxy_set_queue((struct wl_proxy*)waylandSurface,
                     mWaylandDisplay->GetEventQueue());

  // We have an active frame callback request so handle it.
  if (mFrameCallback) {
    int waylandSurfaceID = wl_proxy_get_id((struct wl_proxy*)waylandSurface);
    if (waylandSurfaceID == mLastCommittedSurfaceID) {
      LOGWAYLAND(("    [%p] wait for frame callback ID %d.\n", (void*)this,
                  waylandSurfaceID));
      // We have an active frame callback pending from our recent surface.
      // It means we should defer the commit to FrameCallbackHandler().
      return true;
    }
    LOGWAYLAND(("    Removing wrong frame callback [%p] ID %d.\n",
                mFrameCallback,
                wl_proxy_get_id((struct wl_proxy*)mFrameCallback)));
    // If our stored wl_surface does not match the actual one it means the frame
    // callback is no longer active and we should release it.
    wl_callback_destroy(mFrameCallback);
    mFrameCallback = nullptr;
    mLastCommittedSurfaceID = -1;
  }

  if (mWaylandFullscreenDamage) {
    LOGWAYLAND(("    wl_surface_damage full screen\n"));
    wl_surface_damage(waylandSurface, 0, 0, INT_MAX, INT_MAX);
  } else {
    for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
         iter.Next()) {
      mozilla::LayoutDeviceIntRect r = iter.Get();
      LOGWAYLAND(("   wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
                  r.y, r.width, r.height));
      wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
    }
  }

#if MOZ_LOGGING
  mWaylandBuffer->DumpToFile("Commit");
#endif

  // Clear all back buffer damage as we're committing
  // all requested regions.
  mWaylandFullscreenDamage = false;
  mWaylandBufferDamage.SetEmpty();

  mFrameCallback = wl_surface_frame(waylandSurface);
  wl_callback_add_listener(mFrameCallback, &frame_listener, this);

  mWaylandBuffer->Attach(waylandSurface);
  mLastCommittedSurfaceID = wl_proxy_get_id((struct wl_proxy*)waylandSurface);
  mLastCommitTime = g_get_monotonic_time() / 1000;

  // There's no pending commit, all changes are sent to compositor.
  mWLBufferIsDirty = false;

  return true;
}

void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
#ifdef MOZ_LOGGING
  {
    gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
    LOGWAYLAND(
        ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d] "
         "MozContainer [%d x %d]\n",
         (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
         mMozContainerRect.width, mMozContainerRect.height));
    LOGWAYLAND(("    mDrawToWaylandBufferDirectly = %d\n",
                mDrawToWaylandBufferDirectly));
  }
#endif

  MutexAutoLock lock(mSurfaceLock);

  if (mDrawToWaylandBufferDirectly) {
    mWaylandBufferDamage.OrWith(aInvalidRegion);
  } else {
    CacheImageSurface(aInvalidRegion);
  }

  mBufferCommitAllowed = true;
  if (FlushPendingCommitsLocked()) {
    mWaylandDisplay->QueueSyncBegin();
  }
}

void WindowSurfaceWayland::FrameCallbackHandler() {
  MOZ_ASSERT(mFrameCallback != nullptr,
             "FrameCallbackHandler() called without valid frame callback!");
  MOZ_ASSERT(mLastCommittedSurfaceID != -1,
             "FrameCallbackHandler() called without valid wl_surface!");
  LOGWAYLAND(
      ("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", (void*)this));

  MutexAutoLock lock(mSurfaceLock);

  wl_callback_destroy(mFrameCallback);
  mFrameCallback = nullptr;

  if (FlushPendingCommitsLocked()) {
    mWaylandDisplay->QueueSyncBegin();
  }
}

}  // namespace widget
}  // namespace mozilla