Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/nsShmImage.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
 *
3
 * This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "nsShmImage.h"
8
9
#ifdef MOZ_HAVE_SHMIMAGE
10
#include "mozilla/X11Util.h"
11
#include "mozilla/gfx/gfxVars.h"
12
#include "mozilla/ipc/SharedMemory.h"
13
#include "gfxPlatform.h"
14
#include "nsPrintfCString.h"
15
#include "nsTArray.h"
16
17
#include <dlfcn.h>
18
#include <errno.h>
19
#include <string.h>
20
#include <sys/ipc.h>
21
#include <sys/shm.h>
22
23
extern "C" {
24
#include <X11/ImUtil.h>
25
}
26
27
using namespace mozilla::ipc;
28
using namespace mozilla::gfx;
29
30
nsShmImage::nsShmImage(Display* aDisplay,
31
                       Drawable aWindow,
32
                       Visual* aVisual,
33
                       unsigned int aDepth)
34
  : mDisplay(aDisplay)
35
  , mConnection(XGetXCBConnection(aDisplay))
36
  , mWindow(aWindow)
37
  , mVisual(aVisual)
38
  , mDepth(aDepth)
39
  , mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN)
40
  , mSize(0, 0)
41
  , mStride(0)
42
  , mPixmap(XCB_NONE)
43
  , mGC(XCB_NONE)
44
  , mRequestPending(false)
45
  , mShmSeg(XCB_NONE)
46
  , mShmId(-1)
47
  , mShmAddr(nullptr)
48
0
{
49
0
  mozilla::PodZero(&mSyncRequest);
50
0
}
51
52
nsShmImage::~nsShmImage()
53
0
{
54
0
  DestroyImage();
55
0
}
56
57
// If XShm isn't available to our client, we'll try XShm once, fail,
58
// set this to false and then never try again.
59
static bool gShmAvailable = true;
60
bool nsShmImage::UseShm()
61
0
{
62
0
  return gShmAvailable;
63
0
}
64
65
bool
66
nsShmImage::CreateShmSegment()
67
0
{
68
0
  size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
69
0
70
#if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
71
  static mozilla::LazyLogModule sPledgeLog("SandboxPledge");
72
  MOZ_LOG(sPledgeLog, mozilla::LogLevel::Debug,
73
         ("%s called when pledged, returning false\n", __func__));
74
  return false;
75
#endif
76
  mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
77
0
  if (mShmId == -1) {
78
0
    return false;
79
0
  }
80
0
  mShmAddr = (uint8_t*) shmat(mShmId, nullptr, 0);
81
0
  mShmSeg = xcb_generate_id(mConnection);
82
0
83
0
  // Mark the handle removed so that it will destroy the segment when unmapped.
84
0
  shmctl(mShmId, IPC_RMID, nullptr);
85
0
86
0
  if (mShmAddr == (void *)-1) {
87
0
    // Since mapping failed, the segment is already destroyed.
88
0
    mShmId = -1;
89
0
90
0
    nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
91
0
    NS_WARNING(warning.get());
92
0
    return false;
93
0
  }
94
0
95
#ifdef DEBUG
96
  struct shmid_ds info;
97
  if (shmctl(mShmId, IPC_STAT, &info) < 0) {
98
    return false;
99
  }
100
101
  MOZ_ASSERT(size <= info.shm_segsz,
102
             "Segment doesn't have enough space!");
103
#endif
104
105
0
  return true;
106
0
}
107
108
void
109
nsShmImage::DestroyShmSegment()
110
0
{
111
0
  if (mShmId != -1) {
112
0
    shmdt(mShmAddr);
113
0
    mShmId = -1;
114
0
  }
115
0
}
116
117
static bool gShmInitialized = false;
118
static bool gUseShmPixmaps = false;
119
120
bool
121
nsShmImage::InitExtension()
122
0
{
123
0
  if (gShmInitialized) {
124
0
    return gShmAvailable;
125
0
  }
126
0
127
0
  gShmInitialized = true;
128
0
129
0
  // Bugs 1397918, 1293474 - race condition in libxcb fixed upstream as of
130
0
  // version 1.11. Since we can't query libxcb's version directly, the only
131
0
  // other option is to check for symbols that were added after 1.11.
132
0
  // xcb_discard_reply64 was added in 1.11.1, so check for existence of
133
0
  // that to verify we are using a version of libxcb with the bug fixed.
134
0
  // Otherwise, we can't risk using libxcb due to aforementioned crashes.
135
0
  if (!dlsym(RTLD_DEFAULT, "xcb_discard_reply64")) {
136
0
    gShmAvailable = false;
137
0
    return false;
138
0
  }
139
0
140
0
  const xcb_query_extension_reply_t* extReply;
141
0
  extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
142
0
  if (!extReply || !extReply->present) {
143
0
    gShmAvailable = false;
144
0
    return false;
145
0
  }
146
0
147
0
  xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
148
0
      mConnection,
149
0
      xcb_shm_query_version(mConnection),
150
0
      nullptr);
151
0
152
0
  if (!shmReply) {
153
0
    gShmAvailable = false;
154
0
    return false;
155
0
  }
156
0
157
0
  gUseShmPixmaps = shmReply->shared_pixmaps &&
158
0
                   shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
159
0
160
0
  free(shmReply);
161
0
162
0
  return true;
163
0
}
164
165
bool
166
nsShmImage::CreateImage(const IntSize& aSize)
167
0
{
168
0
  MOZ_ASSERT(mConnection && mVisual);
169
0
170
0
  if (!InitExtension()) {
171
0
    return false;
172
0
  }
173
0
174
0
  mSize = aSize;
175
0
176
0
  BackendType backend = gfxVars::ContentBackend();
177
0
178
0
  mFormat = SurfaceFormat::UNKNOWN;
179
0
  switch (mDepth) {
180
0
  case 32:
181
0
    if (mVisual->red_mask == 0xff0000 &&
182
0
        mVisual->green_mask == 0xff00 &&
183
0
        mVisual->blue_mask == 0xff) {
184
0
      mFormat = SurfaceFormat::B8G8R8A8;
185
0
    }
186
0
    break;
187
0
  case 24:
188
0
    // Only support the BGRX layout, and report it as BGRA to the compositor.
189
0
    // The alpha channel will be discarded when we put the image.
190
0
    // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
191
0
    // just report it as BGRX directly in that case.
192
0
    if (mVisual->red_mask == 0xff0000 &&
193
0
        mVisual->green_mask == 0xff00 &&
194
0
        mVisual->blue_mask == 0xff) {
195
0
      mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8 : SurfaceFormat::B8G8R8A8;
196
0
    }
197
0
    break;
198
0
  case 16:
199
0
    if (mVisual->red_mask == 0xf800 &&
200
0
        mVisual->green_mask == 0x07e0 &&
201
0
        mVisual->blue_mask == 0x1f) {
202
0
      mFormat = SurfaceFormat::R5G6B5_UINT16;
203
0
    }
204
0
    break;
205
0
  }
206
0
207
0
  if (mFormat == SurfaceFormat::UNKNOWN) {
208
0
    NS_WARNING("Unsupported XShm Image format!");
209
0
    gShmAvailable = false;
210
0
    return false;
211
0
  }
212
0
213
0
  // Round up stride to the display's scanline pad (in bits) as XShm expects.
214
0
  int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
215
0
  int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
216
0
  int bitsPerLine = ((bitsPerPixel * aSize.width + scanlinePad - 1)
217
0
                     / scanlinePad) * scanlinePad;
218
0
  mStride = bitsPerLine / 8;
219
0
220
0
  if (!CreateShmSegment()) {
221
0
    DestroyImage();
222
0
    return false;
223
0
  }
224
0
225
0
  xcb_generic_error_t* error;
226
0
  xcb_void_cookie_t cookie;
227
0
228
0
  cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
229
0
230
0
  if ((error = xcb_request_check(mConnection, cookie))) {
231
0
    NS_WARNING("Failed to attach MIT-SHM segment.");
232
0
    DestroyImage();
233
0
    gShmAvailable = false;
234
0
    free(error);
235
0
    return false;
236
0
  }
237
0
238
0
  if (gUseShmPixmaps) {
239
0
    mPixmap = xcb_generate_id(mConnection);
240
0
    cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
241
0
                                           aSize.width, aSize.height, mDepth,
242
0
                                           mShmSeg, 0);
243
0
244
0
    if ((error = xcb_request_check(mConnection, cookie))) {
245
0
      // Disable shared pixmaps permanently if creation failed.
246
0
      mPixmap = XCB_NONE;
247
0
      gUseShmPixmaps = false;
248
0
      free(error);
249
0
    }
250
0
  }
251
0
252
0
  return true;
253
0
}
254
255
void
256
nsShmImage::DestroyImage()
257
0
{
258
0
  if (mGC) {
259
0
    xcb_free_gc(mConnection, mGC);
260
0
    mGC = XCB_NONE;
261
0
  }
262
0
  if (mPixmap != XCB_NONE) {
263
0
    xcb_free_pixmap(mConnection, mPixmap);
264
0
    mPixmap = XCB_NONE;
265
0
  }
266
0
  if (mShmSeg != XCB_NONE) {
267
0
    xcb_shm_detach_checked(mConnection, mShmSeg);
268
0
    mShmSeg = XCB_NONE;
269
0
  }
270
0
  DestroyShmSegment();
271
0
  // Avoid leaking any pending reply.  No real need to wait but CentOS 6 build
272
0
  // machines don't have xcb_discard_reply().
273
0
  WaitIfPendingReply();
274
0
}
275
276
// Wait for any in-flight shm-affected requests to complete.
277
// Typically X clients would wait for a XShmCompletionEvent to be received,
278
// but this works as it's sent immediately after the request is sent.
279
void
280
nsShmImage::WaitIfPendingReply()
281
0
{
282
0
  if (mRequestPending) {
283
0
    xcb_get_input_focus_reply_t* reply =
284
0
      xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
285
0
    free(reply);
286
0
    mRequestPending = false;
287
0
  }
288
0
}
289
290
already_AddRefed<DrawTarget>
291
nsShmImage::CreateDrawTarget(const mozilla::LayoutDeviceIntRegion& aRegion)
292
0
{
293
0
  WaitIfPendingReply();
294
0
295
0
  // Due to bug 1205045, we must avoid making GTK calls off the main thread to query window size.
296
0
  // Instead we just track the largest offset within the image we are drawing to and grow the image
297
0
  // to accomodate it. Since usually the entire window is invalidated on the first paint to it,
298
0
  // this should grow the image to the necessary size quickly without many intermediate reallocations.
299
0
  IntRect bounds = aRegion.GetBounds().ToUnknownRect();
300
0
  IntSize size(bounds.XMost(), bounds.YMost());
301
0
  if (size.width > mSize.width || size.height > mSize.height) {
302
0
    DestroyImage();
303
0
    if (!CreateImage(size)) {
304
0
      return nullptr;
305
0
    }
306
0
  }
307
0
308
0
  return gfxPlatform::CreateDrawTargetForData(
309
0
    reinterpret_cast<unsigned char*>(mShmAddr)
310
0
      + bounds.y * mStride + bounds.x * BytesPerPixel(mFormat),
311
0
    bounds.Size(),
312
0
    mStride,
313
0
    mFormat);
314
0
}
315
316
void
317
nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion)
318
0
{
319
0
  AutoTArray<xcb_rectangle_t, 32> xrects;
320
0
  xrects.SetCapacity(aRegion.GetNumRects());
321
0
322
0
  for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
323
0
    const mozilla::LayoutDeviceIntRect &r = iter.Get();
324
0
    xcb_rectangle_t xrect = { (short)r.x, (short)r.y, (unsigned short)r.width, (unsigned short)r.height };
325
0
    xrects.AppendElement(xrect);
326
0
  }
327
0
328
0
  if (!mGC) {
329
0
    mGC = xcb_generate_id(mConnection);
330
0
    xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
331
0
  }
332
0
333
0
  xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
334
0
                          xrects.Length(), xrects.Elements());
335
0
336
0
  if (mPixmap != XCB_NONE) {
337
0
    xcb_copy_area(mConnection, mPixmap, mWindow, mGC,
338
0
                  0, 0, 0, 0, mSize.width, mSize.height);
339
0
  } else {
340
0
    xcb_shm_put_image(mConnection, mWindow, mGC,
341
0
                      mSize.width, mSize.height,
342
0
                      0, 0, mSize.width, mSize.height,
343
0
                      0, 0, mDepth,
344
0
                      XCB_IMAGE_FORMAT_Z_PIXMAP, 0,
345
0
                      mShmSeg, 0);
346
0
  }
347
0
348
0
  // Send a request that returns a response so that we don't have to start a
349
0
  // sync in nsShmImage::CreateDrawTarget.
350
0
  mSyncRequest = xcb_get_input_focus(mConnection);
351
0
  mRequestPending = true;
352
0
353
0
  xcb_flush(mConnection);
354
0
}
355
356
#endif  // MOZ_HAVE_SHMIMAGE