Bug 1751217 Part 3: Make HDR-capable macOS screens report 30 pixelDepth. r=mstange
[gecko.git] / widget / gtk / nsClipboard.cpp
blobba9a06a3fdd1c0a90c3458e66da4378bb8a813c8
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:expandtab:shiftwidth=2:tabstop=2:
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 #endif
16 #include "nsGtkUtils.h"
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 ClipboardTargets nsRetrievalContext::sClipboardTargets;
63 ClipboardTargets nsRetrievalContext::sPrimaryTargets;
65 // Callback when someone asks us for the data
66 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
67 GtkSelectionData* aSelectionData, guint info,
68 gpointer user_data);
70 // Callback when someone asks us to clear a clipboard
71 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
73 static bool ConvertHTMLtoUCS2(Span<const char> aData, nsCString& charset,
74 char16_t** unicodeData, int32_t& outUnicodeLen);
76 static bool GetHTMLCharset(Span<const char> aData, nsCString& str);
78 ClipboardTargets ClipboardTargets::Clone() {
79 ClipboardTargets ret;
80 ret.mCount = mCount;
81 if (mCount) {
82 ret.mTargets.reset(
83 reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
84 memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
86 return ret;
89 void ClipboardTargets::Set(ClipboardTargets aTargets) {
90 mCount = aTargets.mCount;
91 mTargets = std::move(aTargets.mTargets);
94 void ClipboardData::SetData(Span<const uint8_t> aData) {
95 mData = nullptr;
96 mLength = aData.Length();
97 if (mLength) {
98 mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
99 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
103 void ClipboardData::SetText(Span<const char> aData) {
104 mData = nullptr;
105 mLength = aData.Length();
106 if (mLength) {
107 mData.reset(
108 reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
109 memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
110 mData.get()[mLength] = '\0';
114 void ClipboardData::SetTargets(ClipboardTargets aTargets) {
115 mLength = aTargets.mCount;
116 mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
119 ClipboardTargets ClipboardData::ExtractTargets() {
120 GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
121 uint32_t length = std::exchange(mLength, 0);
122 return ClipboardTargets{std::move(targets), length};
125 GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
126 if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
127 return GDK_SELECTION_CLIPBOARD;
129 return GDK_SELECTION_PRIMARY;
132 int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
133 if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
134 return nsClipboard::kSelectionClipboard;
135 else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
136 return nsClipboard::kGlobalClipboard;
138 return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
141 void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
142 GdkEvent* aEvent,
143 gpointer data) {
144 LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
145 sClipboardTargets.Clear();
148 void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
149 GdkEvent* aEvent,
150 gpointer data) {
151 LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
152 sPrimaryTargets.Clear();
155 ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) {
156 LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
157 aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
158 : "clipboard");
159 ClipboardTargets& storedTargets =
160 (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
161 : sClipboardTargets;
162 if (!storedTargets) {
163 LOGCLIP(" getting targets from system");
164 storedTargets.Set(GetTargetsImpl(aWhichClipboard));
165 } else {
166 LOGCLIP(" using cached targets");
168 return storedTargets.Clone();
171 nsRetrievalContext::nsRetrievalContext() {
172 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change",
173 G_CALLBACK(ClearCachedTargetsClipboard), this);
174 g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
175 G_CALLBACK(ClearCachedTargetsPrimary), this);
178 nsRetrievalContext::~nsRetrievalContext() {
179 g_signal_handlers_disconnect_by_func(
180 gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
181 FuncToGpointer(ClearCachedTargetsClipboard), this);
182 g_signal_handlers_disconnect_by_func(
183 gtk_clipboard_get(GDK_SELECTION_PRIMARY),
184 FuncToGpointer(ClearCachedTargetsPrimary), this);
185 sClipboardTargets.Clear();
186 sPrimaryTargets.Clear();
189 nsClipboard::nsClipboard() = default;
191 nsClipboard::~nsClipboard() {
192 // We have to clear clipboard before gdk_display_close() call.
193 // See bug 531580 for details.
194 if (mGlobalTransferable) {
195 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
197 if (mSelectionTransferable) {
198 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
202 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
204 nsresult nsClipboard::Init(void) {
205 if (widget::GdkIsX11Display()) {
206 mContext = new nsRetrievalContextX11();
207 #if defined(MOZ_WAYLAND)
208 } else if (widget::GdkIsWaylandDisplay()) {
209 mContext = new nsRetrievalContextWayland();
210 #endif
211 } else {
212 NS_WARNING("Missing nsRetrievalContext for nsClipboard!");
213 return NS_OK;
216 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
217 if (os) {
218 os->AddObserver(this, "xpcom-shutdown", false);
221 return NS_OK;
224 NS_IMETHODIMP
225 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
226 const char16_t* aData) {
227 // Save global clipboard content to CLIPBOARD_MANAGER.
228 // gtk_clipboard_store() can run an event loop, so call from a dedicated
229 // runnable.
230 return SchedulerGroup::Dispatch(
231 TaskCategory::Other,
232 NS_NewRunnableFunction("gtk_clipboard_store()", []() {
233 LOGCLIP("nsClipboard storing clipboard content\n");
234 gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
235 }));
238 NS_IMETHODIMP
239 nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
240 int32_t aWhichClipboard) {
241 // See if we can short cut
242 if ((aWhichClipboard == kGlobalClipboard &&
243 aTransferable == mGlobalTransferable.get() &&
244 aOwner == mGlobalOwner.get()) ||
245 (aWhichClipboard == kSelectionClipboard &&
246 aTransferable == mSelectionTransferable.get() &&
247 aOwner == mSelectionOwner.get())) {
248 return NS_OK;
251 LOGCLIP("nsClipboard::SetData (%s)\n",
252 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
254 // List of suported targets
255 GtkTargetList* list = gtk_target_list_new(nullptr, 0);
257 // Get the types of supported flavors
258 nsTArray<nsCString> flavors;
259 nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
260 if (NS_FAILED(rv)) {
261 LOGCLIP(" FlavorsTransferableCanExport failed!\n");
262 // Fall through. |gtkTargets| will be null below.
265 // Add all the flavors to this widget's supported type.
266 bool imagesAdded = false;
267 for (uint32_t i = 0; i < flavors.Length(); i++) {
268 nsCString& flavorStr = flavors[i];
269 LOGCLIP(" processing target %s\n", flavorStr.get());
271 // Special case text/unicode since we can handle all of the string types.
272 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
273 LOGCLIP(" adding TEXT targets\n");
274 gtk_target_list_add_text_targets(list, 0);
275 continue;
278 if (nsContentUtils::IsFlavorImage(flavorStr)) {
279 // Don't bother adding image targets twice
280 if (!imagesAdded) {
281 // accept any writable image type
282 LOGCLIP(" adding IMAGE targets\n");
283 gtk_target_list_add_image_targets(list, 0, TRUE);
284 imagesAdded = true;
286 continue;
289 // Add this to our list of valid targets
290 LOGCLIP(" adding OTHER target %s\n", flavorStr.get());
291 GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
292 gtk_target_list_add(list, atom, 0, 0);
295 // Get GTK clipboard (CLIPBOARD or PRIMARY)
296 GtkClipboard* gtkClipboard =
297 gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
299 gint numTargets = 0;
300 GtkTargetEntry* gtkTargets =
301 gtk_target_table_new_from_list(list, &numTargets);
302 if (!gtkTargets || numTargets == 0) {
303 LOGCLIP(
304 " gtk_target_table_new_from_list() failed or empty list of "
305 "targets!\n");
306 // Clear references to the any old data and let GTK know that it is no
307 // longer available.
308 EmptyClipboard(aWhichClipboard);
309 return NS_ERROR_FAILURE;
312 ClearCachedTargets(aWhichClipboard);
314 // Set getcallback and request to store data after an application exit
315 if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
316 clipboard_get_cb, clipboard_clear_cb, this)) {
317 // We managed to set-up the clipboard so update internal state
318 // We have to set it now because gtk_clipboard_set_with_data() calls
319 // clipboard_clear_cb() which reset our internal state
320 if (aWhichClipboard == kSelectionClipboard) {
321 mSelectionOwner = aOwner;
322 mSelectionTransferable = aTransferable;
323 } else {
324 mGlobalOwner = aOwner;
325 mGlobalTransferable = aTransferable;
326 gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
329 rv = NS_OK;
330 } else {
331 LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
332 EmptyClipboard(aWhichClipboard);
333 rv = NS_ERROR_FAILURE;
336 gtk_target_table_free(gtkTargets, numTargets);
337 gtk_target_list_unref(list);
339 return rv;
342 void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
343 nsCString& aFlavor,
344 const char* aClipboardData,
345 uint32_t aClipboardDataLength) {
346 LOGCLIP("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get());
348 nsCOMPtr<nsISupports> wrapper;
349 nsPrimitiveHelpers::CreatePrimitiveForData(
350 aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
351 aTransferable->SetTransferData(aFlavor.get(), wrapper);
354 static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
355 const char* aMime) {
356 for (const auto& flavorStr : aFlavourList) {
357 if (flavorStr.Equals(aMime)) {
358 return true;
361 return false;
364 // When clipboard contains only images, X11/Gtk tries to convert them
365 // to text when we request text instead of just fail to provide the data.
366 // So if clipboard contains images only remove text MIME offer.
367 bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
368 nsTArray<nsCString>& aFlavors) {
369 LOGCLIP("nsClipboard::FilterImportedFlavors");
371 auto targets = mContext->GetTargets(aWhichClipboard);
372 if (!targets) {
373 LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
374 return true;
377 for (const auto& atom : targets.AsSpan()) {
378 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
379 if (!atom_name) {
380 continue;
382 // Filter out system MIME types.
383 if (strcmp(atom_name.get(), "TARGETS") == 0 ||
384 strcmp(atom_name.get(), "TIMESTAMP") == 0 ||
385 strcmp(atom_name.get(), "SAVE_TARGETS") == 0 ||
386 strcmp(atom_name.get(), "MULTIPLE") == 0) {
387 continue;
389 // Filter out types which can't be converted to text.
390 if (strncmp(atom_name.get(), "image/", 6) == 0 ||
391 strncmp(atom_name.get(), "application/", 12) == 0 ||
392 strncmp(atom_name.get(), "audio/", 6) == 0 ||
393 strncmp(atom_name.get(), "video/", 6) == 0) {
394 continue;
396 // We have some other MIME type on clipboard which can be hopefully
397 // converted to text without any problem.
398 LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
399 return true;
402 // So make sure we offer only types we have at clipboard.
403 nsTArray<nsCString> clipboardFlavors;
404 for (const auto& atom : targets.AsSpan()) {
405 GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
406 if (!atom_name) {
407 continue;
409 if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
410 clipboardFlavors.AppendElement(nsCString(atom_name.get()));
413 aFlavors.SwapElements(clipboardFlavors);
414 #ifdef MOZ_LOGGING
415 LOGCLIP(" X11: Flavors which match clipboard content:\n");
416 for (uint32_t i = 0; i < aFlavors.Length(); i++) {
417 LOGCLIP(" %s\n", aFlavors[i].get());
419 #endif
420 return true;
423 NS_IMETHODIMP
424 nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
425 LOGCLIP("nsClipboard::GetData (%s)\n",
426 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
428 // TODO: Ensure we don't re-enter here.
429 if (!aTransferable || !mContext) {
430 return NS_ERROR_FAILURE;
433 // Get a list of flavors this transferable can import
434 nsTArray<nsCString> flavors;
435 nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
436 if (NS_FAILED(rv)) {
437 LOGCLIP(" FlavorsTransferableCanImport falied!\n");
438 return rv;
440 #ifdef MOZ_LOGGING
441 LOGCLIP(" Flavors which can be imported:");
442 for (uint32_t i = 0; i < flavors.Length(); i++) {
443 LOGCLIP(" %s\n", flavors[i].get());
445 #endif
447 // Filter out MIME types on X11 to prevent unwanted conversions,
448 // see Bug 1611407
449 if (widget::GdkIsX11Display() &&
450 !FilterImportedFlavors(aWhichClipboard, flavors)) {
451 LOGCLIP(" Missing suitable clipboard data, quit.");
452 return NS_OK;
455 for (uint32_t i = 0; i < flavors.Length(); i++) {
456 nsCString& flavorStr = flavors[i];
458 if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
459 flavorStr.EqualsLiteral(kJPGImageMime) ||
460 flavorStr.EqualsLiteral(kPNGImageMime) ||
461 flavorStr.EqualsLiteral(kGIFImageMime)) {
462 // Emulate support for image/jpg
463 if (flavorStr.EqualsLiteral(kJPGImageMime)) {
464 flavorStr.Assign(kJPEGImageMime);
467 LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
469 auto clipboardData =
470 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
471 if (!clipboardData) {
472 LOGCLIP(" %s type is missing\n", flavorStr.get());
473 continue;
476 nsCOMPtr<nsIInputStream> byteStream;
477 NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
478 NS_ASSIGNMENT_COPY);
479 aTransferable->SetTransferData(flavorStr.get(), byteStream);
480 LOGCLIP(" got %s MIME data\n", flavorStr.get());
481 return NS_OK;
484 // Special case text/unicode since we can convert any
485 // string into text/unicode
486 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
487 LOGCLIP(" Getting unicode %s MIME clipboard data\n", flavorStr.get());
489 auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
490 if (!clipboardData) {
491 LOGCLIP(" failed to get unicode data\n");
492 // If the type was text/unicode and we couldn't get
493 // text off the clipboard, run the next loop
494 // iteration.
495 continue;
498 // Convert utf-8 into our unicode format.
499 NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
500 SetTransferableData(aTransferable, flavorStr,
501 (const char*)ucs2string.BeginReading(),
502 ucs2string.Length() * 2);
504 LOGCLIP(" got unicode data, length %zd\n", ucs2string.Length());
505 return NS_OK;
508 if (flavorStr.EqualsLiteral(kFileMime)) {
509 LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
511 auto clipboardData =
512 mContext->GetClipboardData(kURIListMime, aWhichClipboard);
513 if (!clipboardData) {
514 LOGCLIP(" text/uri-list type is missing\n");
515 continue;
518 nsDependentCSubstring data(clipboardData.AsSpan());
519 nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(data);
520 if (!uris.IsEmpty()) {
521 nsCOMPtr<nsIURI> fileURI;
522 NS_NewURI(getter_AddRefs(fileURI), uris[0]);
523 if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
524 nsCOMPtr<nsIFile> file;
525 rv = fileURL->GetFile(getter_AddRefs(file));
526 if (NS_SUCCEEDED(rv)) {
527 aTransferable->SetTransferData(flavorStr.get(), file);
528 LOGCLIP(" successfully set file to clipboard\n");
532 return NS_OK;
535 LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
537 auto clipboardData =
538 mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
540 #ifdef MOZ_LOGGING
541 if (!clipboardData) {
542 LOGCLIP(" %s type is missing\n", flavorStr.get());
544 #endif
546 if (clipboardData) {
547 LOGCLIP(" got %s mime type data.\n", flavorStr.get());
549 // Special case text/html since we can convert into UCS2
550 if (flavorStr.EqualsLiteral(kHTMLMime)) {
551 char16_t* htmlBody = nullptr;
552 int32_t htmlBodyLen = 0;
553 // Convert text/html into our unicode format
554 nsAutoCString charset;
555 if (!GetHTMLCharset(clipboardData.AsSpan(), charset)) {
556 // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
557 LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
558 charset.AssignLiteral("utf-8");
560 if (!ConvertHTMLtoUCS2(clipboardData.AsSpan(), charset, &htmlBody,
561 htmlBodyLen)) {
562 LOGCLIP(" failed to convert text/html to UCS2.\n");
563 continue;
566 SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
567 htmlBodyLen * 2);
568 free(htmlBody);
569 } else {
570 auto span = clipboardData.AsSpan();
571 SetTransferableData(aTransferable, flavorStr, span.data(),
572 span.Length());
574 return NS_OK;
578 LOGCLIP(" failed to get clipboard content.\n");
579 return NS_OK;
582 NS_IMETHODIMP
583 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
584 LOGCLIP("nsClipboard::EmptyClipboard (%s)\n",
585 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
586 if (aWhichClipboard == kSelectionClipboard) {
587 if (mSelectionTransferable) {
588 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
589 MOZ_ASSERT(!mSelectionTransferable);
591 } else {
592 if (mGlobalTransferable) {
593 gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
594 MOZ_ASSERT(!mGlobalTransferable);
597 ClearCachedTargets(aWhichClipboard);
598 return NS_OK;
601 void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
602 if (aWhichClipboard == kSelectionClipboard) {
603 if (mSelectionOwner) {
604 mSelectionOwner->LosingOwnership(mSelectionTransferable);
605 mSelectionOwner = nullptr;
607 mSelectionTransferable = nullptr;
608 } else {
609 if (mGlobalOwner) {
610 mGlobalOwner->LosingOwnership(mGlobalTransferable);
611 mGlobalOwner = nullptr;
613 mGlobalTransferable = nullptr;
617 NS_IMETHODIMP
618 nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
619 int32_t aWhichClipboard, bool* _retval) {
620 if (!_retval) {
621 return NS_ERROR_NULL_POINTER;
624 LOGCLIP("nsClipboard::HasDataMatchingFlavors (%s)\n",
625 aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
627 *_retval = false;
629 if (!mContext) {
630 return NS_ERROR_FAILURE;
633 auto targets = mContext->GetTargets(aWhichClipboard);
634 if (!targets) {
635 LOGCLIP(" no targes at clipboard (null)\n");
636 return NS_OK;
639 #ifdef MOZ_LOGGING
640 if (LOGCLIP_ENABLED()) {
641 LOGCLIP(" Clipboard content (target nums %zu):\n",
642 targets.AsSpan().Length());
643 for (const auto& target : targets.AsSpan()) {
644 GUniquePtr<gchar> atom_name(gdk_atom_name(target));
645 if (!atom_name) {
646 LOGCLIP(" failed to get MIME\n");
647 continue;
649 LOGCLIP(" MIME %s\n", atom_name.get());
651 LOGCLIP(" Asking for content:\n");
652 for (auto& flavor : aFlavorList) {
653 LOGCLIP(" MIME %s\n", flavor.get());
656 #endif
658 // Walk through the provided types and try to match it to a
659 // provided type.
660 for (auto& flavor : aFlavorList) {
661 // We special case text/unicode here.
662 if (flavor.EqualsLiteral(kUnicodeMime) &&
663 gtk_targets_include_text(targets.AsSpan().data(),
664 targets.AsSpan().Length())) {
665 *_retval = true;
666 LOGCLIP(" has kUnicodeMime\n");
667 break;
670 for (const auto& target : targets.AsSpan()) {
671 GUniquePtr<gchar> atom_name(gdk_atom_name(target));
672 if (!atom_name) {
673 continue;
676 if (flavor.Equals(atom_name.get())) {
677 LOGCLIP(" has %s\n", atom_name.get());
678 *_retval = true;
679 break;
681 // X clipboard supports image/jpeg, but we want to emulate support
682 // for image/jpg as well
683 if (flavor.EqualsLiteral(kJPGImageMime) &&
684 !strcmp(atom_name.get(), kJPEGImageMime)) {
685 LOGCLIP(" has image/jpg\n");
686 *_retval = true;
687 break;
689 // application/x-moz-file should be treated like text/uri-list
690 if (flavor.EqualsLiteral(kFileMime) &&
691 !strcmp(atom_name.get(), kURIListMime)) {
692 LOGCLIP(" has text/uri-list treating as application/x-moz-file");
693 *_retval = true;
694 break;
698 if (*_retval) {
699 break;
703 #ifdef MOZ_LOGGING
704 if (!(*_retval)) {
705 LOGCLIP(" no targes at clipboard (bad match)\n");
707 #endif
709 return NS_OK;
712 NS_IMETHODIMP
713 nsClipboard::SupportsSelectionClipboard(bool* _retval) {
714 *_retval = true;
715 return NS_OK;
718 NS_IMETHODIMP
719 nsClipboard::SupportsFindClipboard(bool* _retval) {
720 *_retval = false;
721 return NS_OK;
724 nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
725 nsITransferable* retval;
727 if (aWhichClipboard == kSelectionClipboard)
728 retval = mSelectionTransferable.get();
729 else
730 retval = mGlobalTransferable.get();
732 return retval;
735 void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
736 GtkSelectionData* aSelectionData) {
737 // Someone has asked us to hand them something. The first thing
738 // that we want to do is see if that something includes text. If
739 // it does, try to give it text/unicode after converting it to
740 // utf-8.
742 int32_t whichClipboard;
744 // which clipboard?
745 GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
746 if (selection == GDK_SELECTION_PRIMARY)
747 whichClipboard = kSelectionClipboard;
748 else if (selection == GDK_SELECTION_CLIPBOARD)
749 whichClipboard = kGlobalClipboard;
750 else
751 return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
753 LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
754 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
756 nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
757 if (!trans) {
758 // We have nothing to serve
759 LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
760 whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
761 return;
764 nsresult rv;
765 nsCOMPtr<nsISupports> item;
767 GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
768 LOGCLIP(" selection target %s\n",
769 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
771 // Check to see if the selection data is some text type.
772 if (gtk_targets_include_text(&selectionTarget, 1)) {
773 LOGCLIP(" providing text/unicode data\n");
774 // Try to convert our internal type into a text string. Get
775 // the transferable for this clipboard and try to get the
776 // text/unicode type for it.
777 rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
778 if (NS_FAILED(rv) || !item) {
779 LOGCLIP(" GetTransferData() failed to get text/unicode!\n");
780 return;
783 nsCOMPtr<nsISupportsString> wideString;
784 wideString = do_QueryInterface(item);
785 if (!wideString) return;
787 nsAutoString ucs2string;
788 wideString->GetData(ucs2string);
789 NS_ConvertUTF16toUTF8 utf8string(ucs2string);
791 LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string.Length());
792 if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
793 LOGCLIP(
794 " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
795 // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
796 // in some versions of GTK.
797 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
798 reinterpret_cast<const guchar*>(utf8string.get()),
799 utf8string.Length());
800 } else {
801 gtk_selection_data_set_text(aSelectionData, utf8string.get(),
802 utf8string.Length());
804 return;
807 // Check to see if the selection data is an image type
808 if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
809 LOGCLIP(" providing image data\n");
810 // Look through our transfer data for the image
811 static const char* const imageMimeTypes[] = {kNativeImageMime,
812 kPNGImageMime, kJPEGImageMime,
813 kJPGImageMime, kGIFImageMime};
814 nsCOMPtr<nsISupports> imageItem;
815 nsCOMPtr<imgIContainer> image;
816 for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
817 rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
818 if (NS_FAILED(rv)) {
819 LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes[i]);
820 continue;
823 image = do_QueryInterface(imageItem);
824 if (image) {
825 LOGCLIP(" %s is available at GetTransferData()\n",
826 imageMimeTypes[i]);
827 break;
831 if (!image) { // Not getting an image for an image mime type!?
832 LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
833 return;
836 RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
837 if (!pixbuf) {
838 LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
839 return;
842 LOGCLIP(" Setting pixbuf image data as %s\n",
843 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
844 gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
845 return;
848 if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
849 LOGCLIP(" providing %s data\n", kHTMLMime);
850 rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
851 if (NS_FAILED(rv) || !item) {
852 LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime);
853 return;
856 nsCOMPtr<nsISupportsString> wideString;
857 wideString = do_QueryInterface(item);
858 if (!wideString) {
859 LOGCLIP(" failed to get wideString interface!");
860 return;
863 nsAutoString ucs2string;
864 wideString->GetData(ucs2string);
866 nsAutoCString html;
867 // Add the prefix so the encoding is correctly detected.
868 html.AppendLiteral(kHTMLMarkupPrefix);
869 AppendUTF16toUTF8(ucs2string, html);
871 LOGCLIP(" Setting %zd bytest of %s data\n", html.Length(),
872 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
873 gtk_selection_data_set(aSelectionData, selectionTarget, 8,
874 (const guchar*)html.get(), html.Length());
875 return;
878 LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
879 GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
881 // Try to match up the selection data target to something our
882 // transferable provides.
883 GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
884 if (!target_name) {
885 LOGCLIP(" Failed to get target name!\n");
886 return;
889 rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
890 // nothing found?
891 if (NS_FAILED(rv) || !item) {
892 LOGCLIP(" Failed to get anything from GetTransferData()!\n");
893 return;
896 void* primitive_data = nullptr;
897 uint32_t dataLen = 0;
898 nsPrimitiveHelpers::CreateDataFromPrimitive(
899 nsDependentCString(target_name.get()), item, &primitive_data, &dataLen);
900 if (!primitive_data) {
901 LOGCLIP(" Failed to get primitive data!\n");
902 return;
905 LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
906 target_name.get(), dataLen);
907 gtk_selection_data_set(aSelectionData, selectionTarget,
908 8, /* 8 bits in a unit */
909 (const guchar*)primitive_data, dataLen);
910 free(primitive_data);
913 void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) {
914 if (aWhichClipboard == kSelectionClipboard) {
915 nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
916 } else {
917 nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
921 void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
922 int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
923 if (whichClipboard < 0) {
924 return;
926 LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
927 whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
928 ClearCachedTargets(whichClipboard);
929 ClearTransferable(whichClipboard);
932 void clipboard_get_cb(GtkClipboard* aGtkClipboard,
933 GtkSelectionData* aSelectionData, guint info,
934 gpointer user_data) {
935 LOGCLIP("clipboard_get_cb() callback\n");
936 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
937 aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
940 void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
941 LOGCLIP("clipboard_clear_cb() callback\n");
942 nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
943 aClipboard->SelectionClearEvent(aGtkClipboard);
947 * when copy-paste, mozilla wants data encoded using UCS2,
948 * other app such as StarOffice use "text/html"(RFC2854).
949 * This function convert data(got from GTK clipboard)
950 * to data mozilla wanted.
952 * data from GTK clipboard can be 3 forms:
953 * 1. From current mozilla
954 * "text/html", charset = utf-16
955 * 2. From old version mozilla or mozilla-based app
956 * content("body" only), charset = utf-16
957 * 3. From other app who use "text/html" when copy-paste
958 * "text/html", has "charset" info
960 * data : got from GTK clipboard
961 * dataLength: got from GTK clipboard
962 * body : pass to Mozilla
963 * bodyLength: pass to Mozilla
965 bool ConvertHTMLtoUCS2(Span<const char> aData, nsCString& charset,
966 char16_t** unicodeData, int32_t& outUnicodeLen) {
967 if (charset.EqualsLiteral("UTF-16")) { // current mozilla
968 outUnicodeLen = (aData.Length() / 2) - 1;
969 *unicodeData = reinterpret_cast<char16_t*>(
970 moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
971 memcpy(*unicodeData, aData.data() + sizeof(char16_t),
972 outUnicodeLen * sizeof(char16_t));
973 (*unicodeData)[outUnicodeLen] = '\0';
974 return true;
976 if (charset.EqualsLiteral("UNKNOWN")) {
977 outUnicodeLen = 0;
978 return false;
980 // app which use "text/html" to copy&paste
981 // get the decoder
982 auto encoding = Encoding::ForLabelNoReplacement(charset);
983 if (!encoding) {
984 LOGCLIP("ConvertHTMLtoUCS2: get unicode decoder error\n");
985 outUnicodeLen = 0;
986 return false;
989 // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
990 // issues, but might confuse other users.
991 const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
992 if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
993 .EqualsLiteral(kHTMLMarkupPrefix)) {
994 aData = aData.From(prefixLen);
997 auto decoder = encoding->NewDecoder();
998 CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(aData.Length());
999 if (!needed.isValid() || needed.value() > INT32_MAX) {
1000 outUnicodeLen = 0;
1001 return false;
1004 outUnicodeLen = 0;
1005 if (needed.value()) {
1006 *unicodeData = reinterpret_cast<char16_t*>(
1007 moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
1008 uint32_t result;
1009 size_t read;
1010 size_t written;
1011 std::tie(result, read, written, std::ignore) = decoder->DecodeToUTF16(
1012 AsBytes(aData), Span(*unicodeData, needed.value()), true);
1013 MOZ_ASSERT(result == kInputEmpty);
1014 MOZ_ASSERT(read == size_t(aData.Length()));
1015 MOZ_ASSERT(written <= needed.value());
1016 outUnicodeLen = written;
1017 // null terminate.
1018 (*unicodeData)[outUnicodeLen] = '\0';
1019 return true;
1020 } // if valid length
1021 return false;
1025 * get "charset" information from clipboard data
1026 * return value can be:
1027 * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
1028 * 2. "UNKNOWN": mozilla can't detect what encode it use
1029 * 3. other: "text/html" with other charset than utf-16
1031 bool GetHTMLCharset(Span<const char> aData, nsCString& str) {
1032 // if detect "FFFE" or "FEFF", assume UTF-16
1034 char16_t* beginChar = (char16_t*)aData.data();
1035 if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
1036 str.AssignLiteral("UTF-16");
1037 LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
1038 return true;
1042 // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
1043 const nsDependentCSubstring htmlStr(aData);
1044 nsACString::const_iterator start, end;
1045 htmlStr.BeginReading(start);
1046 htmlStr.EndReading(end);
1047 nsACString::const_iterator valueStart(start), valueEnd(start);
1049 if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
1050 start = end;
1051 htmlStr.EndReading(end);
1053 if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
1054 valueStart = end;
1055 start = end;
1056 htmlStr.EndReading(end);
1058 if (FindCharInReadable('"', start, end)) valueEnd = start;
1061 // find "charset" in HTML
1062 if (valueStart != valueEnd) {
1063 str = Substring(valueStart, valueEnd);
1064 ToUpperCase(str);
1065 LOGCLIP("GetHTMLCharset: Charset of HTML = %s\n", str.get());
1066 return true;
1068 str.AssignLiteral("UNKNOWN");
1069 LOGCLIP("GetHTMLCharset: Failed to get HTML Charset!\n");
1070 return false;