Bug 1527719 [wpt PR 15359] - KV storage: make backingStore return the same frozen...
[gecko.git] / widget / nsShmImage.cpp
blob7208b7075f11f1f520b2b06a5f374c8948cdc80e
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
7 #include "nsShmImage.h"
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"
17 # include <dlfcn.h>
18 # include <errno.h>
19 # include <string.h>
20 # include <sys/ipc.h>
21 # include <sys/shm.h>
23 extern "C" {
24 # include <X11/ImUtil.h>
27 using namespace mozilla::ipc;
28 using namespace mozilla::gfx;
30 nsShmImage::nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
31 unsigned int aDepth)
32 : mDisplay(aDisplay),
33 mConnection(XGetXCBConnection(aDisplay)),
34 mWindow(aWindow),
35 mVisual(aVisual),
36 mDepth(aDepth),
37 mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN),
38 mSize(0, 0),
39 mStride(0),
40 mPixmap(XCB_NONE),
41 mGC(XCB_NONE),
42 mRequestPending(false),
43 mShmSeg(XCB_NONE),
44 mShmId(-1),
45 mShmAddr(nullptr) {
46 mozilla::PodZero(&mSyncRequest);
49 nsShmImage::~nsShmImage() { DestroyImage(); }
51 // If XShm isn't available to our client, we'll try XShm once, fail,
52 // set this to false and then never try again.
53 static bool gShmAvailable = true;
54 bool nsShmImage::UseShm() { return gShmAvailable; }
56 bool nsShmImage::CreateShmSegment() {
57 size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
59 # if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
60 static mozilla::LazyLogModule sPledgeLog("SandboxPledge");
61 MOZ_LOG(sPledgeLog, mozilla::LogLevel::Debug,
62 ("%s called when pledged, returning false\n", __func__));
63 return false;
64 # endif
65 mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
66 if (mShmId == -1) {
67 return false;
69 mShmAddr = (uint8_t*)shmat(mShmId, nullptr, 0);
70 mShmSeg = xcb_generate_id(mConnection);
72 // Mark the handle removed so that it will destroy the segment when unmapped.
73 shmctl(mShmId, IPC_RMID, nullptr);
75 if (mShmAddr == (void*)-1) {
76 // Since mapping failed, the segment is already destroyed.
77 mShmId = -1;
79 nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
80 NS_WARNING(warning.get());
81 return false;
84 # ifdef DEBUG
85 struct shmid_ds info;
86 if (shmctl(mShmId, IPC_STAT, &info) < 0) {
87 return false;
90 MOZ_ASSERT(size <= info.shm_segsz, "Segment doesn't have enough space!");
91 # endif
93 return true;
96 void nsShmImage::DestroyShmSegment() {
97 if (mShmId != -1) {
98 shmdt(mShmAddr);
99 mShmId = -1;
103 static bool gShmInitialized = false;
104 static bool gUseShmPixmaps = false;
106 bool nsShmImage::InitExtension() {
107 if (gShmInitialized) {
108 return gShmAvailable;
111 gShmInitialized = true;
113 // Bugs 1397918, 1293474 - race condition in libxcb fixed upstream as of
114 // version 1.11. Since we can't query libxcb's version directly, the only
115 // other option is to check for symbols that were added after 1.11.
116 // xcb_discard_reply64 was added in 1.11.1, so check for existence of
117 // that to verify we are using a version of libxcb with the bug fixed.
118 // Otherwise, we can't risk using libxcb due to aforementioned crashes.
119 if (!dlsym(RTLD_DEFAULT, "xcb_discard_reply64")) {
120 gShmAvailable = false;
121 return false;
124 const xcb_query_extension_reply_t* extReply;
125 extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
126 if (!extReply || !extReply->present) {
127 gShmAvailable = false;
128 return false;
131 xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
132 mConnection, xcb_shm_query_version(mConnection), nullptr);
134 if (!shmReply) {
135 gShmAvailable = false;
136 return false;
139 gUseShmPixmaps = shmReply->shared_pixmaps &&
140 shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
142 free(shmReply);
144 return true;
147 bool nsShmImage::CreateImage(const IntSize& aSize) {
148 MOZ_ASSERT(mConnection && mVisual);
150 if (!InitExtension()) {
151 return false;
154 mSize = aSize;
156 BackendType backend = gfxVars::ContentBackend();
158 mFormat = SurfaceFormat::UNKNOWN;
159 switch (mDepth) {
160 case 32:
161 if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
162 mVisual->blue_mask == 0xff) {
163 mFormat = SurfaceFormat::B8G8R8A8;
165 break;
166 case 24:
167 // Only support the BGRX layout, and report it as BGRA to the compositor.
168 // The alpha channel will be discarded when we put the image.
169 // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
170 // just report it as BGRX directly in that case.
171 if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
172 mVisual->blue_mask == 0xff) {
173 mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8
174 : SurfaceFormat::B8G8R8A8;
176 break;
177 case 16:
178 if (mVisual->red_mask == 0xf800 && mVisual->green_mask == 0x07e0 &&
179 mVisual->blue_mask == 0x1f) {
180 mFormat = SurfaceFormat::R5G6B5_UINT16;
182 break;
185 if (mFormat == SurfaceFormat::UNKNOWN) {
186 NS_WARNING("Unsupported XShm Image format!");
187 gShmAvailable = false;
188 return false;
191 // Round up stride to the display's scanline pad (in bits) as XShm expects.
192 int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
193 int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
194 int bitsPerLine =
195 ((bitsPerPixel * aSize.width + scanlinePad - 1) / scanlinePad) *
196 scanlinePad;
197 mStride = bitsPerLine / 8;
199 if (!CreateShmSegment()) {
200 DestroyImage();
201 return false;
204 xcb_generic_error_t* error;
205 xcb_void_cookie_t cookie;
207 cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
209 if ((error = xcb_request_check(mConnection, cookie))) {
210 NS_WARNING("Failed to attach MIT-SHM segment.");
211 DestroyImage();
212 gShmAvailable = false;
213 free(error);
214 return false;
217 if (gUseShmPixmaps) {
218 mPixmap = xcb_generate_id(mConnection);
219 cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
220 aSize.width, aSize.height, mDepth,
221 mShmSeg, 0);
223 if ((error = xcb_request_check(mConnection, cookie))) {
224 // Disable shared pixmaps permanently if creation failed.
225 mPixmap = XCB_NONE;
226 gUseShmPixmaps = false;
227 free(error);
231 return true;
234 void nsShmImage::DestroyImage() {
235 if (mGC) {
236 xcb_free_gc(mConnection, mGC);
237 mGC = XCB_NONE;
239 if (mPixmap != XCB_NONE) {
240 xcb_free_pixmap(mConnection, mPixmap);
241 mPixmap = XCB_NONE;
243 if (mShmSeg != XCB_NONE) {
244 xcb_shm_detach_checked(mConnection, mShmSeg);
245 mShmSeg = XCB_NONE;
247 DestroyShmSegment();
248 // Avoid leaking any pending reply. No real need to wait but CentOS 6 build
249 // machines don't have xcb_discard_reply().
250 WaitIfPendingReply();
253 // Wait for any in-flight shm-affected requests to complete.
254 // Typically X clients would wait for a XShmCompletionEvent to be received,
255 // but this works as it's sent immediately after the request is sent.
256 void nsShmImage::WaitIfPendingReply() {
257 if (mRequestPending) {
258 xcb_get_input_focus_reply_t* reply =
259 xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
260 free(reply);
261 mRequestPending = false;
265 already_AddRefed<DrawTarget> nsShmImage::CreateDrawTarget(
266 const mozilla::LayoutDeviceIntRegion& aRegion) {
267 WaitIfPendingReply();
269 // Due to bug 1205045, we must avoid making GTK calls off the main thread to
270 // query window size. Instead we just track the largest offset within the
271 // image we are drawing to and grow the image to accomodate it. Since usually
272 // the entire window is invalidated on the first paint to it, this should grow
273 // the image to the necessary size quickly without many intermediate
274 // reallocations.
275 IntRect bounds = aRegion.GetBounds().ToUnknownRect();
276 IntSize size(bounds.XMost(), bounds.YMost());
277 if (size.width > mSize.width || size.height > mSize.height) {
278 DestroyImage();
279 if (!CreateImage(size)) {
280 return nullptr;
284 return gfxPlatform::CreateDrawTargetForData(
285 reinterpret_cast<unsigned char*>(mShmAddr) + bounds.y * mStride +
286 bounds.x * BytesPerPixel(mFormat),
287 bounds.Size(), mStride, mFormat);
290 void nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion) {
291 AutoTArray<xcb_rectangle_t, 32> xrects;
292 xrects.SetCapacity(aRegion.GetNumRects());
294 for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
295 const mozilla::LayoutDeviceIntRect& r = iter.Get();
296 xcb_rectangle_t xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
297 (unsigned short)r.height};
298 xrects.AppendElement(xrect);
301 if (!mGC) {
302 mGC = xcb_generate_id(mConnection);
303 xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
306 xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
307 xrects.Length(), xrects.Elements());
309 if (mPixmap != XCB_NONE) {
310 xcb_copy_area(mConnection, mPixmap, mWindow, mGC, 0, 0, 0, 0, mSize.width,
311 mSize.height);
312 } else {
313 xcb_shm_put_image(mConnection, mWindow, mGC, mSize.width, mSize.height, 0,
314 0, mSize.width, mSize.height, 0, 0, mDepth,
315 XCB_IMAGE_FORMAT_Z_PIXMAP, 0, mShmSeg, 0);
318 // Send a request that returns a response so that we don't have to start a
319 // sync in nsShmImage::CreateDrawTarget.
320 mSyncRequest = xcb_get_input_focus(mConnection);
321 mRequestPending = true;
323 xcb_flush(mConnection);
326 #endif // MOZ_HAVE_SHMIMAGE