1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 #if defined(MOZ_WAYLAND)
14 # include "nsClipboardWayland.h"
15 #endif
16 #include "nsContentUtils.h"
17 #include "HeadlessClipboard.h"
18 #include "nsSupportsPrimitives.h"
19 #include "nsString.h"
20 #include "nsReadableUtils.h"
21 #include "nsPrimitiveHelpers.h"
22 #include "nsImageToPixbuf.h"
23 #include "nsStringStream.h"
24 #include "nsIFileURL.h"
25 #include "nsIObserverService.h"
26 #include "mozilla/Services.h"
27 #include "mozilla/RefPtr.h"
28 #include "mozilla/SchedulerGroup.h"
29 #include "mozilla/TimeStamp.h"
30 #include "WidgetUtilsGtk.h"
31
32 #include "imgIContainer.h"
33
34 #include <gtk/gtk.h>
35 #include <gtk/gtkx.h>
36
37 #include "mozilla/Encoding.h"
38
39 using namespace mozilla;
40
41 // Idle timeout for receiving selection and property notify events (microsec)
42 const int kClipboardTimeout = 500000;
43
44 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
45 // detect the HTML as UTF-8 encoded.
46 static const char kHTMLMarkupPrefix[] =
47 R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
48
49 static const char kURIListMime[] = "text/uri-list";
50
51 // Callback when someone asks us for the data
52 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
53 GtkSelectionData* aSelectionData, guint info,
54 gpointer user_data);
55
56 // Callback when someone asks us to clear a clipboard
57 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
58
59 static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
60 nsCString& charset, char16_t** unicodeData,
61 int32_t& outUnicodeLen);
62
63 static bool GetHTMLCharset(const char* data, int32_t dataLength,
64 nsCString& str);
65
GetSelectionAtom(int32_t aWhichClipboard)66 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
67 if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
68 return GDK_SELECTION_CLIPBOARD;
69
70 return GDK_SELECTION_PRIMARY;
71 }
72
GetGeckoClipboardType(GtkClipboard * aGtkClipboard)73 int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
74 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
75 return nsClipboard::kSelectionClipboard;
76 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
77 return nsClipboard::kGlobalClipboard;
78
79 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
80 }
81
82 nsClipboard::nsClipboard() = default;
83
~nsClipboard()84 nsClipboard::~nsClipboard() {
85 // We have to clear clipboard before gdk_display_close() call.
86 // See bug 531580 for details.
87 if (mGlobalTransferable) {
88 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
89 }
90 if (mSelectionTransferable) {
91 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
92 }
93 }
94
NS_IMPL_ISUPPORTS(nsClipboard,nsIClipboard,nsIObserver)95 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
96
97 nsresult nsClipboard::Init(void) {
98 if (widget::GdkIsX11Display()) {
99 mContext = MakeUnique<nsRetrievalContextX11>();
100 #if defined(MOZ_WAYLAND)
101 } else if (widget::GdkIsWaylandDisplay()) {
102 mContext = MakeUnique<nsRetrievalContextWayland>();
103 #endif
104 } else {
105 NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
106 return NS_OK;
107 }
108
109 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
110 if (os) {
111 os->AddObserver(this, "xpcom-shutdown", false);
112 }
113
114 return NS_OK;
115 }
116
117 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)118 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
119 const char16_t* aData) {
120 // Save global clipboard content to CLIPBOARD_MANAGER.
121 // gtk_clipboard_store() can run an event loop, so call from a dedicated
122 // runnable.
123 return SchedulerGroup::Dispatch(
124 TaskCategory::Other,
125 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
126 LOGCLIP(("nsClipboard storing clipboard content\n"));
127 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
128 }));
129 }
130
131 NS_IMETHODIMP
SetData(nsITransferable * aTransferable,nsIClipboardOwner * aOwner,int32_t aWhichClipboard)132 nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
133 int32_t aWhichClipboard) {
134 // See if we can short cut
135 if ((aWhichClipboard == kGlobalClipboard &&
136 aTransferable == mGlobalTransferable.get() &&
137 aOwner == mGlobalOwner.get()) ||
138 (aWhichClipboard == kSelectionClipboard &&
139 aTransferable == mSelectionTransferable.get() &&
140 aOwner == mSelectionOwner.get())) {
141 return NS_OK;
142 }
143
144 LOGCLIP(("nsClipboard::SetData (%s)\n",
145 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
146
147 // List of suported targets
148 GtkTargetList* list = gtk_target_list_new(nullptr, 0);
149
150 // Get the types of supported flavors
151 nsTArray<nsCString> flavors;
152 nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
153 if (NS_FAILED(rv)) {
154 LOGCLIP((" FlavorsTransferableCanExport failed!\n"));
155 // Fall through. |gtkTargets| will be null below.
156 }
157
158 // Add all the flavors to this widget's supported type.
159 bool imagesAdded = false;
160 for (uint32_t i = 0; i < flavors.Length(); i++) {
161 nsCString& flavorStr = flavors[i];
162 LOGCLIP((" processing target %s\n", flavorStr.get()));
163
164 // Special case text/unicode since we can handle all of the string types.
165 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
166 LOGCLIP((" adding TEXT targets\n"));
167 gtk_target_list_add_text_targets(list, 0);
168 continue;
169 }
170
171 if (nsContentUtils::IsFlavorImage(flavorStr)) {
172 // Don't bother adding image targets twice
173 if (!imagesAdded) {
174 // accept any writable image type
175 LOGCLIP((" adding IMAGE targets\n"));
176 gtk_target_list_add_image_targets(list, 0, TRUE);
177 imagesAdded = true;
178 }
179 continue;
180 }
181
182 // Add this to our list of valid targets
183 LOGCLIP((" adding OTHER target %s\n", flavorStr.get()));
184 GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
185 gtk_target_list_add(list, atom, 0, 0);
186 }
187
188 // Get GTK clipboard (CLIPBOARD or PRIMARY)
189 GtkClipboard* gtkClipboard =
190 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
191
192 gint numTargets;
193 GtkTargetEntry* gtkTargets =
194 gtk_target_table_new_from_list(list, &numTargets);
195 if (!gtkTargets) {
196 LOGCLIP((" gtk_clipboard_set_with_data() failed!\n"));
197 // Clear references to the any old data and let GTK know that it is no
198 // longer available.
199 EmptyClipboard(aWhichClipboard);
200 return NS_ERROR_FAILURE;
201 }
202
203 // Set getcallback and request to store data after an application exit
204 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
205 clipboard_get_cb, clipboard_clear_cb, this)) {
206 // We managed to set-up the clipboard so update internal state
207 // We have to set it now because gtk_clipboard_set_with_data() calls
208 // clipboard_clear_cb() which reset our internal state
209 if (aWhichClipboard == kSelectionClipboard) {
210 mSelectionOwner = aOwner;
211 mSelectionTransferable = aTransferable;
212 } else {
213 mGlobalOwner = aOwner;
214 mGlobalTransferable = aTransferable;
215 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
216 }
217
218 rv = NS_OK;
219 } else {
220 LOGCLIP((" gtk_clipboard_set_with_data() failed!\n"));
221 EmptyClipboard(aWhichClipboard);
222 rv = NS_ERROR_FAILURE;
223 }
224
225 gtk_target_table_free(gtkTargets, numTargets);
226 gtk_target_list_unref(list);
227
228 return rv;
229 }
230
SetTransferableData(nsITransferable * aTransferable,nsCString & aFlavor,const char * aClipboardData,uint32_t aClipboardDataLength)231 void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
232 nsCString& aFlavor,
233 const char* aClipboardData,
234 uint32_t aClipboardDataLength) {
235 LOGCLIP(("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get()));
236
237 nsCOMPtr<nsISupports> wrapper;
238 nsPrimitiveHelpers::CreatePrimitiveForData(
239 aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
240 aTransferable->SetTransferData(aFlavor.get(), wrapper);
241 }
242
243 NS_IMETHODIMP
GetData(nsITransferable * aTransferable,int32_t aWhichClipboard)244 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
245 LOGCLIP(("nsClipboard::GetData (%s)\n",
246 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
247
248 if (!aTransferable || !mContext) {
249 return NS_ERROR_FAILURE;
250 }
251
252 // Get a list of flavors this transferable can import
253 nsTArray<nsCString> flavors;
254 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
255 if (NS_FAILED(rv)) {
256 LOGCLIP((" FlavorsTransferableCanImport falied!\n"));
257 return rv;
258 }
259
260 #ifdef MOZ_LOGGING
261 LOGCLIP(("Flavors which can be imported:\n"));
262 for (uint32_t i = 0; i < flavors.Length(); i++) {
263 LOGCLIP((" %s\n", flavors[i].get()));
264 }
265 #endif
266
267 for (uint32_t i = 0; i < flavors.Length(); i++) {
268 nsCString& flavorStr = flavors[i];
269
270 if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
271 flavorStr.EqualsLiteral(kJPGImageMime) ||
272 flavorStr.EqualsLiteral(kPNGImageMime) ||
273 flavorStr.EqualsLiteral(kGIFImageMime)) {
274 // Emulate support for image/jpg
275 if (flavorStr.EqualsLiteral(kJPGImageMime)) {
276 flavorStr.Assign(kJPEGImageMime);
277 }
278
279 LOGCLIP((" Getting image %s MIME clipboard data\n", flavorStr.get()));
280
281 uint32_t clipboardDataLength;
282 const char* clipboardData = mContext->GetClipboardData(
283 flavorStr.get(), aWhichClipboard, &clipboardDataLength);
284 if (!clipboardData) {
285 LOGCLIP((" %s type is missing\n", flavorStr.get()));
286 continue;
287 }
288
289 nsCOMPtr<nsIInputStream> byteStream;
290 NS_NewByteInputStream(getter_AddRefs(byteStream),
291 Span(clipboardData, clipboardDataLength),
292 NS_ASSIGNMENT_COPY);
293 aTransferable->SetTransferData(flavorStr.get(), byteStream);
294 LOGCLIP((" got %s MIME data\n", flavorStr.get()));
295
296 mContext->ReleaseClipboardData(clipboardData);
297 return NS_OK;
298 }
299
300 // Special case text/unicode since we can convert any
301 // string into text/unicode
302 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
303 LOGCLIP(
304 (" Getting unicode %s MIME clipboard data\n", flavorStr.get()));
305
306 const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
307 if (!clipboardData) {
308 LOGCLIP((" failed to get unicode data\n"));
309 // If the type was text/unicode and we couldn't get
310 // text off the clipboard, run the next loop
311 // iteration.
312 continue;
313 }
314
315 // Convert utf-8 into our unicode format.
316 NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
317 const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
318 uint32_t unicodeDataLength = ucs2string.Length() * 2;
319 SetTransferableData(aTransferable, flavorStr, unicodeData,
320 unicodeDataLength);
321 free((void*)unicodeData);
322
323 LOGCLIP((" got unicode data, length %zd\n", ucs2string.Length()));
324
325 mContext->ReleaseClipboardData(clipboardData);
326 return NS_OK;
327 }
328
329 if (flavorStr.EqualsLiteral(kFileMime)) {
330 LOGCLIP((" Getting %s file clipboard data\n", flavorStr.get()));
331
332 uint32_t clipboardDataLength;
333 const char* clipboardData = mContext->GetClipboardData(
334 kURIListMime, aWhichClipboard, &clipboardDataLength);
335 if (!clipboardData) {
336 LOGCLIP((" text/uri-list type is missing\n"));
337 continue;
338 }
339
340 nsDependentCSubstring data(clipboardData, clipboardDataLength);
341 nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(data);
342 if (!uris.IsEmpty()) {
343 nsCOMPtr<nsIURI> fileURI;
344 NS_NewURI(getter_AddRefs(fileURI), uris[0]);
345 if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
346 nsCOMPtr<nsIFile> file;
347 rv = fileURL->GetFile(getter_AddRefs(file));
348 if (NS_SUCCEEDED(rv)) {
349 aTransferable->SetTransferData(flavorStr.get(), file);
350 LOGCLIP((" successfully set file to clipboard\n"));
351 }
352 }
353 }
354
355 mContext->ReleaseClipboardData(clipboardData);
356 return NS_OK;
357 }
358
359 LOGCLIP((" Getting %s MIME clipboard data\n", flavorStr.get()));
360
361 uint32_t clipboardDataLength;
362 const char* clipboardData = mContext->GetClipboardData(
363 flavorStr.get(), aWhichClipboard, &clipboardDataLength);
364
365 #ifdef MOZ_LOGGING
366 if (!clipboardData) {
367 LOGCLIP((" %s type is missing\n", flavorStr.get()));
368 }
369 #endif
370
371 if (clipboardData) {
372 LOGCLIP((" got %s mime type data.\n", flavorStr.get()));
373
374 // Special case text/html since we can convert into UCS2
375 if (flavorStr.EqualsLiteral(kHTMLMime)) {
376 char16_t* htmlBody = nullptr;
377 int32_t htmlBodyLen = 0;
378 // Convert text/html into our unicode format
379 nsAutoCString charset;
380 if (!GetHTMLCharset(clipboardData, clipboardDataLength, charset)) {
381 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
382 LOGCLIP(("Failed to get html/text encoding, fall back to utf-8.\n"));
383 charset.AssignLiteral("utf-8");
384 }
385 if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
386 &htmlBody, htmlBodyLen)) {
387 LOGCLIP((" failed to convert text/html to UCS2.\n"));
388 mContext->ReleaseClipboardData(clipboardData);
389 continue;
390 }
391
392 SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
393 htmlBodyLen * 2);
394 free(htmlBody);
395 } else {
396 SetTransferableData(aTransferable, flavorStr, clipboardData,
397 clipboardDataLength);
398 }
399
400 mContext->ReleaseClipboardData(clipboardData);
401 return NS_OK;
402 }
403 }
404
405 LOGCLIP((" failed to get clipboard content.\n"));
406 return NS_OK;
407 }
408
409 NS_IMETHODIMP
EmptyClipboard(int32_t aWhichClipboard)410 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
411 LOGCLIP(("nsClipboard::EmptyClipboard (%s)\n",
412 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
413 if (aWhichClipboard == kSelectionClipboard) {
414 if (mSelectionTransferable) {
415 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
416 MOZ_ASSERT(!mSelectionTransferable);
417 }
418 } else {
419 if (mGlobalTransferable) {
420 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
421 MOZ_ASSERT(!mGlobalTransferable);
422 }
423 }
424
425 return NS_OK;
426 }
427
ClearTransferable(int32_t aWhichClipboard)428 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
429 if (aWhichClipboard == kSelectionClipboard) {
430 if (mSelectionOwner) {
431 mSelectionOwner->LosingOwnership(mSelectionTransferable);
432 mSelectionOwner = nullptr;
433 }
434 mSelectionTransferable = nullptr;
435 } else {
436 if (mGlobalOwner) {
437 mGlobalOwner->LosingOwnership(mGlobalTransferable);
438 mGlobalOwner = nullptr;
439 }
440 mGlobalTransferable = nullptr;
441 }
442 }
443
444 NS_IMETHODIMP
HasDataMatchingFlavors(const nsTArray<nsCString> & aFlavorList,int32_t aWhichClipboard,bool * _retval)445 nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
446 int32_t aWhichClipboard, bool* _retval) {
447 if (!_retval) {
448 return NS_ERROR_NULL_POINTER;
449 }
450
451 LOGCLIP(("nsClipboard::HasDataMatchingFlavors (%s)\n",
452 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
453
454 *_retval = false;
455
456 if (!mContext) {
457 return NS_ERROR_FAILURE;
458 }
459
460 int targetNums;
461 GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
462 if (!targets) {
463 LOGCLIP((" no targes at clipboard (null)\n"));
464 return NS_OK;
465 }
466
467 #ifdef MOZ_LOGGING
468 LOGCLIP((" Clipboard content (target nums %d):\n", targetNums));
469 for (int32_t j = 0; j < targetNums; j++) {
470 gchar* atom_name = gdk_atom_name(targets[j]);
471 if (!atom_name) {
472 LOGCLIP((" failed to get MIME\n"));
473 continue;
474 }
475 LOGCLIP((" MIME %s\n", atom_name));
476 }
477 LOGCLIP((" Asking for content:\n"));
478 for (auto& flavor : aFlavorList) {
479 LOGCLIP((" MIME %s\n", flavor.get()));
480 }
481 #endif
482
483 // Walk through the provided types and try to match it to a
484 // provided type.
485 for (auto& flavor : aFlavorList) {
486 // We special case text/unicode here.
487 if (flavor.EqualsLiteral(kUnicodeMime) &&
488 gtk_targets_include_text(targets, targetNums)) {
489 *_retval = true;
490 LOGCLIP((" has kUnicodeMime\n"));
491 break;
492 }
493
494 for (int32_t j = 0; j < targetNums; j++) {
495 gchar* atom_name = gdk_atom_name(targets[j]);
496 if (!atom_name) continue;
497
498 if (flavor.Equals(atom_name)) {
499 *_retval = true;
500 LOGCLIP((" has %s\n", atom_name));
501 }
502 // X clipboard supports image/jpeg, but we want to emulate support
503 // for image/jpg as well
504 else if (flavor.EqualsLiteral(kJPGImageMime) &&
505 !strcmp(atom_name, kJPEGImageMime)) {
506 *_retval = true;
507 LOGCLIP((" has image/jpg\n"));
508 }
509 // application/x-moz-file should be treated like text/uri-list
510 else if (flavor.EqualsLiteral(kFileMime) &&
511 !strcmp(atom_name, kURIListMime)) {
512 *_retval = true;
513 LOGCLIP((" has text/uri-list treating as application/x-moz-file"));
514 }
515
516 g_free(atom_name);
517
518 if (*_retval) break;
519 }
520 }
521
522 #ifdef MOZ_LOGGING
523 if (!(*_retval)) {
524 LOGCLIP((" no targes at clipboard (bad match)\n"));
525 }
526 #endif
527
528 g_free(targets);
529 return NS_OK;
530 }
531
532 NS_IMETHODIMP
SupportsSelectionClipboard(bool * _retval)533 nsClipboard::SupportsSelectionClipboard(bool* _retval) {
534 *_retval = mContext ? mContext->HasSelectionSupport() : false;
535 return NS_OK;
536 }
537
538 NS_IMETHODIMP
SupportsFindClipboard(bool * _retval)539 nsClipboard::SupportsFindClipboard(bool* _retval) {
540 *_retval = false;
541 return NS_OK;
542 }
543
GetTransferable(int32_t aWhichClipboard)544 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
545 nsITransferable* retval;
546
547 if (aWhichClipboard == kSelectionClipboard)
548 retval = mSelectionTransferable.get();
549 else
550 retval = mGlobalTransferable.get();
551
552 return retval;
553 }
554
SelectionGetEvent(GtkClipboard * aClipboard,GtkSelectionData * aSelectionData)555 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
556 GtkSelectionData* aSelectionData) {
557 // Someone has asked us to hand them something. The first thing
558 // that we want to do is see if that something includes text. If
559 // it does, try to give it text/unicode after converting it to
560 // utf-8.
561
562 int32_t whichClipboard;
563
564 // which clipboard?
565 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
566 if (selection == GDK_SELECTION_PRIMARY)
567 whichClipboard = kSelectionClipboard;
568 else if (selection == GDK_SELECTION_CLIPBOARD)
569 whichClipboard = kGlobalClipboard;
570 else
571 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
572
573 LOGCLIP(("nsClipboard::SelectionGetEvent (%s)\n",
574 whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
575
576 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
577 if (!trans) {
578 // We have nothing to serve
579 LOGCLIP(("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
580 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard"));
581 return;
582 }
583
584 nsresult rv;
585 nsCOMPtr<nsISupports> item;
586
587 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
588 LOGCLIP((" selection target %s\n", gdk_atom_name(selectionTarget)));
589
590 // Check to see if the selection data is some text type.
591 if (gtk_targets_include_text(&selectionTarget, 1)) {
592 LOGCLIP((" providing text/unicode data\n"));
593 // Try to convert our internal type into a text string. Get
594 // the transferable for this clipboard and try to get the
595 // text/unicode type for it.
596 rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
597 if (NS_FAILED(rv) || !item) {
598 LOGCLIP((" GetTransferData() failed to get text/unicode!\n"));
599 return;
600 }
601
602 nsCOMPtr<nsISupportsString> wideString;
603 wideString = do_QueryInterface(item);
604 if (!wideString) return;
605
606 nsAutoString ucs2string;
607 wideString->GetData(ucs2string);
608 NS_ConvertUTF16toUTF8 utf8string(ucs2string);
609
610 LOGCLIP((" sent %zd bytes of utf-8 data\n", utf8string.Length()));
611 if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
612 LOGCLIP(
613 (" using gtk_selection_data_set for 'text/plain;charset=utf-8'\n"));
614 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
615 // in some versions of GTK.
616 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
617 reinterpret_cast<const guchar*>(utf8string.get()),
618 utf8string.Length());
619 } else {
620 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
621 utf8string.Length());
622 }
623 return;
624 }
625
626 // Check to see if the selection data is an image type
627 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
628 LOGCLIP((" providing image data\n"));
629 // Look through our transfer data for the image
630 static const char* const imageMimeTypes[] = {kNativeImageMime,
631 kPNGImageMime, kJPEGImageMime,
632 kJPGImageMime, kGIFImageMime};
633 nsCOMPtr<nsISupports> imageItem;
634 nsCOMPtr<imgIContainer> image;
635 for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
636 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
637 if (NS_FAILED(rv)) {
638 LOGCLIP(
639 (" %s is missing at GetTransferData()\n", imageMimeTypes[i]));
640 continue;
641 }
642
643 image = do_QueryInterface(imageItem);
644 if (image) {
645 LOGCLIP(
646 (" %s is available at GetTransferData()\n", imageMimeTypes[i]));
647 break;
648 }
649 }
650
651 if (!image) { // Not getting an image for an image mime type!?
652 LOGCLIP((" Failed to get any image mime from GetTransferData()!\n"));
653 return;
654 }
655
656 GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
657 if (!pixbuf) {
658 LOGCLIP((" nsImageToPixbuf::ImageToPixbuf() failed!\n"));
659 return;
660 }
661
662 LOGCLIP((" Setting pixbuf image data as %s\n",
663 gdk_atom_name(selectionTarget)));
664 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
665 g_object_unref(pixbuf);
666 return;
667 }
668
669 if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
670 LOGCLIP((" providing %s data\n", kHTMLMime));
671 rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
672 if (NS_FAILED(rv) || !item) {
673 LOGCLIP((" failed to get %s data by GetTransferData()!\n", kHTMLMime));
674 return;
675 }
676
677 nsCOMPtr<nsISupportsString> wideString;
678 wideString = do_QueryInterface(item);
679 if (!wideString) {
680 LOGCLIP((" failed to get wideString interface!"));
681 return;
682 }
683
684 nsAutoString ucs2string;
685 wideString->GetData(ucs2string);
686
687 nsAutoCString html;
688 // Add the prefix so the encoding is correctly detected.
689 html.AppendLiteral(kHTMLMarkupPrefix);
690 AppendUTF16toUTF8(ucs2string, html);
691
692 LOGCLIP((" Setting %zd bytest of %s data\n", html.Length(),
693 gdk_atom_name(selectionTarget)));
694 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
695 (const guchar*)html.get(), html.Length());
696 return;
697 }
698
699 LOGCLIP((" Try if we have anything at GetTransferData() for %s\n",
700 gdk_atom_name(selectionTarget)));
701
702 // Try to match up the selection data target to something our
703 // transferable provides.
704 gchar* target_name = gdk_atom_name(selectionTarget);
705 if (!target_name) {
706 LOGCLIP((" Failed to get target name!\n"));
707 return;
708 }
709
710 rv = trans->GetTransferData(target_name, getter_AddRefs(item));
711 // nothing found?
712 if (NS_FAILED(rv) || !item) {
713 LOGCLIP((" Failed to get anything from GetTransferData()!\n"));
714 g_free(target_name);
715 return;
716 }
717
718 void* primitive_data = nullptr;
719 uint32_t dataLen = 0;
720 nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name),
721 item, &primitive_data, &dataLen);
722
723 if (primitive_data) {
724 LOGCLIP((" Setting %s as a primitive data type, %d bytes\n", target_name,
725 dataLen));
726 gtk_selection_data_set(aSelectionData, selectionTarget,
727 8, /* 8 bits in a unit */
728 (const guchar*)primitive_data, dataLen);
729 free(primitive_data);
730 } else {
731 LOGCLIP((" Failed to get primitive data!\n"));
732 }
733
734 g_free(target_name);
735 }
736
SelectionClearEvent(GtkClipboard * aGtkClipboard)737 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
738 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
739 if (whichClipboard < 0) {
740 return;
741 }
742
743 LOGCLIP(("nsClipboard::SelectionClearEvent (%s)\n",
744 whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
745
746 ClearTransferable(whichClipboard);
747 }
748
clipboard_get_cb(GtkClipboard * aGtkClipboard,GtkSelectionData * aSelectionData,guint info,gpointer user_data)749 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
750 GtkSelectionData* aSelectionData, guint info,
751 gpointer user_data) {
752 LOGCLIP(("clipboard_get_cb() callback\n"));
753 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
754 aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
755 }
756
clipboard_clear_cb(GtkClipboard * aGtkClipboard,gpointer user_data)757 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
758 LOGCLIP(("clipboard_clear_cb() callback\n"));
759 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
760 aClipboard->SelectionClearEvent(aGtkClipboard);
761 }
762
763 /*
764 * when copy-paste, mozilla wants data encoded using UCS2,
765 * other app such as StarOffice use "text/html"(RFC2854).
766 * This function convert data(got from GTK clipboard)
767 * to data mozilla wanted.
768 *
769 * data from GTK clipboard can be 3 forms:
770 * 1. From current mozilla
771 * "text/html", charset = utf-16
772 * 2. From old version mozilla or mozilla-based app
773 * content("body" only), charset = utf-16
774 * 3. From other app who use "text/html" when copy-paste
775 * "text/html", has "charset" info
776 *
777 * data : got from GTK clipboard
778 * dataLength: got from GTK clipboard
779 * body : pass to Mozilla
780 * bodyLength: pass to Mozilla
781 */
ConvertHTMLtoUCS2(const char * data,int32_t dataLength,nsCString & charset,char16_t ** unicodeData,int32_t & outUnicodeLen)782 bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
783 char16_t** unicodeData, int32_t& outUnicodeLen) {
784 if (charset.EqualsLiteral("UTF-16")) { // current mozilla
785 outUnicodeLen = (dataLength / 2) - 1;
786 *unicodeData = reinterpret_cast<char16_t*>(
787 moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
788 memcpy(*unicodeData, data + sizeof(char16_t),
789 outUnicodeLen * sizeof(char16_t));
790 (*unicodeData)[outUnicodeLen] = '\0';
791 return true;
792 }
793 if (charset.EqualsLiteral("UNKNOWN")) {
794 outUnicodeLen = 0;
795 return false;
796 }
797 // app which use "text/html" to copy&paste
798 // get the decoder
799 auto encoding = Encoding::ForLabelNoReplacement(charset);
800 if (!encoding) {
801 LOGCLIP(("ConvertHTMLtoUCS2: get unicode decoder error\n"));
802 outUnicodeLen = 0;
803 return false;
804 }
805
806 auto dataSpan = Span(data, dataLength);
807 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
808 // issues, but might confuse other users.
809 const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
810 if (dataSpan.Length() >= prefixLen &&
811 Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
812 dataSpan = dataSpan.From(prefixLen);
813 }
814
815 auto decoder = encoding->NewDecoder();
816 CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataSpan.Length());
817 if (!needed.isValid() || needed.value() > INT32_MAX) {
818 outUnicodeLen = 0;
819 return false;
820 }
821
822 outUnicodeLen = 0;
823 if (needed.value()) {
824 *unicodeData = reinterpret_cast<char16_t*>(
825 moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
826 uint32_t result;
827 size_t read;
828 size_t written;
829 bool hadErrors;
830 Tie(result, read, written, hadErrors) = decoder->DecodeToUTF16(
831 AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
832 MOZ_ASSERT(result == kInputEmpty);
833 MOZ_ASSERT(read == size_t(dataSpan.Length()));
834 MOZ_ASSERT(written <= needed.value());
835 Unused << hadErrors;
836 outUnicodeLen = written;
837 // null terminate.
838 (*unicodeData)[outUnicodeLen] = '\0';
839 return true;
840 } // if valid length
841 return false;
842 }
843
844 /*
845 * get "charset" information from clipboard data
846 * return value can be:
847 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
848 * 2. "UNKNOWN": mozilla can't detect what encode it use
849 * 3. other: "text/html" with other charset than utf-16
850 */
GetHTMLCharset(const char * data,int32_t dataLength,nsCString & str)851 bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
852 // if detect "FFFE" or "FEFF", assume UTF-16
853 char16_t* beginChar = (char16_t*)data;
854 if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
855 str.AssignLiteral("UTF-16");
856 LOGCLIP(("GetHTMLCharset: Charset of HTML is UTF-16\n"));
857 return true;
858 }
859 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
860 const nsDependentCSubstring htmlStr(data, dataLength);
861 nsACString::const_iterator start, end;
862 htmlStr.BeginReading(start);
863 htmlStr.EndReading(end);
864 nsACString::const_iterator valueStart(start), valueEnd(start);
865
866 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
867 start = end;
868 htmlStr.EndReading(end);
869
870 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
871 valueStart = end;
872 start = end;
873 htmlStr.EndReading(end);
874
875 if (FindCharInReadable('"', start, end)) valueEnd = start;
876 }
877 }
878 // find "charset" in HTML
879 if (valueStart != valueEnd) {
880 str = Substring(valueStart, valueEnd);
881 ToUpperCase(str);
882 LOGCLIP(("GetHTMLCharset: Charset of HTML = %s\n", str.get()));
883 return true;
884 }
885 str.AssignLiteral("UNKNOWN");
886 LOGCLIP(("GetHTMLCharset: Failed to get HTML Charset!\n"));
887 return false;
888 }
889