Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/widget/gtk/nsClipboardX11.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim:expandtab:shiftwidth=4:tabstop=4:
3
 */
4
/* This Source Code Form is subject to the terms of the Mozilla Public
5
 * License, v. 2.0. If a copy of the MPL was not distributed with this
6
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8
#include "mozilla/ArrayUtils.h"
9
10
#include "nsArrayUtils.h"
11
#include "nsClipboard.h"
12
#include "nsClipboardX11.h"
13
#include "nsSupportsPrimitives.h"
14
#include "nsString.h"
15
#include "nsReadableUtils.h"
16
#include "nsPrimitiveHelpers.h"
17
#include "nsIServiceManager.h"
18
#include "nsImageToPixbuf.h"
19
#include "nsStringStream.h"
20
#include "nsIObserverService.h"
21
#include "mozilla/Services.h"
22
#include "mozilla/RefPtr.h"
23
#include "mozilla/TimeStamp.h"
24
25
#include "imgIContainer.h"
26
27
#include <gtk/gtk.h>
28
29
// For manipulation of the X event queue
30
#include <X11/Xlib.h>
31
#include <gdk/gdkx.h>
32
#include <sys/time.h>
33
#include <sys/types.h>
34
#include <errno.h>
35
#include <unistd.h>
36
#include "X11UndefineNone.h"
37
38
using namespace mozilla;
39
40
bool
41
nsRetrievalContextX11::HasSelectionSupport(void)
42
0
{
43
0
  // yeah, unix supports the selection clipboard on X11.
44
0
  return true;
45
0
}
46
47
static GdkFilterReturn
48
selection_request_filter(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data)
49
0
{
50
0
    XEvent *xevent = static_cast<XEvent*>(gdk_xevent);
51
0
    if (xevent->xany.type == SelectionRequest) {
52
0
        if (xevent->xselectionrequest.requestor == X11None)
53
0
            return GDK_FILTER_REMOVE;
54
0
55
0
        GdkDisplay *display = gdk_x11_lookup_xdisplay(
56
0
                xevent->xselectionrequest.display);
57
0
        if (!display)
58
0
            return GDK_FILTER_REMOVE;
59
0
60
0
        GdkWindow *window = gdk_x11_window_foreign_new_for_display(display,
61
0
                xevent->xselectionrequest.requestor);
62
0
        if (!window)
63
0
            return GDK_FILTER_REMOVE;
64
0
65
0
        g_object_unref(window);
66
0
    }
67
0
    return GDK_FILTER_CONTINUE;
68
0
}
69
70
nsRetrievalContextX11::nsRetrievalContextX11()
71
  : mState(INITIAL)
72
  , mClipboardRequestNumber(0)
73
  , mClipboardData(nullptr)
74
  , mClipboardDataLength(0)
75
  , mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE))
76
0
{
77
0
    // A custom event filter to workaround attempting to dereference a null
78
0
    // selection requestor in GTK3 versions before 3.11.3. See bug 1178799.
79
0
#if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11)
80
0
    if (gtk_check_version(3, 11, 3))
81
0
        gdk_window_add_filter(nullptr, selection_request_filter, nullptr);
82
0
#endif
83
0
}
84
85
nsRetrievalContextX11::~nsRetrievalContextX11()
86
0
{
87
0
    gdk_window_remove_filter(nullptr, selection_request_filter, nullptr);
88
0
}
89
90
static void
91
DispatchSelectionNotifyEvent(GtkWidget *widget, XEvent *xevent)
92
0
{
93
0
    GdkEvent event;
94
0
    event.selection.type = GDK_SELECTION_NOTIFY;
95
0
    event.selection.window = gtk_widget_get_window(widget);
96
0
    event.selection.selection = gdk_x11_xatom_to_atom(xevent->xselection.selection);
97
0
    event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
98
0
    event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
99
0
    event.selection.time = xevent->xselection.time;
100
0
101
0
    gtk_widget_event(widget, &event);
102
0
}
103
104
static void
105
DispatchPropertyNotifyEvent(GtkWidget *widget, XEvent *xevent)
106
0
{
107
0
    GdkWindow *window = gtk_widget_get_window(widget);
108
0
    if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
109
0
        GdkEvent event;
110
0
        event.property.type = GDK_PROPERTY_NOTIFY;
111
0
        event.property.window = window;
112
0
        event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
113
0
        event.property.time = xevent->xproperty.time;
114
0
        event.property.state = xevent->xproperty.state;
115
0
116
0
        gtk_widget_event(widget, &event);
117
0
    }
118
0
}
119
120
struct checkEventContext
121
{
122
    GtkWidget *cbWidget;
123
    Atom       selAtom;
124
};
125
126
static Bool
127
checkEventProc(Display *display, XEvent *event, XPointer arg)
128
0
{
129
0
    checkEventContext *context = (checkEventContext *) arg;
130
0
131
0
    if (event->xany.type == SelectionNotify ||
132
0
        (event->xany.type == PropertyNotify &&
133
0
         event->xproperty.atom == context->selAtom)) {
134
0
135
0
        GdkWindow *cbWindow =
136
0
            gdk_x11_window_lookup_for_display(gdk_x11_lookup_xdisplay(display),
137
0
                                              event->xany.window);
138
0
        if (cbWindow) {
139
0
            GtkWidget *cbWidget = nullptr;
140
0
            gdk_window_get_user_data(cbWindow, (gpointer *)&cbWidget);
141
0
            if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
142
0
                context->cbWidget = cbWidget;
143
0
                return True;
144
0
            }
145
0
        }
146
0
    }
147
0
148
0
    return False;
149
0
}
150
151
bool
152
nsRetrievalContextX11::WaitForX11Content()
153
0
{
154
0
    if (mState == COMPLETED) { // the request completed synchronously
155
0
        return true;
156
0
    }
157
0
158
0
    GdkDisplay *gdkDisplay = gdk_display_get_default();
159
0
    if (GDK_IS_X11_DISPLAY(gdkDisplay)) {
160
0
        Display *xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
161
0
        checkEventContext context;
162
0
        context.cbWidget = nullptr;
163
0
        context.selAtom = gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION",
164
0
                                                                FALSE));
165
0
166
0
        // Send X events which are relevant to the ongoing selection retrieval
167
0
        // to the clipboard widget.  Wait until either the operation completes, or
168
0
        // we hit our timeout.  All other X events remain queued.
169
0
170
0
        int select_result;
171
0
172
0
        int cnumber = ConnectionNumber(xDisplay);
173
0
        fd_set select_set;
174
0
        FD_ZERO(&select_set);
175
0
        FD_SET(cnumber, &select_set);
176
0
        ++cnumber;
177
0
        TimeStamp start = TimeStamp::Now();
178
0
179
0
        do {
180
0
            XEvent xevent;
181
0
182
0
            while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
183
0
                                 (XPointer) &context)) {
184
0
185
0
                if (xevent.xany.type == SelectionNotify)
186
0
                    DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
187
0
                else
188
0
                    DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
189
0
190
0
                if (mState == COMPLETED) {
191
0
                    return true;
192
0
                }
193
0
            }
194
0
195
0
            TimeStamp now = TimeStamp::Now();
196
0
            struct timeval tv;
197
0
            tv.tv_sec = 0;
198
0
            tv.tv_usec = std::max<int32_t>(0,
199
0
                kClipboardTimeout - (now - start).ToMicroseconds());
200
0
            select_result = select(cnumber, &select_set, nullptr, nullptr, &tv);
201
0
        } while (select_result == 1 ||
202
0
                 (select_result == -1 && errno == EINTR));
203
0
    }
204
#ifdef DEBUG_CLIPBOARD
205
    printf("exceeded clipboard timeout\n");
206
#endif
207
0
    mState = TIMED_OUT;
208
0
    return false;
209
0
}
210
211
// Call this when data has been retrieved.
212
void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
213
                                     const void* aData,
214
                                     int aDataRequestNumber)
215
0
{
216
0
  if (mClipboardRequestNumber != aDataRequestNumber) {
217
0
      NS_WARNING("nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
218
0
      return;
219
0
  }
220
0
221
0
  if (mState == INITIAL) {
222
0
      mState = COMPLETED;
223
0
224
0
      MOZ_ASSERT(mClipboardData == nullptr &&
225
0
                 mClipboardDataLength == 0,
226
0
                 "We're leaking clipboard data!");
227
0
228
0
      switch (aDataType) {
229
0
      case CLIPBOARD_TEXT:
230
0
          {
231
0
              const char* text = static_cast<const char*>(aData);
232
0
              if (text) {
233
0
                  mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
234
0
                  mClipboardData = moz_xmalloc(mClipboardDataLength);
235
0
                  memcpy(mClipboardData, text, mClipboardDataLength);
236
0
              }
237
0
          }
238
0
          break;
239
0
      case CLIPBOARD_TARGETS:
240
0
          {
241
0
              const GtkSelectionData *selection =
242
0
                  static_cast<const GtkSelectionData *>(aData);
243
0
244
0
              gint n_targets = 0;
245
0
              GdkAtom *targets = nullptr;
246
0
247
0
              if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
248
0
                  !n_targets) {
249
0
                  return;
250
0
              }
251
0
252
0
              mClipboardData = targets;
253
0
              mClipboardDataLength = n_targets;
254
0
          }
255
0
          break;
256
0
      case CLIPBOARD_DATA:
257
0
          {
258
0
              const GtkSelectionData *selection =
259
0
                  static_cast<const GtkSelectionData *>(aData);
260
0
261
0
              gint dataLength = gtk_selection_data_get_length(selection);
262
0
              if (dataLength > 0) {
263
0
                  mClipboardDataLength = dataLength;
264
0
                  mClipboardData = moz_xmalloc(dataLength);
265
0
                  memcpy(mClipboardData, gtk_selection_data_get_data(selection),
266
0
                         dataLength);
267
0
              }
268
0
          }
269
0
          break;
270
0
      }
271
0
  } else {
272
0
      // Already timed out
273
0
      MOZ_ASSERT(mState == TIMED_OUT);
274
0
  }
275
0
}
276
277
static void
278
clipboard_contents_received(GtkClipboard     *clipboard,
279
                            GtkSelectionData *selection_data,
280
                            gpointer          data)
281
0
{
282
0
    ClipboardRequestHandler *handler =
283
0
        static_cast<ClipboardRequestHandler*>(data);
284
0
    handler->Complete(selection_data);
285
0
    delete handler;
286
0
}
287
288
static void
289
clipboard_text_received(GtkClipboard     *clipboard,
290
                        const gchar      *text,
291
                        gpointer          data)
292
0
{
293
0
    ClipboardRequestHandler *handler =
294
0
        static_cast<ClipboardRequestHandler*>(data);
295
0
    handler->Complete(text);
296
0
    delete handler;
297
0
}
298
299
bool
300
nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
301
                                            GtkClipboard *clipboard,
302
                                            const char *aMimeType)
303
0
{
304
0
    mState = INITIAL;
305
0
    NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
306
0
307
0
    // Call ClipboardRequestHandler() with unique clipboard request number.
308
0
    // The request number pairs gtk_clipboard_request_contents() data request
309
0
    // with clipboard_contents_received() callback where the data
310
0
    // is provided by Gtk.
311
0
    mClipboardRequestNumber++;
312
0
    ClipboardRequestHandler* handler =
313
0
        new ClipboardRequestHandler(this, aDataType, mClipboardRequestNumber);
314
0
315
0
    switch (aDataType) {
316
0
    case CLIPBOARD_DATA:
317
0
        gtk_clipboard_request_contents(clipboard,
318
0
            gdk_atom_intern(aMimeType, FALSE), clipboard_contents_received,
319
0
            handler);
320
0
        break;
321
0
    case CLIPBOARD_TEXT:
322
0
        gtk_clipboard_request_text(clipboard, clipboard_text_received,
323
0
            handler);
324
0
        break;
325
0
    case CLIPBOARD_TARGETS:
326
0
        gtk_clipboard_request_contents(clipboard,
327
0
            mTargetMIMEType, clipboard_contents_received,
328
0
            handler);
329
0
        break;
330
0
    }
331
0
332
0
    return WaitForX11Content();
333
0
}
334
335
GdkAtom*
336
nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard, int* aTargetNums)
337
0
{
338
0
    GtkClipboard *clipboard =
339
0
        gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
340
0
341
0
    if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard))
342
0
        return nullptr;
343
0
344
0
    *aTargetNums = mClipboardDataLength;
345
0
    GdkAtom* targets = static_cast<GdkAtom*>(mClipboardData);
346
0
347
0
    // We don't hold the target list internally but we transfer the ownership.
348
0
    mClipboardData = nullptr;
349
0
    mClipboardDataLength = 0;
350
0
351
0
    return targets;
352
0
}
353
354
const char*
355
nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
356
                                        int32_t aWhichClipboard,
357
                                        uint32_t* aContentLength)
358
0
{
359
0
    GtkClipboard *clipboard;
360
0
    clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
361
0
362
0
    if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
363
0
        return nullptr;
364
0
365
0
    *aContentLength = mClipboardDataLength;
366
0
    return static_cast<const char*>(mClipboardData);
367
0
}
368
369
const char*
370
nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard)
371
0
{
372
0
    GtkClipboard *clipboard;
373
0
    clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
374
0
375
0
    if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard))
376
0
        return nullptr;
377
0
378
0
    return static_cast<const char*>(mClipboardData);
379
0
}
380
381
void nsRetrievalContextX11::ReleaseClipboardData(const char* aClipboardData)
382
0
{
383
0
    NS_ASSERTION(aClipboardData == mClipboardData,
384
0
        "Releasing unknown clipboard data!");
385
0
    free((void*)aClipboardData);
386
0
387
0
    mClipboardData = nullptr;
388
0
    mClipboardDataLength = 0;
389
0
}