/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:expandtab:shiftwidth=2:tabstop=2: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ArrayUtils.h" #include "nsArrayUtils.h" #include "nsClipboard.h" #if defined(MOZ_X11) # include "nsClipboardX11.h" #endif #if defined(MOZ_WAYLAND) # include "nsClipboardWayland.h" # include "nsWaylandDisplay.h" #endif #include "nsGtkUtils.h" #include "nsIURI.h" #include "nsIFile.h" #include "nsNetUtil.h" #include "nsContentUtils.h" #include "HeadlessClipboard.h" #include "nsSupportsPrimitives.h" #include "nsString.h" #include "nsReadableUtils.h" #include "nsPrimitiveHelpers.h" #include "nsImageToPixbuf.h" #include "nsStringStream.h" #include "nsIFileURL.h" #include "nsIObserverService.h" #include "mozilla/GRefPtr.h" #include "mozilla/RefPtr.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_widget.h" #include "mozilla/TimeStamp.h" #include "GRefPtr.h" #include "WidgetUtilsGtk.h" #include "imgIContainer.h" #include #if defined(MOZ_X11) # include #endif #include "mozilla/Encoding.h" using namespace mozilla; // Idle timeout for receiving selection and property notify events (microsec) // Right now it's set to 1 sec. const int kClipboardTimeout = 1000000; // Defines how many event loop iterations will be done without sleep. // We ususally get data in first 2-3 iterations unless some large object // (an image for instance) is transferred through clipboard. const int kClipboardFastIterationNum = 3; // We add this prefix to HTML markup, so that GetHTMLCharset can correctly // detect the HTML as UTF-8 encoded. static const char kHTMLMarkupPrefix[] = R"()"; static const char kURIListMime[] = "text/uri-list"; MOZ_CONSTINIT ClipboardTargets nsRetrievalContext::sClipboardTargets; MOZ_CONSTINIT ClipboardTargets nsRetrievalContext::sPrimaryTargets; // Callback when someone asks us for the data static void clipboard_get_cb(GtkClipboard* aGtkClipboard, GtkSelectionData* aSelectionData, guint info, gpointer user_data); // Callback when someone asks us to clear a clipboard static void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data); // Callback when owner of clipboard data is changed static void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard, GdkEventOwnerChange* aEvent, gpointer aUserData); static bool GetHTMLCharset(Span aData, nsCString& str); static void SetTransferableData(nsITransferable* aTransferable, const nsACString& aFlavor, const char* aClipboardData, uint32_t aClipboardDataLength) { MOZ_CLIPBOARD_LOG("SetTransferableData MIME %s\n", PromiseFlatCString(aFlavor).get()); nsCOMPtr wrapper; nsPrimitiveHelpers::CreatePrimitiveForData( aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper)); aTransferable->SetTransferData(PromiseFlatCString(aFlavor).get(), wrapper); } ClipboardTargets ClipboardTargets::Clone() { ClipboardTargets ret; ret.mCount = mCount; if (mCount) { ret.mTargets.reset( reinterpret_cast(g_malloc(sizeof(GdkAtom) * mCount))); memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount); } return ret; } void ClipboardTargets::Set(ClipboardTargets aTargets) { mCount = aTargets.mCount; mTargets = std::move(aTargets.mTargets); } void ClipboardData::SetData(Span aData) { mData = nullptr; mLength = aData.Length(); if (mLength) { mData.reset(reinterpret_cast(g_malloc(sizeof(char) * mLength))); memcpy(mData.get(), aData.data(), sizeof(char) * mLength); } } void ClipboardData::SetText(Span aData) { mData = nullptr; mLength = aData.Length(); if (mLength) { mData.reset( reinterpret_cast(g_malloc(sizeof(char) * (mLength + 1)))); memcpy(mData.get(), aData.data(), sizeof(char) * mLength); mData.get()[mLength] = '\0'; } } void ClipboardData::SetTargets(ClipboardTargets aTargets) { mLength = aTargets.mCount; mData.reset(reinterpret_cast(aTargets.mTargets.release())); } ClipboardTargets ClipboardData::ExtractTargets() { GUniquePtr targets(reinterpret_cast(mData.release())); uint32_t length = std::exchange(mLength, 0); return ClipboardTargets{std::move(targets), length}; } GdkAtom GetSelectionAtom(int32_t aWhichClipboard) { if (aWhichClipboard == nsIClipboard::kGlobalClipboard) return GDK_SELECTION_CLIPBOARD; return GDK_SELECTION_PRIMARY; } Maybe GetGeckoClipboardType( GtkClipboard* aGtkClipboard) { if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY)) { return Some(nsClipboard::kSelectionClipboard); } if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)) { return Some(nsClipboard::kGlobalClipboard); } return Nothing(); // THAT AIN'T NO CLIPBOARD I EVER HEARD OF } void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard, GdkEvent* aEvent, gpointer data) { MOZ_CLIPBOARD_LOG("nsRetrievalContext::ClearCachedTargetsClipboard()"); sClipboardTargets.Clear(); } void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard, GdkEvent* aEvent, gpointer data) { MOZ_CLIPBOARD_LOG("nsRetrievalContext::ClearCachedTargetsPrimary()"); sPrimaryTargets.Clear(); } ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) { MOZ_CLIPBOARD_LOG("nsRetrievalContext::GetTargets(%s)\n", aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary" : "clipboard"); ClipboardTargets& storedTargets = (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets : sClipboardTargets; if (!storedTargets) { MOZ_CLIPBOARD_LOG(" getting targets from system"); storedTargets.Set(GetTargetsImpl(aWhichClipboard)); } else { MOZ_CLIPBOARD_LOG(" using cached targets"); } return storedTargets.Clone(); } nsRetrievalContext::~nsRetrievalContext() { sClipboardTargets.Clear(); sPrimaryTargets.Clear(); } nsClipboard::nsClipboard() : nsBaseClipboard(mozilla::dom::ClipboardCapabilities( #ifdef MOZ_WAYLAND widget::GdkIsWaylandDisplay() ? widget::WaylandDisplayGet()->IsPrimarySelectionEnabled() : true, #else true, /* supportsSelectionClipboard */ #endif false /* supportsFindClipboard */, false /* supportsSelectionCache */)) { g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change", G_CALLBACK(clipboard_owner_change_cb), this); g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change", G_CALLBACK(clipboard_owner_change_cb), this); } nsClipboard::~nsClipboard() { g_signal_handlers_disconnect_by_func( gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), FuncToGpointer(clipboard_owner_change_cb), this); g_signal_handlers_disconnect_by_func( gtk_clipboard_get(GDK_SELECTION_PRIMARY), FuncToGpointer(clipboard_owner_change_cb), this); } NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver) nsresult nsClipboard::Init(void) { #if defined(MOZ_X11) if (widget::GdkIsX11Display()) { mContext = new nsRetrievalContextX11(); } #endif #if defined(MOZ_WAYLAND) if (widget::GdkIsWaylandDisplay()) { mContext = new nsRetrievalContextWayland(); } #endif nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->AddObserver(this, "xpcom-shutdown", false); } return NS_OK; } NS_IMETHODIMP nsClipboard::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { // Save global clipboard content to CLIPBOARD_MANAGER. // gtk_clipboard_store() can run an event loop, so call from a dedicated // runnable. return SchedulerGroup::Dispatch( NS_NewRunnableFunction("gtk_clipboard_store()", []() { MOZ_CLIPBOARD_LOG("nsClipboard storing clipboard content\n"); gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); })); } NS_IMETHODIMP nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable, ClipboardType aWhichClipboard) { MOZ_DIAGNOSTIC_ASSERT(aTransferable); MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); // See if we can short cut if ((aWhichClipboard == kGlobalClipboard && aTransferable == mGlobalTransferable.get()) || (aWhichClipboard == kSelectionClipboard && aTransferable == mSelectionTransferable.get())) { return NS_OK; } MOZ_CLIPBOARD_LOG( "nsClipboard::SetNativeClipboardData (%s)\n", aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"); // List of suported targets GtkTargetList* list = gtk_target_list_new(nullptr, 0); // Get the types of supported flavors nsTArray flavors; nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors); if (NS_FAILED(rv)) { MOZ_CLIPBOARD_LOG(" FlavorsTransferableCanExport failed!\n"); // Fall through. |gtkTargets| will be null below. } // Add all the flavors to this widget's supported type. bool imagesAdded = false; for (uint32_t i = 0; i < flavors.Length(); i++) { nsCString& flavorStr = flavors[i]; MOZ_CLIPBOARD_LOG(" processing target %s\n", flavorStr.get()); // Special case text/plain since we can handle all of the string types. if (flavorStr.EqualsLiteral(kTextMime)) { MOZ_CLIPBOARD_LOG(" adding TEXT targets\n"); gtk_target_list_add_text_targets(list, 0); continue; } if (nsContentUtils::IsFlavorImage(flavorStr)) { // Don't bother adding image targets twice if (!imagesAdded) { // accept any writable image type MOZ_CLIPBOARD_LOG(" adding IMAGE targets\n"); gtk_target_list_add_image_targets(list, 0, TRUE); imagesAdded = true; } continue; } if (flavorStr.EqualsLiteral(kFileMime)) { MOZ_CLIPBOARD_LOG(" adding text/uri-list target\n"); GdkAtom atom = gdk_atom_intern(kURIListMime, FALSE); gtk_target_list_add(list, atom, 0, 0); continue; } // Add this to our list of valid targets MOZ_CLIPBOARD_LOG(" adding OTHER target %s\n", flavorStr.get()); GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE); gtk_target_list_add(list, atom, 0, 0); } // Get GTK clipboard (CLIPBOARD or PRIMARY) GtkClipboard* gtkClipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)); gint numTargets = 0; GtkTargetEntry* gtkTargets = gtk_target_table_new_from_list(list, &numTargets); if (!gtkTargets || numTargets == 0) { MOZ_CLIPBOARD_LOG( " gtk_target_table_new_from_list() failed or empty list of " "targets!\n"); // Clear references to the any old data and let GTK know that it is no // longer available. EmptyNativeClipboardData(aWhichClipboard); return NS_ERROR_FAILURE; } ClearCachedTargets(aWhichClipboard); // Set getcallback and request to store data after an application exit if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets, clipboard_get_cb, clipboard_clear_cb, this)) { // We managed to set-up the clipboard so update internal state // We have to set it now because gtk_clipboard_set_with_data() calls // clipboard_clear_cb() which reset our internal state if (aWhichClipboard == kSelectionClipboard) { mSelectionSequenceNumber++; mSelectionTransferable = aTransferable; } else { mGlobalSequenceNumber++; mGlobalTransferable = aTransferable; gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets); } rv = NS_OK; } else { MOZ_CLIPBOARD_LOG(" gtk_clipboard_set_with_data() failed!\n"); EmptyNativeClipboardData(aWhichClipboard); rv = NS_ERROR_FAILURE; } gtk_target_table_free(gtkTargets, numTargets); gtk_target_list_unref(list); return rv; } mozilla::Result nsClipboard::GetNativeClipboardSequenceNumber(ClipboardType aWhichClipboard) { MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber : mGlobalSequenceNumber; } static bool IsMIMEAtFlavourList(const nsTArray& aFlavourList, const char* aMime) { for (const auto& flavorStr : aFlavourList) { if (flavorStr.Equals(aMime)) { return true; } } return false; } // When clipboard contains only images, X11/Gtk tries to convert them // to text when we request text instead of just fail to provide the data. // So if clipboard contains images only remove text MIME offer. bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard, nsTArray& aFlavors) { MOZ_CLIPBOARD_LOG("nsClipboard::FilterImportedFlavors"); auto targets = mContext->GetTargets(aWhichClipboard); if (!targets) { MOZ_CLIPBOARD_LOG(" X11: no targes at clipboard (null), quit.\n"); return true; } for (const auto& atom : targets.AsSpan()) { GUniquePtr atom_name(gdk_atom_name(atom)); if (!atom_name) { continue; } // Filter out system MIME types. if (strcmp(atom_name.get(), "TARGETS") == 0 || strcmp(atom_name.get(), "TIMESTAMP") == 0 || strcmp(atom_name.get(), "SAVE_TARGETS") == 0 || strcmp(atom_name.get(), "MULTIPLE") == 0) { continue; } // Filter out types which can't be converted to text. if (strncmp(atom_name.get(), "image/", 6) == 0 || strncmp(atom_name.get(), "application/", 12) == 0 || strncmp(atom_name.get(), "audio/", 6) == 0 || strncmp(atom_name.get(), "video/", 6) == 0) { continue; } // We have some other MIME type on clipboard which can be hopefully // converted to text without any problem. MOZ_CLIPBOARD_LOG( " X11: text types in clipboard, no need to filter them.\n"); return true; } // So make sure we offer only types we have at clipboard. nsTArray clipboardFlavors; for (const auto& atom : targets.AsSpan()) { GUniquePtr atom_name(gdk_atom_name(atom)); if (!atom_name) { continue; } if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) { clipboardFlavors.AppendElement(nsCString(atom_name.get())); } } aFlavors.SwapElements(clipboardFlavors); #ifdef MOZ_LOGGING MOZ_CLIPBOARD_LOG(" X11: Flavors which match clipboard content:\n"); for (uint32_t i = 0; i < aFlavors.Length(); i++) { MOZ_CLIPBOARD_LOG(" %s\n", aFlavors[i].get()); } #endif return true; } static nsresult GetTransferableFlavors(nsITransferable* aTransferable, nsTArray& aFlavors) { if (!aTransferable) { return NS_ERROR_FAILURE; } // Get a list of flavors this transferable can import nsresult rv = aTransferable->FlavorsTransferableCanImport(aFlavors); if (NS_FAILED(rv)) { MOZ_CLIPBOARD_LOG(" FlavorsTransferableCanImport falied!\n"); return rv; } #ifdef MOZ_LOGGING MOZ_CLIPBOARD_LOG(" Flavors which can be imported:"); for (const auto& flavor : aFlavors) { MOZ_CLIPBOARD_LOG(" %s", flavor.get()); } #endif return NS_OK; } static bool TransferableSetFile(nsITransferable* aTransferable, const nsACString& aURIList) { nsresult rv; nsTArray uris = mozilla::widget::ParseTextURIList(aURIList); if (!uris.IsEmpty()) { nsCOMPtr fileURI; NS_NewURI(getter_AddRefs(fileURI), uris[0]); if (nsCOMPtr fileURL = do_QueryInterface(fileURI, &rv)) { nsCOMPtr file; rv = fileURL->GetFile(getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) { aTransferable->SetTransferData(kFileMime, file); MOZ_CLIPBOARD_LOG(" successfully set file to clipboard\n"); return true; } } } return false; } static bool TransferableSetHTML(nsITransferable* aTransferable, Span aData) { nsLiteralCString mimeType(kHTMLMime); // Convert text/html into our text format nsAutoCString charset; if (!GetHTMLCharset(aData, charset)) { // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix. MOZ_CLIPBOARD_LOG( "Failed to get html/text encoding, fall back to utf-8.\n"); charset.AssignLiteral("utf-8"); } MOZ_CLIPBOARD_LOG("TransferableSetHTML: HTML detected charset %s", charset.get()); // app which use "text/html" to copy&paste // get the decoder auto encoding = Encoding::ForLabelNoReplacement(charset); if (!encoding) { MOZ_CLIPBOARD_LOG( "TransferableSetHTML: get unicode decoder error (charset: %s)", charset.get()); return false; } // According to spec html UTF-16BE/LE should be switched to UTF-8 // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2 if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) { encoding = UTF_8_ENCODING; } // Remove kHTMLMarkupPrefix again, it won't necessarily cause any // issues, but might confuse other users. const size_t prefixLen = std::size(kHTMLMarkupPrefix) - 1; if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen)) .EqualsLiteral(kHTMLMarkupPrefix)) { aData = aData.From(prefixLen); } nsAutoString unicodeData; auto [rv, enc] = encoding->Decode(AsBytes(aData), unicodeData); #if MOZ_LOGGING if (enc != UTF_8_ENCODING && MOZ_CLIPBOARD_LOG_ENABLED()) { nsCString decoderName; enc->Name(decoderName); MOZ_CLIPBOARD_LOG("TransferableSetHTML: expected UTF-8 decoder but got %s", decoderName.get()); } #endif if (NS_FAILED(rv)) { MOZ_CLIPBOARD_LOG("TransferableSetHTML: failed to decode HTML"); return false; } SetTransferableData(aTransferable, mimeType, (const char*)unicodeData.BeginReading(), unicodeData.Length() * sizeof(char16_t)); return true; } NS_IMETHODIMP nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable, ClipboardType aWhichClipboard) { MOZ_DIAGNOSTIC_ASSERT(aTransferable); MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); MOZ_CLIPBOARD_LOG( "nsClipboard::GetNativeClipboardData (%s)\n", aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"); // TODO: Ensure we don't re-enter here. if (!mContext) { return NS_ERROR_FAILURE; } nsTArray flavors; nsresult rv = GetTransferableFlavors(aTransferable, flavors); NS_ENSURE_SUCCESS(rv, rv); // Filter out MIME types on X11 to prevent unwanted conversions, // see Bug 1611407 if (widget::GdkIsX11Display() && !FilterImportedFlavors(aWhichClipboard, flavors)) { MOZ_CLIPBOARD_LOG(" Missing suitable clipboard data, quit."); return NS_OK; } for (uint32_t i = 0; i < flavors.Length(); i++) { nsCString& flavorStr = flavors[i]; if (flavorStr.EqualsLiteral(kJPEGImageMime) || flavorStr.EqualsLiteral(kJPGImageMime) || flavorStr.EqualsLiteral(kPNGImageMime) || flavorStr.EqualsLiteral(kGIFImageMime)) { // Emulate support for image/jpg if (flavorStr.EqualsLiteral(kJPGImageMime)) { flavorStr.Assign(kJPEGImageMime); } MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data\n", flavorStr.get()); auto clipboardData = mContext->GetClipboardData(flavorStr.get(), aWhichClipboard); if (!clipboardData) { MOZ_CLIPBOARD_LOG(" %s type is missing\n", flavorStr.get()); continue; } nsCOMPtr byteStream; NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(), NS_ASSIGNMENT_COPY); aTransferable->SetTransferData(flavorStr.get(), byteStream); MOZ_CLIPBOARD_LOG(" got %s MIME data\n", flavorStr.get()); return NS_OK; } // Special case text/plain since we can convert any // string into text/plain if (flavorStr.EqualsLiteral(kTextMime)) { MOZ_CLIPBOARD_LOG(" Getting text %s MIME clipboard data\n", flavorStr.get()); auto clipboardData = mContext->GetClipboardText(aWhichClipboard); if (!clipboardData) { MOZ_CLIPBOARD_LOG(" failed to get text data\n"); // If the type was text/plain and we couldn't get // text off the clipboard, run the next loop // iteration. continue; } // Convert utf-8 into our text format. NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get()); SetTransferableData(aTransferable, flavorStr, (const char*)ucs2string.BeginReading(), ucs2string.Length() * 2); MOZ_CLIPBOARD_LOG(" got text data, length %zd\n", ucs2string.Length()); return NS_OK; } if (flavorStr.EqualsLiteral(kFileMime)) { MOZ_CLIPBOARD_LOG(" Getting %s file clipboard data\n", flavorStr.get()); auto clipboardData = mContext->GetClipboardData(kURIListMime, aWhichClipboard); if (!clipboardData) { MOZ_CLIPBOARD_LOG(" text/uri-list type is missing\n"); continue; } nsDependentCSubstring fileName(clipboardData.AsSpan()); if (!TransferableSetFile(aTransferable, fileName)) { continue; } return NS_OK; } MOZ_CLIPBOARD_LOG(" Getting %s MIME clipboard data\n", flavorStr.get()); auto clipboardData = mContext->GetClipboardData(flavorStr.get(), aWhichClipboard); #ifdef MOZ_LOGGING if (!clipboardData) { MOZ_CLIPBOARD_LOG(" %s type is missing\n", flavorStr.get()); } #endif if (clipboardData) { MOZ_CLIPBOARD_LOG(" got %s mime type data.\n", flavorStr.get()); // Special case text/html since we can convert into UCS2 if (flavorStr.EqualsLiteral(kHTMLMime)) { if (!TransferableSetHTML(aTransferable, clipboardData.AsSpan())) { continue; } } else { auto span = clipboardData.AsSpan(); SetTransferableData(aTransferable, flavorStr, span.data(), span.Length()); } return NS_OK; } } MOZ_CLIPBOARD_LOG(" failed to get clipboard content.\n"); return NS_OK; } enum DataType { DATATYPE_IMAGE, DATATYPE_FILE, DATATYPE_HTML, DATATYPE_RAW, }; struct DataCallbackHandler { RefPtr mTransferable; nsBaseClipboard::GetDataCallback mDataCallback; nsCString mMimeType; DataType mDataType; explicit DataCallbackHandler(RefPtr aTransferable, nsBaseClipboard::GetDataCallback&& aDataCallback, const char* aMimeType, DataType aDataType = DATATYPE_RAW) : mTransferable(std::move(aTransferable)), mDataCallback(std::move(aDataCallback)), mMimeType(aMimeType), mDataType(aDataType) { MOZ_COUNT_CTOR(DataCallbackHandler); MOZ_CLIPBOARD_LOG("DataCallbackHandler created [%p] MIME %s type %d", this, mMimeType.get(), mDataType); } ~DataCallbackHandler() { MOZ_CLIPBOARD_LOG("DataCallbackHandler deleted [%p]", this); MOZ_COUNT_DTOR(DataCallbackHandler); } }; static void AsyncGetTextImpl(nsITransferable* aTransferable, int32_t aWhichClipboard, nsBaseClipboard::GetDataCallback&& aCallback) { MOZ_CLIPBOARD_LOG("AsyncGetText() type '%s'", aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary" : "clipboard"); gtk_clipboard_request_text( gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)), [](GtkClipboard* aClipboard, const gchar* aText, gpointer aData) -> void { UniquePtr ref( static_cast(aData)); MOZ_CLIPBOARD_LOG("AsyncGetText async handler of [%p]", aData); size_t dataLength = aText ? strlen(aText) : 0; if (dataLength <= 0) { MOZ_CLIPBOARD_LOG(" quit, text is not available"); ref->mDataCallback(NS_OK); return; } // Convert utf-8 into our unicode format. NS_ConvertUTF8toUTF16 utf16string(aText, dataLength); nsLiteralCString flavor(kTextMime); SetTransferableData(ref->mTransferable, flavor, (const char*)utf16string.BeginReading(), utf16string.Length() * 2); MOZ_CLIPBOARD_LOG(" text is set, length = %d", (int)dataLength); ref->mDataCallback(NS_OK); }, new DataCallbackHandler(aTransferable, std::move(aCallback), kTextMime)); } static void AsyncGetDataImpl(nsITransferable* aTransferable, int32_t aWhichClipboard, const char* aMimeType, DataType aDataType, nsBaseClipboard::GetDataCallback&& aCallback) { MOZ_CLIPBOARD_LOG("AsyncGetData() type '%s'", aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary" : "clipboard"); const char* gtkMIMEType = nullptr; switch (aDataType) { case DATATYPE_FILE: // Don't ask Gtk for application/x-moz-file gtkMIMEType = kURIListMime; break; case DATATYPE_IMAGE: case DATATYPE_HTML: case DATATYPE_RAW: gtkMIMEType = aMimeType; break; } gtk_clipboard_request_contents( gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)), gdk_atom_intern(gtkMIMEType, FALSE), [](GtkClipboard* aClipboard, GtkSelectionData* aSelection, gpointer aData) -> void { UniquePtr ref( static_cast(aData)); MOZ_CLIPBOARD_LOG("AsyncGetData async handler [%p] MIME %s type %d", aData, ref->mMimeType.get(), ref->mDataType); int dataLength = gtk_selection_data_get_length(aSelection); if (dataLength <= 0) { ref->mDataCallback(NS_OK); return; } const char* data = (const char*)gtk_selection_data_get_data(aSelection); if (!data) { ref->mDataCallback(NS_OK); return; } switch (ref->mDataType) { case DATATYPE_IMAGE: { MOZ_CLIPBOARD_LOG(" set image clipboard data"); nsCOMPtr byteStream; NS_NewByteInputStream(getter_AddRefs(byteStream), Span(data, dataLength), NS_ASSIGNMENT_COPY); ref->mTransferable->SetTransferData(ref->mMimeType.get(), byteStream); break; } case DATATYPE_FILE: { MOZ_CLIPBOARD_LOG(" set file clipboard data"); nsDependentCSubstring file(data, dataLength); TransferableSetFile(ref->mTransferable, file); break; } case DATATYPE_HTML: { MOZ_CLIPBOARD_LOG(" html clipboard data"); Span dataSpan(data, dataLength); TransferableSetHTML(ref->mTransferable, dataSpan); break; } case DATATYPE_RAW: { MOZ_CLIPBOARD_LOG(" raw clipboard data %s", ref->mMimeType.get()); SetTransferableData(ref->mTransferable, ref->mMimeType, data, dataLength); break; } } ref->mDataCallback(NS_OK); }, new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType, aDataType)); } static void AsyncGetDataFlavor(nsITransferable* aTransferable, int32_t aWhichClipboard, nsCString& aFlavorStr, nsBaseClipboard::GetDataCallback&& aCallback) { if (aFlavorStr.EqualsLiteral(kJPEGImageMime) || aFlavorStr.EqualsLiteral(kJPGImageMime) || aFlavorStr.EqualsLiteral(kPNGImageMime) || aFlavorStr.EqualsLiteral(kGIFImageMime)) { // Emulate support for image/jpg if (aFlavorStr.EqualsLiteral(kJPGImageMime)) { aFlavorStr.Assign(kJPEGImageMime); } MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data", aFlavorStr.get()); AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(), DATATYPE_IMAGE, std::move(aCallback)); return; } // Special case text/plain since we can convert any // string into text/plain if (aFlavorStr.EqualsLiteral(kTextMime)) { MOZ_CLIPBOARD_LOG(" Getting unicode clipboard data"); AsyncGetTextImpl(aTransferable, aWhichClipboard, std::move(aCallback)); return; } if (aFlavorStr.EqualsLiteral(kFileMime)) { MOZ_CLIPBOARD_LOG(" Getting file clipboard data\n"); AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(), DATATYPE_FILE, std::move(aCallback)); return; } if (aFlavorStr.EqualsLiteral(kHTMLMime)) { MOZ_CLIPBOARD_LOG(" Getting HTML clipboard data"); AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(), DATATYPE_HTML, std::move(aCallback)); return; } MOZ_CLIPBOARD_LOG(" Getting raw %s MIME clipboard data\n", aFlavorStr.get()); AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(), DATATYPE_RAW, std::move(aCallback)); } void nsClipboard::AsyncGetNativeClipboardData(nsITransferable* aTransferable, ClipboardType aWhichClipboard, GetDataCallback&& aCallback) { MOZ_DIAGNOSTIC_ASSERT(aTransferable); MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); MOZ_CLIPBOARD_LOG("nsClipboard::AsyncGetNativeClipboardData (%s)", aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary" : "clipboard"); nsTArray importedFlavors; nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors); if (NS_FAILED(rv)) { aCallback(rv); return; } auto flavorsNum = importedFlavors.Length(); if (!flavorsNum) { aCallback(NS_OK); return; } #ifdef MOZ_LOGGING if (flavorsNum > 1) { MOZ_CLIPBOARD_LOG( " Only first MIME type (%s) will be imported from clipboard!", importedFlavors[0].get()); } #endif // Filter out MIME types on X11 to prevent unwanted conversions, // see Bug 1611407 if (widget::GdkIsX11Display()) { AsyncHasNativeClipboardDataMatchingFlavors( importedFlavors, aWhichClipboard, [aWhichClipboard, transferable = nsCOMPtr{aTransferable}, callback = std::move(aCallback)](auto aResultOrError) mutable { if (aResultOrError.isErr()) { callback(aResultOrError.unwrapErr()); return; } nsTArray clipboardFlavors = std::move(aResultOrError.unwrap()); if (!clipboardFlavors.Length()) { MOZ_CLIPBOARD_LOG(" no flavors in clipboard, quit."); callback(NS_OK); return; } AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0], std::move(callback)); }); return; } // Read clipboard directly on Wayland AsyncGetDataFlavor(aTransferable, aWhichClipboard, importedFlavors[0], std::move(aCallback)); } nsresult nsClipboard::EmptyNativeClipboardData(ClipboardType aWhichClipboard) { MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); MOZ_CLIPBOARD_LOG( "nsClipboard::EmptyNativeClipboardData (%s)\n", aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"); if (aWhichClipboard == kSelectionClipboard) { if (mSelectionTransferable) { gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY)); MOZ_ASSERT(!mSelectionTransferable); } } else { if (mGlobalTransferable) { gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD)); MOZ_ASSERT(!mGlobalTransferable); } } ClearCachedTargets(aWhichClipboard); return NS_OK; } void nsClipboard::ClearTransferable(int32_t aWhichClipboard) { if (aWhichClipboard == kSelectionClipboard) { mSelectionSequenceNumber++; mSelectionTransferable = nullptr; } else { mGlobalSequenceNumber++; mGlobalTransferable = nullptr; } } static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) { GUniquePtr atom_name(gdk_atom_name(aTarget)); if (!atom_name) { return false; } if (aFlavor.Equals(atom_name.get())) { MOZ_CLIPBOARD_LOG(" has %s\n", atom_name.get()); return true; } // X clipboard supports image/jpeg, but we want to emulate support // for image/jpg as well if (aFlavor.EqualsLiteral(kJPGImageMime) && !strcmp(atom_name.get(), kJPEGImageMime)) { MOZ_CLIPBOARD_LOG(" has image/jpg\n"); return true; } // application/x-moz-file should be treated like text/uri-list if (aFlavor.EqualsLiteral(kFileMime) && !strcmp(atom_name.get(), kURIListMime)) { MOZ_CLIPBOARD_LOG( " has text/uri-list treating as application/x-moz-file"); return true; } return false; } mozilla::Result nsClipboard::HasNativeClipboardDataMatchingFlavors( const nsTArray& aFlavorList, ClipboardType aWhichClipboard) { MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); MOZ_CLIPBOARD_LOG( "nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n", aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"); if (!mContext) { return Err(NS_ERROR_FAILURE); } auto targets = mContext->GetTargets(aWhichClipboard); if (!targets) { MOZ_CLIPBOARD_LOG(" no targes at clipboard (null)\n"); return false; } #ifdef MOZ_LOGGING if (MOZ_CLIPBOARD_LOG_ENABLED()) { MOZ_CLIPBOARD_LOG(" Asking for content:\n"); for (auto& flavor : aFlavorList) { MOZ_CLIPBOARD_LOG(" MIME %s\n", flavor.get()); } MOZ_CLIPBOARD_LOG(" Clipboard content (target nums %zu):\n", targets.AsSpan().Length()); for (const auto& target : targets.AsSpan()) { GUniquePtr atom_name(gdk_atom_name(target)); if (!atom_name) { MOZ_CLIPBOARD_LOG(" failed to get MIME\n"); continue; } MOZ_CLIPBOARD_LOG(" MIME %s\n", atom_name.get()); } } #endif // Walk through the provided types and try to match it to a // provided type. for (auto& flavor : aFlavorList) { // We special case text/plain here. if (flavor.EqualsLiteral(kTextMime) && gtk_targets_include_text(targets.AsSpan().data(), targets.AsSpan().Length())) { MOZ_CLIPBOARD_LOG(" has kTextMime\n"); return true; } for (const auto& target : targets.AsSpan()) { if (FlavorMatchesTarget(flavor, target)) { return true; } } } MOZ_CLIPBOARD_LOG(" no targes at clipboard (bad match)\n"); return false; } struct TragetCallbackHandler { TragetCallbackHandler(const nsTArray& aAcceptedFlavorList, nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) : mAcceptedFlavorList(aAcceptedFlavorList.Clone()), mCallback(std::move(aCallback)) { MOZ_CLIPBOARD_LOG("TragetCallbackHandler(%p) created", this); } ~TragetCallbackHandler() { MOZ_CLIPBOARD_LOG("TragetCallbackHandler(%p) deleted", this); } nsTArray mAcceptedFlavorList; nsBaseClipboard::HasMatchingFlavorsCallback mCallback; }; void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors( const nsTArray& aFlavorList, ClipboardType aWhichClipboard, nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) { MOZ_DIAGNOSTIC_ASSERT( nsIClipboard::IsClipboardTypeSupported(aWhichClipboard)); MOZ_CLIPBOARD_LOG( "nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)", aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"); gtk_clipboard_request_contents( gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)), gdk_atom_intern("TARGETS", FALSE), [](GtkClipboard* aClipboard, GtkSelectionData* aSelection, gpointer aData) -> void { MOZ_CLIPBOARD_LOG("gtk_clipboard_request_contents async handler (%p)", aData); UniquePtr handler( static_cast(aData)); GdkAtom* targets = nullptr; gint targetsNum = 0; if (gtk_selection_data_get_length(aSelection) > 0) { gtk_selection_data_get_targets(aSelection, &targets, &targetsNum); #ifdef MOZ_LOGGING if (MOZ_CLIPBOARD_LOG_ENABLED()) { MOZ_CLIPBOARD_LOG(" Clipboard content (target nums %d):\n", targetsNum); for (int i = 0; i < targetsNum; i++) { GUniquePtr atom_name(gdk_atom_name(targets[i])); if (!atom_name) { MOZ_CLIPBOARD_LOG(" failed to get MIME\n"); continue; } MOZ_CLIPBOARD_LOG(" MIME %s\n", atom_name.get()); } } #endif } nsTArray results; if (targetsNum) { for (auto& flavor : handler->mAcceptedFlavorList) { MOZ_CLIPBOARD_LOG(" looking for %s", flavor.get()); if (flavor.EqualsLiteral(kTextMime) && gtk_targets_include_text(targets, targetsNum)) { results.AppendElement(flavor); MOZ_CLIPBOARD_LOG(" has kTextMime\n"); continue; } for (int i = 0; i < targetsNum; i++) { if (FlavorMatchesTarget(flavor, targets[i])) { results.AppendElement(flavor); } } } } handler->mCallback(std::move(results)); }, new TragetCallbackHandler(aFlavorList, std::move(aCallback))); } nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) { nsITransferable* retval; if (aWhichClipboard == kSelectionClipboard) retval = mSelectionTransferable.get(); else retval = mGlobalTransferable.get(); return retval; } void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard, GtkSelectionData* aSelectionData) { // Someone has asked us to hand them something. The first thing // that we want to do is see if that something includes text. If // it does, try to give it text/plain after converting it to // utf-8. int32_t whichClipboard; // which clipboard? GdkAtom selection = gtk_selection_data_get_selection(aSelectionData); if (selection == GDK_SELECTION_PRIMARY) whichClipboard = kSelectionClipboard; else if (selection == GDK_SELECTION_CLIPBOARD) whichClipboard = kGlobalClipboard; else return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF MOZ_CLIPBOARD_LOG( "nsClipboard::SelectionGetEvent (%s)\n", whichClipboard == kSelectionClipboard ? "primary" : "clipboard"); nsCOMPtr trans = GetTransferable(whichClipboard); if (!trans) { // We have nothing to serve MOZ_CLIPBOARD_LOG( "nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n", whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard"); return; } nsresult rv; nsCOMPtr item; GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData); MOZ_CLIPBOARD_LOG(" selection target %s\n", GUniquePtr(gdk_atom_name(selectionTarget)).get()); // Check to see if the selection data is some text type. if (gtk_targets_include_text(&selectionTarget, 1)) { MOZ_CLIPBOARD_LOG(" providing text/plain data\n"); // Try to convert our internal type into a text string. Get // the transferable for this clipboard and try to get the // text/plain type for it. rv = trans->GetTransferData("text/plain", getter_AddRefs(item)); if (NS_FAILED(rv) || !item) { MOZ_CLIPBOARD_LOG(" GetTransferData() failed to get text/plain!\n"); return; } nsCOMPtr wideString; wideString = do_QueryInterface(item); if (!wideString) return; nsAutoString ucs2string; wideString->GetData(ucs2string); NS_ConvertUTF16toUTF8 utf8string(ucs2string); MOZ_CLIPBOARD_LOG(" sent %zd bytes of utf-8 data\n", utf8string.Length()); if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) { MOZ_CLIPBOARD_LOG( " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n"); // Bypass gtk_selection_data_set_text, which will convert \n to \r\n // in some versions of GTK. gtk_selection_data_set(aSelectionData, selectionTarget, 8, reinterpret_cast(utf8string.get()), utf8string.Length()); } else { gtk_selection_data_set_text(aSelectionData, utf8string.get(), utf8string.Length()); } return; } // Check to see if the selection data is an image type if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) { MOZ_CLIPBOARD_LOG(" providing image data\n"); // Look through our transfer data for the image static const char* const imageMimeTypes[] = {kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime}; nsCOMPtr imageItem; nsCOMPtr image; for (uint32_t i = 0; i < std::size(imageMimeTypes); i++) { rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem)); if (NS_FAILED(rv)) { MOZ_CLIPBOARD_LOG(" %s is missing at GetTransferData()\n", imageMimeTypes[i]); continue; } image = do_QueryInterface(imageItem); if (image) { MOZ_CLIPBOARD_LOG(" %s is available at GetTransferData()\n", imageMimeTypes[i]); break; } } if (!image) { // Not getting an image for an image mime type!? MOZ_CLIPBOARD_LOG( " Failed to get any image mime from GetTransferData()!\n"); return; } RefPtr pixbuf = nsImageToPixbuf::ImageToPixbuf(image); if (!pixbuf) { MOZ_CLIPBOARD_LOG(" nsImageToPixbuf::ImageToPixbuf() failed!\n"); return; } MOZ_CLIPBOARD_LOG(" Setting pixbuf image data as %s\n", GUniquePtr(gdk_atom_name(selectionTarget)).get()); gtk_selection_data_set_pixbuf(aSelectionData, pixbuf); return; } if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) { MOZ_CLIPBOARD_LOG(" providing %s data\n", kHTMLMime); rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item)); if (NS_FAILED(rv) || !item) { MOZ_CLIPBOARD_LOG(" failed to get %s data by GetTransferData()!\n", kHTMLMime); return; } nsCOMPtr wideString; wideString = do_QueryInterface(item); if (!wideString) { MOZ_CLIPBOARD_LOG(" failed to get wideString interface!"); return; } nsAutoString ucs2string; wideString->GetData(ucs2string); nsAutoCString html; // Add the prefix so the encoding is correctly detected. html.AppendLiteral(kHTMLMarkupPrefix); AppendUTF16toUTF8(ucs2string, html); MOZ_CLIPBOARD_LOG(" Setting %zd bytes of %s data\n", html.Length(), GUniquePtr(gdk_atom_name(selectionTarget)).get()); gtk_selection_data_set(aSelectionData, selectionTarget, 8, (const guchar*)html.get(), html.Length()); return; } // We put kFileMime onto the clipboard as kURIListMime. if (selectionTarget == gdk_atom_intern(kURIListMime, FALSE)) { MOZ_CLIPBOARD_LOG(" providing %s data\n", kURIListMime); rv = trans->GetTransferData(kFileMime, getter_AddRefs(item)); if (NS_FAILED(rv) || !item) { MOZ_CLIPBOARD_LOG(" failed to get %s data by GetTransferData()!\n", kFileMime); return; } nsCOMPtr file = do_QueryInterface(item); if (!file) { MOZ_CLIPBOARD_LOG(" failed to get nsIFile interface!"); return; } nsCOMPtr fileURI; rv = NS_NewFileURI(getter_AddRefs(fileURI), file); if (NS_FAILED(rv)) { MOZ_CLIPBOARD_LOG(" failed to get fileURI\n"); return; } nsAutoCString uri; if (NS_FAILED(fileURI->GetSpec(uri))) { MOZ_CLIPBOARD_LOG(" failed to get fileURI spec\n"); return; } MOZ_CLIPBOARD_LOG(" Setting %zd bytes of data\n", uri.Length()); gtk_selection_data_set(aSelectionData, selectionTarget, 8, (const guchar*)uri.get(), uri.Length()); return; } MOZ_CLIPBOARD_LOG(" Try if we have anything at GetTransferData() for %s\n", GUniquePtr(gdk_atom_name(selectionTarget)).get()); // Try to match up the selection data target to something our // transferable provides. GUniquePtr target_name(gdk_atom_name(selectionTarget)); if (!target_name) { MOZ_CLIPBOARD_LOG(" Failed to get target name!\n"); return; } rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item)); // nothing found? if (NS_FAILED(rv) || !item) { MOZ_CLIPBOARD_LOG(" Failed to get anything from GetTransferData()!\n"); return; } void* primitive_data = nullptr; uint32_t dataLen = 0; nsPrimitiveHelpers::CreateDataFromPrimitive( nsDependentCString(target_name.get()), item, &primitive_data, &dataLen); if (!primitive_data) { MOZ_CLIPBOARD_LOG(" Failed to get primitive data!\n"); return; } MOZ_CLIPBOARD_LOG(" Setting %s as a primitive data type, %d bytes\n", target_name.get(), dataLen); gtk_selection_data_set(aSelectionData, selectionTarget, 8, /* 8 bits in a unit */ (const guchar*)primitive_data, dataLen); free(primitive_data); } void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) { if (aWhichClipboard == kSelectionClipboard) { nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr); } else { nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr); } } void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) { Maybe whichClipboard = GetGeckoClipboardType(aGtkClipboard); if (whichClipboard.isNothing()) { return; } MOZ_CLIPBOARD_LOG( "nsClipboard::SelectionClearEvent (%s)\n", *whichClipboard == kSelectionClipboard ? "primary" : "clipboard"); ClearCachedTargets(*whichClipboard); ClearTransferable(*whichClipboard); ClearClipboardCache(*whichClipboard); } void nsClipboard::OwnerChangedEvent(GtkClipboard* aGtkClipboard, GdkEventOwnerChange* aEvent) { Maybe whichClipboard = GetGeckoClipboardType(aGtkClipboard); if (whichClipboard.isNothing()) { return; } MOZ_CLIPBOARD_LOG( "nsClipboard::OwnerChangedEvent (%s)\n", *whichClipboard == kSelectionClipboard ? "primary" : "clipboard"); GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* { if (!aEvent->owner) { return nullptr; } gpointer user_data = nullptr; gdk_window_get_user_data(aEvent->owner, &user_data); return GTK_WIDGET(user_data); }(); // If we can get GtkWidget from the current clipboard owner, this // owner-changed event must be triggered by ourself via calling // gtk_clipboard_set_with_data, the sequence number should already be handled. if (!gtkWidget) { if (*whichClipboard == kSelectionClipboard) { mSelectionSequenceNumber++; } else { mGlobalSequenceNumber++; } } ClearCachedTargets(*whichClipboard); } void clipboard_get_cb(GtkClipboard* aGtkClipboard, GtkSelectionData* aSelectionData, guint info, gpointer user_data) { MOZ_CLIPBOARD_LOG("clipboard_get_cb() callback\n"); nsClipboard* clipboard = static_cast(user_data); clipboard->SelectionGetEvent(aGtkClipboard, aSelectionData); } void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) { MOZ_CLIPBOARD_LOG("clipboard_clear_cb() callback\n"); nsClipboard* clipboard = static_cast(user_data); clipboard->SelectionClearEvent(aGtkClipboard); } void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard, GdkEventOwnerChange* aEvent, gpointer aUserData) { MOZ_CLIPBOARD_LOG("clipboard_owner_change_cb() callback\n"); nsClipboard* clipboard = static_cast(aUserData); clipboard->OwnerChangedEvent(aGtkClipboard, aEvent); } /* * This function extracts the encoding label from the subset of HTML internal * encoding declaration syntax that uses the old long form with double quotes * and without spaces around the equals sign between the "content" attribute * name and the attribute value. * * This was added for the sake of an ancient version of StarOffice * in the pre-UTF-8 era in bug 123389. It is unclear if supporting * non-UTF-8 encodings is still necessary and if this function * still needs to exist. * * As of December 2022, both Gecko and LibreOffice emit an UTF-8 * declaration that this function successfully extracts "UTF-8" from, * but that's also the default that we fall back on if this function * fails to extract a label. */ bool GetHTMLCharset(Span aData, nsCString& aFoundCharset) { // Assume ASCII first to find "charset" info const nsDependentCSubstring htmlStr(aData); nsACString::const_iterator start, end; htmlStr.BeginReading(start); htmlStr.EndReading(end); nsACString::const_iterator valueStart(start), valueEnd(start); if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) { start = end; htmlStr.EndReading(end); if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) { valueStart = end; start = end; htmlStr.EndReading(end); if (FindCharInReadable('"', start, end)) valueEnd = start; } } // find "charset" in HTML if (valueStart != valueEnd) { aFoundCharset = Substring(valueStart, valueEnd); ToUpperCase(aFoundCharset); return true; } return false; }