Bug 1734943 [wpt PR 31170] - Correct scrolling contents cull rect, a=testonly
[gecko.git] / widget / gtk / nsClipboard.cpp
blob1bd99a2cbb0bde8e812532d76bcaa62fc3b0aadd
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/. */
8 #include "mozilla/ArrayUtils.h"
10 #include "nsArrayUtils.h"
11 #include "nsClipboard.h"
12 #include "nsClipboardX11.h"
13 #if defined(MOZ_WAYLAND)
14 # include "nsClipboardWayland.h"
15 # include "nsClipboardWaylandAsync.h"
16 #endif
17 #include "nsIURI.h"
18 #include "nsIFile.h"
19 #include "nsNetUtil.h"
20 #include "nsContentUtils.h"
21 #include "HeadlessClipboard.h"
22 #include "nsSupportsPrimitives.h"
23 #include "nsString.h"
24 #include "nsReadableUtils.h"
25 #include "nsPrimitiveHelpers.h"
26 #include "nsImageToPixbuf.h"
27 #include "nsStringStream.h"
28 #include "nsIFileURL.h"
29 #include "nsIObserverService.h"
30 #include "mozilla/Services.h"
31 #include "mozilla/RefPtr.h"
32 #include "mozilla/SchedulerGroup.h"
33 #include "mozilla/StaticPrefs_widget.h"
34 #include "mozilla/TimeStamp.h"
35 #include "WidgetUtilsGtk.h"
37 #include "imgIContainer.h"
39 #include <gtk/gtk.h>
40 #include <gtk/gtkx.h>
42 #include "mozilla/Encoding.h"
44 using namespace mozilla;
46 // Idle timeout for receiving selection and property notify events (microsec)
47 // Right now it's set to 1 sec.
48 const int kClipboardTimeout = 1000000;
50 // Defines how many event loop iterations will be done without sleep.
51 // We ususally get data in first 2-3 iterations unless some large object
52 // (an image for instance) is transferred through clipboard.
53 const int kClipboardFastIterationNum = 3;
55 // We add this prefix to HTML markup, so that GetHTMLCharset can correctly
56 // detect the HTML as UTF-8 encoded.
57 static const char kHTMLMarkupPrefix[] =
58 R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
60 static const char kURIListMime[] = "text/uri-list";
62 // Callback when someone asks us for the data
63 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
64 GtkSelectionData* aSelectionData, guint info,
65 gpointer user_data);
67 // Callback when someone asks us to clear a clipboard
68 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
70 static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
71 nsCString& charset, char16_t** unicodeData,
72 int32_t& outUnicodeLen);
74 static bool GetHTMLCharset(const char* data, int32_t dataLength,
75 nsCString& str);
77 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
78 if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
79 return GDK_SELECTION_CLIPBOARD;
81 return GDK_SELECTION_PRIMARY;
84 int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
85 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
86 return nsClipboard::kSelectionClipboard;
87 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
88 return nsClipboard::kGlobalClipboard;
90 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
93 nsClipboard::nsClipboard() = default;
95 nsClipboard::~nsClipboard() {
96 // We have to clear clipboard before gdk_display_close() call.
97 // See bug 531580 for details.
98 if (mGlobalTransferable) {
99 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
101 if (mSelectionTransferable) {
102 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
106 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
108 nsresult nsClipboard::Init(void) {
109 if (widget::GdkIsX11Display()) {
110 mContext = new nsRetrievalContextX11();
111 #if defined(MOZ_WAYLAND)
112 } else if (widget::GdkIsWaylandDisplay()) {
113 if (StaticPrefs::widget_wayland_async_clipboard_enabled_AtStartup()) {
114 mContext = new nsRetrievalContextWaylandAsync();
115 } else {
116 mContext = new nsRetrievalContextWayland();
118 #endif
119 } else {
120 NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
121 return NS_OK;
124 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
125 if (os) {
126 os->AddObserver(this, "xpcom-shutdown", false);
129 return NS_OK;
132 NS_IMETHODIMP
133 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
134 const char16_t* aData) {
135 // Save global clipboard content to CLIPBOARD_MANAGER.
136 // gtk_clipboard_store() can run an event loop, so call from a dedicated
137 // runnable.
138 return SchedulerGroup::Dispatch(
139 TaskCategory::Other,
140 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
141 LOGCLIP("nsClipboard storing clipboard content\n");
142 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
143 }));
146 NS_IMETHODIMP
147 nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
148 int32_t aWhichClipboard) {
149 // See if we can short cut
150 if ((aWhichClipboard == kGlobalClipboard &&
151 aTransferable == mGlobalTransferable.get() &&
152 aOwner == mGlobalOwner.get()) ||
153 (aWhichClipboard == kSelectionClipboard &&
154 aTransferable == mSelectionTransferable.get() &&
155 aOwner == mSelectionOwner.get())) {
156 return NS_OK;
159 LOGCLIP("nsClipboard::SetData (%s)\n",
160 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
162 // List of suported targets
163 GtkTargetList* list = gtk_target_list_new(nullptr, 0);
165 // Get the types of supported flavors
166 nsTArray<nsCString> flavors;
167 nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
168 if (NS_FAILED(rv)) {
169 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
170 // Fall through. |gtkTargets| will be null below.
173 // Add all the flavors to this widget's supported type.
174 bool imagesAdded = false;
175 for (uint32_t i = 0; i < flavors.Length(); i++) {
176 nsCString& flavorStr = flavors[i];
177 LOGCLIP(" processing target %s\n", flavorStr.get());
179 // Special case text/unicode since we can handle all of the string types.
180 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
181 LOGCLIP(" adding TEXT targets\n");
182 gtk_target_list_add_text_targets(list, 0);
183 continue;
186 if (nsContentUtils::IsFlavorImage(flavorStr)) {
187 // Don't bother adding image targets twice
188 if (!imagesAdded) {
189 // accept any writable image type
190 LOGCLIP(" adding IMAGE targets\n");
191 gtk_target_list_add_image_targets(list, 0, TRUE);
192 imagesAdded = true;
194 continue;
197 // Add this to our list of valid targets
198 LOGCLIP(" adding OTHER target %s\n", flavorStr.get());
199 GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
200 gtk_target_list_add(list, atom, 0, 0);
203 // Get GTK clipboard (CLIPBOARD or PRIMARY)
204 GtkClipboard* gtkClipboard =
205 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
207 gint numTargets = 0;
208 GtkTargetEntry* gtkTargets =
209 gtk_target_table_new_from_list(list, &numTargets);
210 if (!gtkTargets || numTargets == 0) {
211 LOGCLIP(
212 " gtk_target_table_new_from_list() failed or empty list of "
213 "targets!\n");
214 // Clear references to the any old data and let GTK know that it is no
215 // longer available.
216 EmptyClipboard(aWhichClipboard);
217 return NS_ERROR_FAILURE;
220 // Set getcallback and request to store data after an application exit
221 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
222 clipboard_get_cb, clipboard_clear_cb, this)) {
223 // We managed to set-up the clipboard so update internal state
224 // We have to set it now because gtk_clipboard_set_with_data() calls
225 // clipboard_clear_cb() which reset our internal state
226 if (aWhichClipboard == kSelectionClipboard) {
227 mSelectionOwner = aOwner;
228 mSelectionTransferable = aTransferable;
229 } else {
230 mGlobalOwner = aOwner;
231 mGlobalTransferable = aTransferable;
232 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
235 rv = NS_OK;
236 } else {
237 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
238 EmptyClipboard(aWhichClipboard);
239 rv = NS_ERROR_FAILURE;
242 gtk_target_table_free(gtkTargets, numTargets);
243 gtk_target_list_unref(list);
245 return rv;
248 void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
249 nsCString& aFlavor,
250 const char* aClipboardData,
251 uint32_t aClipboardDataLength) {
252 LOGCLIP("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get());
254 nsCOMPtr<nsISupports> wrapper;
255 nsPrimitiveHelpers::CreatePrimitiveForData(
256 aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
257 aTransferable->SetTransferData(aFlavor.get(), wrapper);
260 static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
261 const char* aMime) {
262 for (const auto& flavorStr : aFlavourList) {
263 if (flavorStr.Equals(aMime)) {
264 return true;
267 return false;
270 // When clipboard contains only images, X11/Gtk tries to convert them
271 // to text when we request text instead of just fail to provide the data.
272 // So if clipboard contains images only remove text MIME offer.
273 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
274 nsTArray<nsCString>& aFlavors) {
275 LOGCLIP("nsClipboard::FilterImportedFlavors");
277 int targetNums;
278 GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
279 if (!targets) {
280 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
281 return false;
284 for (int i = 0; i < targetNums; i++) {
285 gchar* atom_name = gdk_atom_name(targets[i]);
286 if (!atom_name) {
287 continue;
289 // Filter out system MIME types.
290 if (strcmp(atom_name, "TARGETS") == 0 ||
291 strcmp(atom_name, "TIMESTAMP") == 0 ||
292 strcmp(atom_name, "SAVE_TARGETS") == 0 ||
293 strcmp(atom_name, "MULTIPLE") == 0) {
294 continue;
296 // Filter out types which can't be converted to text.
297 if (strncmp(atom_name, "image/", 6) == 0 ||
298 strncmp(atom_name, "application/", 12) == 0 ||
299 strncmp(atom_name, "audio/", 6) == 0 ||
300 strncmp(atom_name, "video/", 6) == 0) {
301 continue;
303 // We have some other MIME type on clipboard which can be hopefully
304 // converted to text without any problem.
305 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
306 return true;
309 // So make sure we offer only types we have at clipboard.
310 nsTArray<nsCString> clipboardFlavors;
311 for (int i = 0; i < targetNums; i++) {
312 gchar* atom_name = gdk_atom_name(targets[i]);
313 if (!atom_name) {
314 continue;
316 if (IsMIMEAtFlavourList(aFlavors, atom_name)) {
317 clipboardFlavors.AppendElement(nsCString(atom_name));
320 aFlavors.SwapElements(clipboardFlavors);
321 #ifdef MOZ_LOGGING
322 LOGCLIP(" X11: Flavors which match clipboard content:\n");
323 for (uint32_t i = 0; i < aFlavors.Length(); i++) {
324 LOGCLIP(" %s\n", aFlavors[i].get());
326 #endif
327 return true;
330 NS_IMETHODIMP
331 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
332 LOGCLIP("nsClipboard::GetData (%s)\n",
333 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
335 // TODO: Ensure we don't re-enter here.
336 if (!aTransferable || !mContext) {
337 return NS_ERROR_FAILURE;
340 // Get a list of flavors this transferable can import
341 nsTArray<nsCString> flavors;
342 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
343 if (NS_FAILED(rv)) {
344 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
345 return rv;
347 #ifdef MOZ_LOGGING
348 LOGCLIP("Flavors which can be imported:\n");
349 for (uint32_t i = 0; i < flavors.Length(); i++) {
350 LOGCLIP(" %s\n", flavors[i].get());
352 #endif
354 // Filter out MIME types on X11 to prevent unwanted conversions,
355 // see Bug 1611407
356 if (widget::GdkIsX11Display()) {
357 if (!FilterImportedFlavors(aWhichClipboard, flavors)) {
358 return NS_OK;
362 for (uint32_t i = 0; i < flavors.Length(); i++) {
363 nsCString& flavorStr = flavors[i];
365 if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
366 flavorStr.EqualsLiteral(kJPGImageMime) ||
367 flavorStr.EqualsLiteral(kPNGImageMime) ||
368 flavorStr.EqualsLiteral(kGIFImageMime)) {
369 // Emulate support for image/jpg
370 if (flavorStr.EqualsLiteral(kJPGImageMime)) {
371 flavorStr.Assign(kJPEGImageMime);
374 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
376 uint32_t clipboardDataLength;
377 const char* clipboardData = mContext->GetClipboardData(
378 flavorStr.get(), aWhichClipboard, &clipboardDataLength);
379 if (!clipboardData) {
380 LOGCLIP(" %s type is missing\n", flavorStr.get());
381 continue;
384 nsCOMPtr<nsIInputStream> byteStream;
385 NS_NewByteInputStream(getter_AddRefs(byteStream),
386 Span(clipboardData, clipboardDataLength),
387 NS_ASSIGNMENT_COPY);
388 aTransferable->SetTransferData(flavorStr.get(), byteStream);
389 LOGCLIP(" got %s MIME data\n", flavorStr.get());
391 mContext->ReleaseClipboardData(clipboardData);
392 return NS_OK;
395 // Special case text/unicode since we can convert any
396 // string into text/unicode
397 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
398 LOGCLIP(" Getting unicode %s MIME clipboard data\n", flavorStr.get());
400 const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
401 if (!clipboardData) {
402 LOGCLIP(" failed to get unicode data\n");
403 // If the type was text/unicode and we couldn't get
404 // text off the clipboard, run the next loop
405 // iteration.
406 continue;
409 // Convert utf-8 into our unicode format.
410 NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
411 const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
412 uint32_t unicodeDataLength = ucs2string.Length() * 2;
413 SetTransferableData(aTransferable, flavorStr, unicodeData,
414 unicodeDataLength);
415 free((void*)unicodeData);
417 LOGCLIP(" got unicode data, length %d\n", ucs2string.Length());
419 mContext->ReleaseClipboardData(clipboardData);
420 return NS_OK;
423 if (flavorStr.EqualsLiteral(kFileMime)) {
424 LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
426 uint32_t clipboardDataLength;
427 const char* clipboardData = mContext->GetClipboardData(
428 kURIListMime, aWhichClipboard, &clipboardDataLength);
429 if (!clipboardData) {
430 LOGCLIP(" text/uri-list type is missing\n");
431 continue;
434 nsDependentCSubstring data(clipboardData, clipboardDataLength);
435 nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(data);
436 if (!uris.IsEmpty()) {
437 nsCOMPtr<nsIURI> fileURI;
438 NS_NewURI(getter_AddRefs(fileURI), uris[0]);
439 if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
440 nsCOMPtr<nsIFile> file;
441 rv = fileURL->GetFile(getter_AddRefs(file));
442 if (NS_SUCCEEDED(rv)) {
443 aTransferable->SetTransferData(flavorStr.get(), file);
444 LOGCLIP(" successfully set file to clipboard\n");
449 mContext->ReleaseClipboardData(clipboardData);
450 return NS_OK;
453 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
455 uint32_t clipboardDataLength;
456 const char* clipboardData = mContext->GetClipboardData(
457 flavorStr.get(), aWhichClipboard, &clipboardDataLength);
459 #ifdef MOZ_LOGGING
460 if (!clipboardData) {
461 LOGCLIP(" %s type is missing\n", flavorStr.get());
463 #endif
465 if (clipboardData) {
466 LOGCLIP(" got %s mime type data.\n", flavorStr.get());
468 // Special case text/html since we can convert into UCS2
469 if (flavorStr.EqualsLiteral(kHTMLMime)) {
470 char16_t* htmlBody = nullptr;
471 int32_t htmlBodyLen = 0;
472 // Convert text/html into our unicode format
473 nsAutoCString charset;
474 if (!GetHTMLCharset(clipboardData, clipboardDataLength, charset)) {
475 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
476 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
477 charset.AssignLiteral("utf-8");
479 if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
480 &htmlBody, htmlBodyLen)) {
481 LOGCLIP(" failed to convert text/html to UCS2.\n");
482 mContext->ReleaseClipboardData(clipboardData);
483 continue;
486 SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
487 htmlBodyLen * 2);
488 free(htmlBody);
489 } else {
490 SetTransferableData(aTransferable, flavorStr, clipboardData,
491 clipboardDataLength);
494 mContext->ReleaseClipboardData(clipboardData);
495 return NS_OK;
499 LOGCLIP(" failed to get clipboard content.\n");
500 return NS_OK;
503 NS_IMETHODIMP
504 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
505 LOGCLIP("nsClipboard::EmptyClipboard (%s)\n",
506 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
507 if (aWhichClipboard == kSelectionClipboard) {
508 if (mSelectionTransferable) {
509 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
510 MOZ_ASSERT(!mSelectionTransferable);
512 } else {
513 if (mGlobalTransferable) {
514 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
515 MOZ_ASSERT(!mGlobalTransferable);
519 return NS_OK;
522 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
523 if (aWhichClipboard == kSelectionClipboard) {
524 if (mSelectionOwner) {
525 mSelectionOwner->LosingOwnership(mSelectionTransferable);
526 mSelectionOwner = nullptr;
528 mSelectionTransferable = nullptr;
529 } else {
530 if (mGlobalOwner) {
531 mGlobalOwner->LosingOwnership(mGlobalTransferable);
532 mGlobalOwner = nullptr;
534 mGlobalTransferable = nullptr;
538 NS_IMETHODIMP
539 nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
540 int32_t aWhichClipboard, bool* _retval) {
541 if (!_retval) {
542 return NS_ERROR_NULL_POINTER;
545 LOGCLIP("nsClipboard::HasDataMatchingFlavors (%s)\n",
546 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
548 *_retval = false;
550 if (!mContext) {
551 return NS_ERROR_FAILURE;
554 int targetNums;
555 GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
556 if (!targets) {
557 LOGCLIP(" no targes at clipboard (null)\n");
558 return NS_OK;
561 #ifdef MOZ_LOGGING
562 LOGCLIP(" Clipboard content (target nums %d):\n", targetNums);
563 for (int32_t j = 0; j < targetNums; j++) {
564 gchar* atom_name = gdk_atom_name(targets[j]);
565 if (!atom_name) {
566 LOGCLIP(" failed to get MIME\n");
567 continue;
569 LOGCLIP(" MIME %s\n", atom_name);
571 LOGCLIP(" Asking for content:\n");
572 for (auto& flavor : aFlavorList) {
573 LOGCLIP(" MIME %s\n", flavor.get());
575 #endif
577 // Walk through the provided types and try to match it to a
578 // provided type.
579 for (auto& flavor : aFlavorList) {
580 // We special case text/unicode here.
581 if (flavor.EqualsLiteral(kUnicodeMime) &&
582 gtk_targets_include_text(targets, targetNums)) {
583 *_retval = true;
584 LOGCLIP(" has kUnicodeMime\n");
585 break;
588 for (int32_t j = 0; j < targetNums; j++) {
589 gchar* atom_name = gdk_atom_name(targets[j]);
590 if (!atom_name) continue;
592 if (flavor.Equals(atom_name)) {
593 *_retval = true;
594 LOGCLIP(" has %s\n", atom_name);
596 // X clipboard supports image/jpeg, but we want to emulate support
597 // for image/jpg as well
598 else if (flavor.EqualsLiteral(kJPGImageMime) &&
599 !strcmp(atom_name, kJPEGImageMime)) {
600 *_retval = true;
601 LOGCLIP(" has image/jpg\n");
603 // application/x-moz-file should be treated like text/uri-list
604 else if (flavor.EqualsLiteral(kFileMime) &&
605 !strcmp(atom_name, kURIListMime)) {
606 *_retval = true;
607 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
610 g_free(atom_name);
612 if (*_retval) break;
616 #ifdef MOZ_LOGGING
617 if (!(*_retval)) {
618 LOGCLIP(" no targes at clipboard (bad match)\n");
620 #endif
622 g_free(targets);
623 return NS_OK;
626 NS_IMETHODIMP
627 nsClipboard::SupportsSelectionClipboard(bool* _retval) {
628 *_retval = mContext ? mContext->HasSelectionSupport() : false;
629 return NS_OK;
632 NS_IMETHODIMP
633 nsClipboard::SupportsFindClipboard(bool* _retval) {
634 *_retval = false;
635 return NS_OK;
638 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
639 nsITransferable* retval;
641 if (aWhichClipboard == kSelectionClipboard)
642 retval = mSelectionTransferable.get();
643 else
644 retval = mGlobalTransferable.get();
646 return retval;
649 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
650 GtkSelectionData* aSelectionData) {
651 // Someone has asked us to hand them something. The first thing
652 // that we want to do is see if that something includes text. If
653 // it does, try to give it text/unicode after converting it to
654 // utf-8.
656 int32_t whichClipboard;
658 // which clipboard?
659 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
660 if (selection == GDK_SELECTION_PRIMARY)
661 whichClipboard = kSelectionClipboard;
662 else if (selection == GDK_SELECTION_CLIPBOARD)
663 whichClipboard = kGlobalClipboard;
664 else
665 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
667 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
668 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
670 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
671 if (!trans) {
672 // We have nothing to serve
673 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
674 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
675 return;
678 nsresult rv;
679 nsCOMPtr<nsISupports> item;
681 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
682 LOGCLIP(" selection target %s\n", gdk_atom_name(selectionTarget));
684 // Check to see if the selection data is some text type.
685 if (gtk_targets_include_text(&selectionTarget, 1)) {
686 LOGCLIP(" providing text/unicode data\n");
687 // Try to convert our internal type into a text string. Get
688 // the transferable for this clipboard and try to get the
689 // text/unicode type for it.
690 rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
691 if (NS_FAILED(rv) || !item) {
692 LOGCLIP(" GetTransferData() failed to get text/unicode!\n");
693 return;
696 nsCOMPtr<nsISupportsString> wideString;
697 wideString = do_QueryInterface(item);
698 if (!wideString) return;
700 nsAutoString ucs2string;
701 wideString->GetData(ucs2string);
702 NS_ConvertUTF16toUTF8 utf8string(ucs2string);
704 LOGCLIP(" sent %d bytes of utf-8 data\n", utf8string.Length());
705 if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
706 LOGCLIP(
707 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
708 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
709 // in some versions of GTK.
710 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
711 reinterpret_cast<const guchar*>(utf8string.get()),
712 utf8string.Length());
713 } else {
714 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
715 utf8string.Length());
717 return;
720 // Check to see if the selection data is an image type
721 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
722 LOGCLIP(" providing image data\n");
723 // Look through our transfer data for the image
724 static const char* const imageMimeTypes[] = {kNativeImageMime,
725 kPNGImageMime, kJPEGImageMime,
726 kJPGImageMime, kGIFImageMime};
727 nsCOMPtr<nsISupports> imageItem;
728 nsCOMPtr<imgIContainer> image;
729 for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
730 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
731 if (NS_FAILED(rv)) {
732 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes[i]);
733 continue;
736 image = do_QueryInterface(imageItem);
737 if (image) {
738 LOGCLIP(" %s is available at GetTransferData()\n",
739 imageMimeTypes[i]);
740 break;
744 if (!image) { // Not getting an image for an image mime type!?
745 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
746 return;
749 GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
750 if (!pixbuf) {
751 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
752 return;
755 LOGCLIP(" Setting pixbuf image data as %s\n",
756 gdk_atom_name(selectionTarget));
757 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
758 g_object_unref(pixbuf);
759 return;
762 if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
763 LOGCLIP(" providing %s data\n", kHTMLMime);
764 rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
765 if (NS_FAILED(rv) || !item) {
766 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime);
767 return;
770 nsCOMPtr<nsISupportsString> wideString;
771 wideString = do_QueryInterface(item);
772 if (!wideString) {
773 LOGCLIP(" failed to get wideString interface!");
774 return;
777 nsAutoString ucs2string;
778 wideString->GetData(ucs2string);
780 nsAutoCString html;
781 // Add the prefix so the encoding is correctly detected.
782 html.AppendLiteral(kHTMLMarkupPrefix);
783 AppendUTF16toUTF8(ucs2string, html);
785 LOGCLIP(" Setting %d bytest of %s data\n", html.Length(),
786 gdk_atom_name(selectionTarget));
787 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
788 (const guchar*)html.get(), html.Length());
789 return;
792 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
793 gdk_atom_name(selectionTarget));
795 // Try to match up the selection data target to something our
796 // transferable provides.
797 gchar* target_name = gdk_atom_name(selectionTarget);
798 if (!target_name) {
799 LOGCLIP(" Failed to get target name!\n");
800 return;
803 rv = trans->GetTransferData(target_name, getter_AddRefs(item));
804 // nothing found?
805 if (NS_FAILED(rv) || !item) {
806 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
807 g_free(target_name);
808 return;
811 void* primitive_data = nullptr;
812 uint32_t dataLen = 0;
813 nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name),
814 item, &primitive_data, &dataLen);
816 if (primitive_data) {
817 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n", target_name,
818 dataLen);
819 gtk_selection_data_set(aSelectionData, selectionTarget,
820 8, /* 8 bits in a unit */
821 (const guchar*)primitive_data, dataLen);
822 free(primitive_data);
823 } else {
824 LOGCLIP(" Failed to get primitive data!\n");
827 g_free(target_name);
830 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
831 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
832 if (whichClipboard < 0) {
833 return;
836 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
837 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
839 ClearTransferable(whichClipboard);
842 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
843 GtkSelectionData* aSelectionData, guint info,
844 gpointer user_data) {
845 LOGCLIP("clipboard_get_cb() callback\n");
846 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
847 aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
850 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
851 LOGCLIP("clipboard_clear_cb() callback\n");
852 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
853 aClipboard->SelectionClearEvent(aGtkClipboard);
857 * when copy-paste, mozilla wants data encoded using UCS2,
858 * other app such as StarOffice use "text/html"(RFC2854).
859 * This function convert data(got from GTK clipboard)
860 * to data mozilla wanted.
862 * data from GTK clipboard can be 3 forms:
863 * 1. From current mozilla
864 * "text/html", charset = utf-16
865 * 2. From old version mozilla or mozilla-based app
866 * content("body" only), charset = utf-16
867 * 3. From other app who use "text/html" when copy-paste
868 * "text/html", has "charset" info
870 * data : got from GTK clipboard
871 * dataLength: got from GTK clipboard
872 * body : pass to Mozilla
873 * bodyLength: pass to Mozilla
875 bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
876 char16_t** unicodeData, int32_t& outUnicodeLen) {
877 if (charset.EqualsLiteral("UTF-16")) { // current mozilla
878 outUnicodeLen = (dataLength / 2) - 1;
879 *unicodeData = reinterpret_cast<char16_t*>(
880 moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
881 memcpy(*unicodeData, data + sizeof(char16_t),
882 outUnicodeLen * sizeof(char16_t));
883 (*unicodeData)[outUnicodeLen] = '\0';
884 return true;
886 if (charset.EqualsLiteral("UNKNOWN")) {
887 outUnicodeLen = 0;
888 return false;
890 // app which use "text/html" to copy&paste
891 // get the decoder
892 auto encoding = Encoding::ForLabelNoReplacement(charset);
893 if (!encoding) {
894 LOGCLIP("ConvertHTMLtoUCS2: get unicode decoder error\n");
895 outUnicodeLen = 0;
896 return false;
899 auto dataSpan = Span(data, dataLength);
900 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
901 // issues, but might confuse other users.
902 const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
903 if (dataSpan.Length() >= prefixLen &&
904 Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
905 dataSpan = dataSpan.From(prefixLen);
908 auto decoder = encoding->NewDecoder();
909 CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataSpan.Length());
910 if (!needed.isValid() || needed.value() > INT32_MAX) {
911 outUnicodeLen = 0;
912 return false;
915 outUnicodeLen = 0;
916 if (needed.value()) {
917 *unicodeData = reinterpret_cast<char16_t*>(
918 moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
919 uint32_t result;
920 size_t read;
921 size_t written;
922 bool hadErrors;
923 Tie(result, read, written, hadErrors) = decoder->DecodeToUTF16(
924 AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
925 MOZ_ASSERT(result == kInputEmpty);
926 MOZ_ASSERT(read == size_t(dataSpan.Length()));
927 MOZ_ASSERT(written <= needed.value());
928 Unused << hadErrors;
929 outUnicodeLen = written;
930 // null terminate.
931 (*unicodeData)[outUnicodeLen] = '\0';
932 return true;
933 } // if valid length
934 return false;
938 * get "charset" information from clipboard data
939 * return value can be:
940 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
941 * 2. "UNKNOWN": mozilla can't detect what encode it use
942 * 3. other: "text/html" with other charset than utf-16
944 bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
945 // if detect "FFFE" or "FEFF", assume UTF-16
946 char16_t* beginChar = (char16_t*)data;
947 if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
948 str.AssignLiteral("UTF-16");
949 LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
950 return true;
952 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
953 const nsDependentCSubstring htmlStr(data, dataLength);
954 nsACString::const_iterator start, end;
955 htmlStr.BeginReading(start);
956 htmlStr.EndReading(end);
957 nsACString::const_iterator valueStart(start), valueEnd(start);
959 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
960 start = end;
961 htmlStr.EndReading(end);
963 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
964 valueStart = end;
965 start = end;
966 htmlStr.EndReading(end);
968 if (FindCharInReadable('"', start, end)) valueEnd = start;
971 // find "charset" in HTML
972 if (valueStart != valueEnd) {
973 str = Substring(valueStart, valueEnd);
974 ToUpperCase(str);
975 LOGCLIP("GetHTMLCharset: Charset of HTML = %s\n", str.get());
976 return true;
978 str.AssignLiteral("UNKNOWN");
979 LOGCLIP("GetHTMLCharset: Failed to get HTML Charset!\n");
980 return false;