Autogenerated with:
cp dom/.clang-format widget/ && mach format widget/**.{cpp,h,mm}
Manual changes:
* Had to add a manual override in nsWaylandDisplay.h /
DMABufFormats.cpp to include mozwayland before protocols.
* Missing includes in nsWindowWayland/X11.h / nsDragService.
* nsFilePicker missed an nsWindow forward declaration.
* Various overrides in widget/windows because windows headers are order
dependent.
Differential Revision: https://phabricator.services.mozilla.com/D311688
1731 lines
65 KiB
C++
1731 lines
65 KiB
C++
/* 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 "nsClipboard.h"
|
|
|
|
#include "nsArrayUtils.h"
|
|
#include "nsComponentManagerUtils.h"
|
|
#if defined(MOZ_X11)
|
|
# include "RetrievalContextX11.h"
|
|
#endif
|
|
#if defined(MOZ_WAYLAND)
|
|
# include "RetrievalContextGtk.h"
|
|
# include "RetrievalContextWayland.h"
|
|
# include "nsWaylandDisplay.h"
|
|
#endif
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "GRefPtr.h"
|
|
#include "HeadlessClipboard.h"
|
|
#include "WidgetUtilsGtk.h"
|
|
#include "imgIContainer.h"
|
|
#include "mozilla/GRefPtr.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPrefs_clipboard.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "mozilla/widget/nsGtkHtmlUtils.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsGtkUtils.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIFileURL.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIURI.h"
|
|
#include "nsImageToPixbuf.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrimitiveHelpers.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsSupportsPrimitives.h"
|
|
#if defined(MOZ_X11)
|
|
# include <gtk/gtkx.h>
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::widget;
|
|
|
|
// Idle timeout for receiving selection and property notify events (microsec)
|
|
// Right now it's set to 1 sec.
|
|
const int mozilla::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 mozilla::kClipboardFastIterationNum = 3;
|
|
|
|
static const char kURIListMime[] = "text/uri-list";
|
|
|
|
// Per-platform target names from the W3C clipboard-apis spec, Appendix A
|
|
// (https://www.w3.org/TR/clipboard-apis/#to-os-specific-custom-map-name and
|
|
// #to-os-specific-custom-name). On Linux/ChromeOS/Android, the map atom is
|
|
// served as "application/web;type=\"custom/formatmap\"" and each per-slot
|
|
// atom is "application/web;type=\"custom/formatN\"" where N is the slot
|
|
// index.
|
|
static constexpr char kWebCustomFormatMapTarget[] =
|
|
"application/web;type=\"custom/formatmap\"";
|
|
static constexpr char kWebCustomFormatTargetPrefix[] =
|
|
"application/web;type=\"custom/format";
|
|
|
|
// MIME to exclude sensitive data (password) from the clipboard history on not
|
|
// just KDE.
|
|
static const char kKDEPasswordManagerHintMime[] = "x-kde-passwordManagerHint";
|
|
|
|
// 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);
|
|
|
|
ClipboardTargets::ClipboardTargets(GList* aTargets) {
|
|
for (GList* tmp = aTargets; tmp; tmp = tmp->next) {
|
|
mTargets.AppendElement(GDK_POINTER_TO_ATOM(tmp->data));
|
|
}
|
|
}
|
|
|
|
ClipboardTargets::ClipboardTargets(GUniquePtr<GdkAtom> aTargets,
|
|
int aTargetsNum) {
|
|
for (int j = 0; j < aTargetsNum; j++) {
|
|
mTargets.AppendElement(aTargets.get()[j]);
|
|
}
|
|
}
|
|
|
|
ClipboardTargets ClipboardTargets::Clone() const {
|
|
return ClipboardTargets(mTargets.Clone());
|
|
}
|
|
|
|
void ClipboardTargets::Set(ClipboardTargets aTargets) {
|
|
mTargets = std::move(aTargets.mTargets);
|
|
}
|
|
|
|
bool ClipboardTargets::Contains(GdkAtom aTarget) const {
|
|
return mTargets.Contains(aTarget);
|
|
}
|
|
|
|
void ClipboardData::SetData(Span<const uint8_t> aData) {
|
|
mData = nullptr;
|
|
mLength = aData.Length();
|
|
if (mLength) {
|
|
mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
|
|
memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
|
|
}
|
|
}
|
|
|
|
void ClipboardData::SetText(Span<const char> aData) {
|
|
mData = nullptr;
|
|
mLength = aData.Length();
|
|
if (mLength) {
|
|
mData.reset(
|
|
reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
|
|
memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
|
|
mData.get()[mLength] = '\0';
|
|
}
|
|
}
|
|
|
|
void ClipboardData::SetTargets(GUniquePtr<GdkAtom> aTarget, int aTargetsNum) {
|
|
mLength = aTargetsNum;
|
|
mData.reset(reinterpret_cast<char*>(aTarget.release()));
|
|
}
|
|
|
|
ClipboardTargets ClipboardData::ExtractTargets() {
|
|
ClipboardTargets targets(
|
|
GUniquePtr<GdkAtom>(reinterpret_cast<GdkAtom*>(mData.release())),
|
|
mLength);
|
|
mLength = 0;
|
|
return targets;
|
|
}
|
|
|
|
GdkAtom mozilla::GetSelectionAtom(int32_t aWhichClipboard) {
|
|
if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
|
|
return GDK_SELECTION_CLIPBOARD;
|
|
|
|
return GDK_SELECTION_PRIMARY;
|
|
}
|
|
|
|
Maybe<nsIClipboard::ClipboardType> mozilla::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
|
|
}
|
|
|
|
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 = MakeRefPtr<RetrievalContextX11>();
|
|
}
|
|
#endif
|
|
#if defined(MOZ_WAYLAND)
|
|
if (widget::GdkIsWaylandDisplay() &&
|
|
StaticPrefs::widget_wayland_native_data_session_AtStartup()) {
|
|
mContext = MakeRefPtr<RetrievalContextWayland>();
|
|
}
|
|
if (!mContext) {
|
|
mContext = MakeRefPtr<RetrievalContextGtk>();
|
|
}
|
|
#endif
|
|
|
|
nsCOMPtr<nsIObserverService> 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<nsCString> 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. For "web foo/bar"
|
|
// flavors we publish only the W3C-spec per-target atom (not the literal
|
|
// flavor name); the essence -> target mapping is recorded in a local map
|
|
// that we'll assign to the member after gtk_clipboard_set_with_data
|
|
// succeeds (that call synchronously fires clipboard_clear_cb for any
|
|
// previously-owned data, which would otherwise wipe the entries we just
|
|
// built).
|
|
mozilla::widget::WebCustomFormatMap newWebCustomFormatMap;
|
|
uint32_t webCustomFormatIndex = 0;
|
|
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;
|
|
}
|
|
|
|
if (StringBeginsWith(flavorStr, nsLiteralCString(kWebCustomFormatPrefix))) {
|
|
if (!nsBaseClipboard::IsValidFlavor(flavorStr)) {
|
|
continue;
|
|
}
|
|
nsAutoCString targetName(kWebCustomFormatTargetPrefix);
|
|
targetName.AppendInt(webCustomFormatIndex);
|
|
targetName.Append('"');
|
|
MOZ_CLIPBOARD_LOG(" adding web custom format target %s for %s\n",
|
|
targetName.get(), flavorStr.get());
|
|
GdkAtom atom = gdk_atom_intern(targetName.get(), FALSE);
|
|
gtk_target_list_add(list, atom, 0, 0);
|
|
nsDependentCSubstring essence(
|
|
Substring(flavorStr, strlen(kWebCustomFormatPrefix)));
|
|
newWebCustomFormatMap.InsertOrUpdate(essence, targetName);
|
|
webCustomFormatIndex++;
|
|
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);
|
|
}
|
|
|
|
// If any web custom formats were published, also advertise the map atom so
|
|
// readers can discover what's available.
|
|
if (!newWebCustomFormatMap.IsEmpty()) {
|
|
MOZ_CLIPBOARD_LOG(" adding web custom format map target %s\n",
|
|
kWebCustomFormatMapTarget);
|
|
GdkAtom mapAtom = gdk_atom_intern(kWebCustomFormatMapTarget, FALSE);
|
|
gtk_target_list_add(list, mapAtom, 0, 0);
|
|
}
|
|
|
|
// Try to exclude private data from clipboard history.
|
|
if (!StaticPrefs::clipboard_copyPrivateDataToClipboardCloudOrHistory() &&
|
|
aTransferable->GetIsPrivateData()) {
|
|
GdkAtom atom = gdk_atom_intern(kKDEPasswordManagerHintMime, 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);
|
|
|
|
// On Wayland, track when we're setting clipboard data to avoid double
|
|
// sequence number increment in OwnerChangedEvent
|
|
MarkNextOwnerClipboardChange(aWhichClipboard, /* aOurChange */ true);
|
|
|
|
// 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)) {
|
|
IncrementSequenceNumber(aWhichClipboard);
|
|
// 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) {
|
|
mSelectionTransferable = aTransferable;
|
|
} else {
|
|
mGlobalTransferable = aTransferable;
|
|
gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
|
|
}
|
|
WebCustomFormatMapFor(aWhichClipboard) = std::move(newWebCustomFormatMap);
|
|
MOZ_CLIPBOARD_LOG(" sequence %d", GetSequenceNumber(aWhichClipboard));
|
|
rv = NS_OK;
|
|
} else {
|
|
MOZ_CLIPBOARD_LOG(" gtk_clipboard_set_with_data() failed!\n");
|
|
EmptyNativeClipboardData(aWhichClipboard);
|
|
MOZ_CLIPBOARD_LOG(" sequence %d", GetSequenceNumber(aWhichClipboard));
|
|
rv = NS_ERROR_FAILURE;
|
|
}
|
|
|
|
gtk_target_table_free(gtkTargets, numTargets);
|
|
gtk_target_list_unref(list);
|
|
return rv;
|
|
}
|
|
|
|
mozilla::Result<int32_t, nsresult>
|
|
nsClipboard::GetNativeClipboardSequenceNumber(ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
|
|
: mGlobalSequenceNumber;
|
|
}
|
|
|
|
// 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.
|
|
mozilla::widget::WebCustomFormatMap
|
|
nsClipboard::GetWebCustomFormatMapFromClipboard(int32_t aWhichClipboard) {
|
|
mozilla::widget::WebCustomFormatMap map;
|
|
if (!mContext) {
|
|
return map;
|
|
}
|
|
auto mapData =
|
|
mContext->GetClipboardData(kWebCustomFormatMapTarget, aWhichClipboard);
|
|
if (!mapData) {
|
|
MOZ_CLIPBOARD_LOG(" no web custom format map on clipboard\n");
|
|
return map;
|
|
}
|
|
if (!mozilla::widget::JSONToWebCustomFormatMap(
|
|
nsDependentCSubstring(mapData.AsSpan()), map)) {
|
|
MOZ_CLIPBOARD_LOG(" failed to parse web custom format map JSON\n");
|
|
map.Clear();
|
|
}
|
|
return map;
|
|
}
|
|
|
|
bool nsClipboard::HasSuitableData(int32_t aWhichClipboard,
|
|
const nsACString& aFlavor) {
|
|
MOZ_CLIPBOARD_LOG("%s for %s", __FUNCTION__,
|
|
PromiseFlatCString(aFlavor).get());
|
|
|
|
auto targets = mContext->GetTargets(aWhichClipboard);
|
|
if (!targets) {
|
|
MOZ_CLIPBOARD_LOG(" X11: no targes at clipboard (null), quit.\n");
|
|
// It is possible that clipboard owner doesn't provide TARGETS properly, but
|
|
// the text data is still available.
|
|
return aFlavor.EqualsLiteral(kTextMime);
|
|
}
|
|
|
|
for (const auto& atom : targets.AsSpan()) {
|
|
GUniquePtr<gchar> 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.
|
|
// XXX should only return true for text/plain type?
|
|
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.
|
|
for (const auto& atom : targets.AsSpan()) {
|
|
GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
|
|
if (!atom_name) {
|
|
continue;
|
|
}
|
|
if (aFlavor.Equals(atom_name.get())) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" X11: no suitable data in clipboard, quit.\n");
|
|
return false;
|
|
}
|
|
|
|
static already_AddRefed<nsIFile> GetFileData(const nsACString& aURIList) {
|
|
nsCOMPtr<nsIFile> file;
|
|
nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(aURIList);
|
|
if (!uris.IsEmpty()) {
|
|
nsCOMPtr<nsIURI> fileURI;
|
|
NS_NewURI(getter_AddRefs(fileURI), uris[0]);
|
|
if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI)) {
|
|
fileURL->GetFile(getter_AddRefs(file));
|
|
}
|
|
}
|
|
return file.forget();
|
|
}
|
|
|
|
static already_AddRefed<nsISupports> GetHTMLData(Span<const char> aData) {
|
|
nsAutoString unicodeData;
|
|
if (!DecodeHTMLData(aData, unicodeData)) {
|
|
MOZ_CLIPBOARD_LOG("GetHTMLData: failed to decode HTML");
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
nsLiteralCString(kHTMLMime), (const char*)unicodeData.BeginReading(),
|
|
unicodeData.Length() * sizeof(char16_t), getter_AddRefs(wrapper));
|
|
return wrapper.forget();
|
|
}
|
|
|
|
mozilla::Result<nsCOMPtr<nsISupports>, nsresult>
|
|
nsClipboard::GetNativeClipboardData(const nsACString& aFlavor,
|
|
ClipboardType aWhichClipboard,
|
|
uint64_t aThreshold) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
MOZ_DIAGNOSTIC_ASSERT(IsValidFlavor(aFlavor));
|
|
|
|
MOZ_CLIPBOARD_LOG(
|
|
"nsClipboard::GetNativeClipboardData (%s) for %s sequence num %d",
|
|
aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard",
|
|
PromiseFlatCString(aFlavor).get(), GetSequenceNumber(aWhichClipboard));
|
|
|
|
// TODO: Ensure we don't re-enter here.
|
|
if (!mContext) {
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
// Web custom format flavors are virtual: the strings the caller asks
|
|
// for (kWebCustomFormatMapType, "web foo/bar") are never atoms on the
|
|
// clipboard. Resolve them to the actual atom name up front by fetching
|
|
// the map JSON, so the TARGETS filter below can do its job on the
|
|
// real atom. We also keep the map so the type-specific fetch can reuse
|
|
// it without re-parsing.
|
|
const bool isWebMap = aFlavor.EqualsLiteral(kWebCustomFormatMapType);
|
|
const bool isWebFormat =
|
|
StringBeginsWith(aFlavor, nsLiteralCString(kWebCustomFormatPrefix));
|
|
mozilla::widget::WebCustomFormatMap cachedMap;
|
|
nsAutoCString targetName(aFlavor);
|
|
if (isWebMap || isWebFormat) {
|
|
cachedMap = GetWebCustomFormatMapFromClipboard(aWhichClipboard);
|
|
if (cachedMap.IsEmpty()) {
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
if (isWebMap) {
|
|
targetName.Assign(kWebCustomFormatMapTarget);
|
|
} else {
|
|
nsDependentCSubstring essence(
|
|
Substring(aFlavor, strlen(kWebCustomFormatPrefix)));
|
|
auto entry = cachedMap.Lookup(essence);
|
|
if (!entry) {
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
targetName.Assign(entry.Data());
|
|
}
|
|
}
|
|
|
|
// X11 TARGETS-based filter (also avoids unwanted conversions, see
|
|
// Bug 1611407). targetName is the resolved target atom for web
|
|
// flavors and aFlavor itself for everything else.
|
|
if (widget::GdkIsX11Display() &&
|
|
!HasSuitableData(aWhichClipboard, targetName)) {
|
|
MOZ_CLIPBOARD_LOG(" Missing suitable clipboard data, quit.");
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
// Web custom format map: surface the parsed map's keys as "web foo/bar"
|
|
// flavors.
|
|
if (isWebMap) {
|
|
nsCOMPtr<nsIMutableArray> customFormats =
|
|
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
|
for (const auto& essence : cachedMap.Keys()) {
|
|
nsCOMPtr<nsISupportsCString> customFormat =
|
|
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
|
customFormat->SetData(nsLiteralCString(kWebCustomFormatPrefix) + essence);
|
|
customFormats->AppendElement(customFormat);
|
|
}
|
|
return nsCOMPtr<nsISupports>(std::move(customFormats));
|
|
}
|
|
|
|
// Specific web custom format: read the bytes from the resolved
|
|
// per-target atom and wrap them as nsISupportsCString.
|
|
if (isWebFormat) {
|
|
MOZ_CLIPBOARD_LOG(
|
|
" Getting web custom format %s clipboard data (target %s)\n",
|
|
PromiseFlatCString(aFlavor).get(), targetName.get());
|
|
auto targetData =
|
|
mContext->GetClipboardData(targetName.get(), aWhichClipboard);
|
|
if (!targetData) {
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
auto span = targetData.AsSpan();
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
aFlavor, span.data(), span.Length(), getter_AddRefs(wrapper));
|
|
return wrapper;
|
|
}
|
|
|
|
if (aFlavor.EqualsLiteral(kJPEGImageMime) ||
|
|
aFlavor.EqualsLiteral(kJPGImageMime) ||
|
|
aFlavor.EqualsLiteral(kPNGImageMime) ||
|
|
aFlavor.EqualsLiteral(kGIFImageMime)) {
|
|
// Emulate support for image/jpg
|
|
nsAutoCString flavor(aFlavor.EqualsLiteral(kJPGImageMime)
|
|
? kJPEGImageMime
|
|
: PromiseFlatCString(aFlavor).get());
|
|
MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data\n",
|
|
flavor.get());
|
|
|
|
auto clipboardData =
|
|
mContext->GetClipboardData(flavor.get(), aWhichClipboard);
|
|
if (!clipboardData) {
|
|
MOZ_CLIPBOARD_LOG(" %s type is missing\n", flavor.get());
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> byteStream;
|
|
NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
|
|
NS_ASSIGNMENT_COPY);
|
|
|
|
MOZ_CLIPBOARD_LOG(" got %s MIME data\n", flavor.get());
|
|
return nsCOMPtr<nsISupports>(std::move(byteStream));
|
|
}
|
|
|
|
// Special case text/plain since we can convert any
|
|
// string into text/plain
|
|
if (aFlavor.EqualsLiteral(kTextMime)) {
|
|
MOZ_CLIPBOARD_LOG(" Getting text %s MIME clipboard data\n",
|
|
PromiseFlatCString(aFlavor).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.
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
if (aThreshold && strlen(clipboardData.get()) * 2 > aThreshold) {
|
|
return mozilla::Err(NS_ERROR_CLIPBOARD_TOO_BIG);
|
|
}
|
|
|
|
// Convert utf-8 into our text format.
|
|
NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
|
|
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
aFlavor, (const char*)ucs2string.BeginReading(),
|
|
ucs2string.Length() * 2, getter_AddRefs(wrapper));
|
|
|
|
MOZ_CLIPBOARD_LOG(" got text data, length %zd\n", ucs2string.Length());
|
|
return wrapper;
|
|
}
|
|
|
|
if (aFlavor.EqualsLiteral(kFileMime)) {
|
|
MOZ_CLIPBOARD_LOG(" Getting file %s MIME clipboard data\n",
|
|
PromiseFlatCString(aFlavor).get());
|
|
|
|
auto clipboardData =
|
|
mContext->GetClipboardData(kURIListMime, aWhichClipboard);
|
|
if (!clipboardData) {
|
|
MOZ_CLIPBOARD_LOG(" text/uri-list type is missing\n");
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
nsDependentCSubstring fileName(clipboardData.AsSpan());
|
|
if (nsCOMPtr<nsIFile> file = GetFileData(fileName)) {
|
|
MOZ_CLIPBOARD_LOG(" got file data\n");
|
|
return nsCOMPtr<nsISupports>(std::move(file));
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" failed to get file data\n");
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" Getting %s MIME clipboard data\n",
|
|
PromiseFlatCString(aFlavor).get());
|
|
|
|
auto clipboardData = mContext->GetClipboardData(
|
|
PromiseFlatCString(aFlavor).get(), aWhichClipboard);
|
|
if (!clipboardData) {
|
|
MOZ_CLIPBOARD_LOG(" failed to get clipboard content.\n");
|
|
return nsCOMPtr<nsISupports>{};
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" got %s mime type data.\n",
|
|
PromiseFlatCString(aFlavor).get());
|
|
|
|
// Special case text/html since we can convert into UCS2
|
|
auto span = clipboardData.AsSpan();
|
|
if (aFlavor.EqualsLiteral(kHTMLMime)) {
|
|
return nsCOMPtr<nsISupports>(GetHTMLData(span));
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
aFlavor, span.data(), span.Length(), getter_AddRefs(wrapper));
|
|
return wrapper;
|
|
}
|
|
|
|
enum DataType {
|
|
DATATYPE_IMAGE,
|
|
DATATYPE_FILE,
|
|
DATATYPE_HTML,
|
|
DATATYPE_RAW,
|
|
};
|
|
|
|
struct DataCallbackHandler {
|
|
nsBaseClipboard::GetNativeDataCallback mDataCallback;
|
|
nsCString mMimeType;
|
|
DataType mDataType;
|
|
|
|
DataCallbackHandler(nsBaseClipboard::GetNativeDataCallback&& aDataCallback,
|
|
const nsACString& aMimeType,
|
|
DataType aDataType = DATATYPE_RAW)
|
|
: 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);
|
|
}
|
|
};
|
|
|
|
// Async fetch + decode the web custom format map JSON for the given
|
|
// clipboard, then invoke the callback with the decoded map. The map will be
|
|
// empty if the target isn't published or the JSON is malformed.
|
|
using WebCustomFormatMapCallback =
|
|
mozilla::MoveOnlyFunction<void(mozilla::widget::WebCustomFormatMap&&)>;
|
|
static void AsyncGetWebCustomFormatMapImpl(
|
|
int32_t aWhichClipboard, WebCustomFormatMapCallback&& aCallback) {
|
|
MOZ_CLIPBOARD_LOG("AsyncGetWebCustomFormatMapImpl() type '%s'",
|
|
aWhichClipboard == nsClipboard::kSelectionClipboard
|
|
? "primary"
|
|
: "clipboard");
|
|
auto* heapCallback = new WebCustomFormatMapCallback(std::move(aCallback));
|
|
gtk_clipboard_request_contents(
|
|
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
|
|
gdk_atom_intern(kWebCustomFormatMapTarget, FALSE),
|
|
[](GtkClipboard*, GtkSelectionData* aSelection, gpointer aData) -> void {
|
|
mozilla::UniquePtr<WebCustomFormatMapCallback> cb(
|
|
static_cast<WebCustomFormatMapCallback*>(aData));
|
|
mozilla::widget::WebCustomFormatMap map;
|
|
int dataLength = gtk_selection_data_get_length(aSelection);
|
|
if (dataLength > 0) {
|
|
const char* data =
|
|
(const char*)gtk_selection_data_get_data(aSelection);
|
|
if (data) {
|
|
mozilla::widget::JSONToWebCustomFormatMap(
|
|
nsDependentCSubstring(data, dataLength), map);
|
|
}
|
|
}
|
|
(*cb)(std::move(map));
|
|
},
|
|
heapCallback);
|
|
}
|
|
|
|
static void AsyncGetTextImpl(
|
|
int32_t aWhichClipboard,
|
|
nsBaseClipboard::GetNativeDataCallback&& 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<DataCallbackHandler> ref(
|
|
static_cast<DataCallbackHandler*>(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(nsCOMPtr<nsISupports>{});
|
|
return;
|
|
}
|
|
|
|
// Convert utf-8 into our unicode format.
|
|
NS_ConvertUTF8toUTF16 utf16string(aText, dataLength);
|
|
nsLiteralCString flavor(kTextMime);
|
|
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
flavor, (const char*)utf16string.BeginReading(),
|
|
utf16string.Length() * 2, getter_AddRefs(wrapper));
|
|
|
|
MOZ_CLIPBOARD_LOG(" text is set, length = %d", (int)dataLength);
|
|
ref->mDataCallback(wrapper);
|
|
},
|
|
new DataCallbackHandler(std::move(aCallback),
|
|
nsLiteralCString(kTextMime)));
|
|
}
|
|
|
|
// aMimeType is the nsIClipboard flavor (what callers know about: text/plain,
|
|
// image/png, "web foo/bar", ...). aTargetName, when non-empty, is the
|
|
// distinct GTK target atom to query on the system clipboard. For most
|
|
// flavors the atom is the same string as the MIME type and aTargetName can
|
|
// stay empty; web custom format reads are the one case where it diverges
|
|
// (the atom is "application/web;type=\"custom/formatN\"" while the MIME
|
|
// type is "web foo/bar").
|
|
static void AsyncGetDataImpl(int32_t aWhichClipboard,
|
|
const nsACString& aMimeType, DataType aDataType,
|
|
nsBaseClipboard::GetNativeDataCallback&& aCallback,
|
|
const nsACString& aTargetName = ""_ns) {
|
|
MOZ_CLIPBOARD_LOG("AsyncGetData() type '%s'",
|
|
aWhichClipboard == nsClipboard::kSelectionClipboard
|
|
? "primary"
|
|
: "clipboard");
|
|
|
|
const nsACString& targetName =
|
|
aTargetName.IsEmpty() ? aMimeType : aTargetName;
|
|
gtk_clipboard_request_contents(
|
|
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
|
|
// Don't ask Gtk for application/x-moz-file.
|
|
gdk_atom_intern((aDataType == DATATYPE_FILE)
|
|
? kURIListMime
|
|
: PromiseFlatCString(targetName).get(),
|
|
FALSE),
|
|
[](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
|
|
gpointer aData) -> void {
|
|
UniquePtr<DataCallbackHandler> ref(
|
|
static_cast<DataCallbackHandler*>(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(nsCOMPtr<nsISupports>{});
|
|
return;
|
|
}
|
|
const char* data = (const char*)gtk_selection_data_get_data(aSelection);
|
|
if (!data) {
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>{});
|
|
return;
|
|
}
|
|
switch (ref->mDataType) {
|
|
case DATATYPE_IMAGE: {
|
|
MOZ_CLIPBOARD_LOG(" get image clipboard data");
|
|
nsCOMPtr<nsIInputStream> byteStream;
|
|
NS_NewByteInputStream(getter_AddRefs(byteStream),
|
|
Span(data, dataLength), NS_ASSIGNMENT_COPY);
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>(byteStream));
|
|
return;
|
|
}
|
|
case DATATYPE_FILE: {
|
|
MOZ_CLIPBOARD_LOG(" get file clipboard data");
|
|
nsDependentCSubstring uriList(data, dataLength);
|
|
if (nsCOMPtr<nsIFile> file = GetFileData(uriList)) {
|
|
MOZ_CLIPBOARD_LOG(" successfully get file data\n");
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>(file));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case DATATYPE_HTML: {
|
|
MOZ_CLIPBOARD_LOG(" html clipboard data");
|
|
Span dataSpan(data, dataLength);
|
|
if (nsCOMPtr<nsISupports> data = GetHTMLData(dataSpan)) {
|
|
MOZ_CLIPBOARD_LOG(" successfully get HTML data\n");
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>(data));
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case DATATYPE_RAW: {
|
|
MOZ_CLIPBOARD_LOG(" raw clipboard data %s", ref->mMimeType.get());
|
|
|
|
nsCOMPtr<nsISupports> wrapper;
|
|
nsPrimitiveHelpers::CreatePrimitiveForData(
|
|
ref->mMimeType, data, dataLength, getter_AddRefs(wrapper));
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>(wrapper));
|
|
return;
|
|
}
|
|
}
|
|
ref->mDataCallback(nsCOMPtr<nsISupports>{});
|
|
},
|
|
new DataCallbackHandler(std::move(aCallback), aMimeType, aDataType));
|
|
}
|
|
|
|
// aWebCustomFormatMap, when present, lets web-flavor reads skip the map
|
|
// JSON round-trip by looking up the per-target atom directly. Typically
|
|
// supplied by AsyncHasNativeClipboardDataMatchingFlavorsWithMap, which has
|
|
// already fetched the map while resolving the flavor query.
|
|
static void AsyncGetDataFlavor(
|
|
int32_t aWhichClipboard, const nsACString& aFlavorStr,
|
|
nsBaseClipboard::GetNativeDataCallback&& aCallback,
|
|
mozilla::Maybe<mozilla::widget::WebCustomFormatMap>&& aWebCustomFormatMap =
|
|
mozilla::Nothing()) {
|
|
// Web custom format map: build the nsIMutableArray of "web foo/bar"
|
|
// flavors from the map we (may have) been handed; fall back to a fresh
|
|
// fetch if we weren't.
|
|
if (aFlavorStr.EqualsLiteral(kWebCustomFormatMapType)) {
|
|
auto buildResult = [callback = std::move(aCallback)](
|
|
mozilla::widget::WebCustomFormatMap&& map) mutable {
|
|
nsCOMPtr<nsIMutableArray> customFormats =
|
|
do_CreateInstance(NS_ARRAY_CONTRACTID);
|
|
for (const auto& essence : map.Keys()) {
|
|
nsCOMPtr<nsISupportsCString> customFormat =
|
|
do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
|
customFormat->SetData(nsLiteralCString(kWebCustomFormatPrefix) +
|
|
essence);
|
|
customFormats->AppendElement(customFormat);
|
|
}
|
|
callback(nsCOMPtr<nsISupports>(std::move(customFormats)));
|
|
};
|
|
if (aWebCustomFormatMap.isSome()) {
|
|
buildResult(aWebCustomFormatMap.extract());
|
|
return;
|
|
}
|
|
AsyncGetWebCustomFormatMapImpl(aWhichClipboard, std::move(buildResult));
|
|
return;
|
|
}
|
|
// Specific "web foo/bar" flavor: use the map (cached or freshly fetched)
|
|
// to find the per-target atom, then read its bytes.
|
|
if (StringBeginsWith(aFlavorStr, nsLiteralCString(kWebCustomFormatPrefix))) {
|
|
nsCString flavorCopy(aFlavorStr);
|
|
auto lookupAndFetch =
|
|
[aWhichClipboard, flavorCopy, callback = std::move(aCallback)](
|
|
mozilla::widget::WebCustomFormatMap&& map) mutable {
|
|
nsDependentCSubstring essence(
|
|
Substring(flavorCopy, strlen(kWebCustomFormatPrefix)));
|
|
auto entry = map.Lookup(essence);
|
|
if (!entry) {
|
|
callback(nsCOMPtr<nsISupports>{});
|
|
return;
|
|
}
|
|
// aMimeType is the user-visible flavor ("web foo/bar"); aTargetName
|
|
// is the per-essence atom Firefox published it under.
|
|
AsyncGetDataImpl(aWhichClipboard, flavorCopy, DATATYPE_RAW,
|
|
std::move(callback), entry.Data());
|
|
};
|
|
if (aWebCustomFormatMap.isSome()) {
|
|
lookupAndFetch(aWebCustomFormatMap.extract());
|
|
return;
|
|
}
|
|
AsyncGetWebCustomFormatMapImpl(aWhichClipboard, std::move(lookupAndFetch));
|
|
return;
|
|
}
|
|
if (aFlavorStr.EqualsLiteral(kJPEGImageMime) ||
|
|
aFlavorStr.EqualsLiteral(kJPGImageMime) ||
|
|
aFlavorStr.EqualsLiteral(kPNGImageMime) ||
|
|
aFlavorStr.EqualsLiteral(kGIFImageMime)) {
|
|
// Emulate support for image/jpg
|
|
nsAutoCString flavor(aFlavorStr.EqualsLiteral(kJPGImageMime)
|
|
? kJPEGImageMime
|
|
: PromiseFlatCString(aFlavorStr).get());
|
|
MOZ_CLIPBOARD_LOG(" Getting image %s MIME clipboard data", flavor.get());
|
|
AsyncGetDataImpl(aWhichClipboard, flavor, 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(aWhichClipboard, std::move(aCallback));
|
|
return;
|
|
}
|
|
if (aFlavorStr.EqualsLiteral(kFileMime)) {
|
|
MOZ_CLIPBOARD_LOG(" Getting file clipboard data\n");
|
|
AsyncGetDataImpl(aWhichClipboard, aFlavorStr, DATATYPE_FILE,
|
|
std::move(aCallback));
|
|
return;
|
|
}
|
|
if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
|
|
MOZ_CLIPBOARD_LOG(" Getting HTML clipboard data");
|
|
AsyncGetDataImpl(aWhichClipboard, aFlavorStr, DATATYPE_HTML,
|
|
std::move(aCallback));
|
|
return;
|
|
}
|
|
MOZ_CLIPBOARD_LOG(" Getting raw %s MIME clipboard data\n",
|
|
PromiseFlatCString(aFlavorStr).get());
|
|
AsyncGetDataImpl(aWhichClipboard, aFlavorStr, DATATYPE_RAW,
|
|
std::move(aCallback));
|
|
}
|
|
|
|
void nsClipboard::AsyncGetNativeClipboardData(
|
|
const nsACString& aFlavor, ClipboardType aWhichClipboard,
|
|
GetNativeDataCallback&& aCallback) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
|
|
MOZ_CLIPBOARD_LOG("nsClipboard::AsyncGetNativeClipboardData (%s) for %s",
|
|
aWhichClipboard == nsClipboard::kSelectionClipboard
|
|
? "primary"
|
|
: "clipboard",
|
|
PromiseFlatCString(aFlavor).get());
|
|
|
|
// X11 path: filter against TARGETS (also avoids unwanted conversions,
|
|
// see Bug 1611407). When the filter has to fetch the web custom format
|
|
// map to resolve a web-related entry, it forwards the parsed map via
|
|
// the callback so AsyncGetDataFlavor can reuse it without re-fetching.
|
|
if (widget::GdkIsX11Display()) {
|
|
AsyncHasNativeClipboardDataMatchingFlavorsWithMap(
|
|
nsTArray<nsCString>{PromiseFlatCString(aFlavor)}, aWhichClipboard,
|
|
[aWhichClipboard, flavorCopy = nsCString(aFlavor),
|
|
callback = std::move(aCallback)](
|
|
auto aResultOrError,
|
|
mozilla::Maybe<mozilla::widget::WebCustomFormatMap>
|
|
aWebCustomFormatMap) mutable {
|
|
if (aResultOrError.isErr()) {
|
|
callback(Err(aResultOrError.unwrapErr()));
|
|
return;
|
|
}
|
|
|
|
nsTArray<nsCString> clipboardFlavors =
|
|
std::move(aResultOrError.unwrap());
|
|
if (!clipboardFlavors.Length()) {
|
|
MOZ_CLIPBOARD_LOG(" no flavors in clipboard, quit.");
|
|
callback(nsCOMPtr<nsISupports>{});
|
|
return;
|
|
}
|
|
|
|
// For web custom format reads we want to read back the flavor
|
|
// the caller asked for (kWebCustomFormatMapType or "web foo/bar"),
|
|
// not whatever the filter expanded it into.
|
|
const bool isWebRequest =
|
|
flavorCopy.EqualsLiteral(kWebCustomFormatMapType) ||
|
|
StringBeginsWith(flavorCopy,
|
|
nsLiteralCString(kWebCustomFormatPrefix));
|
|
const nsACString& fetchFlavor =
|
|
isWebRequest
|
|
? static_cast<const nsACString&>(flavorCopy)
|
|
: static_cast<const nsACString&>(clipboardFlavors[0]);
|
|
AsyncGetDataFlavor(aWhichClipboard, fetchFlavor, std::move(callback),
|
|
std::move(aWebCustomFormatMap));
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Wayland path: no TARGETS filter step. Hand directly to
|
|
// AsyncGetDataFlavor, which knows how to fetch the web custom format
|
|
// map on demand when needed.
|
|
AsyncGetDataFlavor(aWhichClipboard, aFlavor, 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);
|
|
MarkNextOwnerClipboardChange(aWhichClipboard, /* aOurChange */ true);
|
|
}
|
|
} else {
|
|
if (mGlobalTransferable) {
|
|
gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
|
|
MOZ_ASSERT(!mGlobalTransferable);
|
|
MarkNextOwnerClipboardChange(aWhichClipboard, /* aOurChange */ true);
|
|
}
|
|
}
|
|
ClearCachedTargets(aWhichClipboard);
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
|
|
IncrementSequenceNumber(aWhichClipboard);
|
|
if (aWhichClipboard == kSelectionClipboard) {
|
|
mSelectionTransferable = nullptr;
|
|
} else {
|
|
mGlobalTransferable = nullptr;
|
|
}
|
|
WebCustomFormatMapFor(aWhichClipboard).Clear();
|
|
}
|
|
|
|
static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
|
|
GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
|
|
if (!atom_name) {
|
|
return false;
|
|
}
|
|
if (aFlavor.Equals(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)) {
|
|
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<bool, nsresult>
|
|
nsClipboard::HasNativeClipboardDataMatchingFlavors(
|
|
const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
|
|
MOZ_CLIPBOARD_LOG(
|
|
"nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
|
|
aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
|
|
|
|
if (!mContext) {
|
|
MOZ_CLIPBOARD_LOG(" RetrievalContext is not available\n");
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
auto targets = mContext->GetTargets(aWhichClipboard);
|
|
if (!targets) {
|
|
MOZ_CLIPBOARD_LOG(" no targes at clipboard (null)\n");
|
|
// If TARGETS is not available, fallback to checking for text data directly,
|
|
// as the clipboard owner might not set TARGETS properly, but the text is
|
|
// still available.
|
|
for (auto& flavor : aFlavorList) {
|
|
if (flavor.EqualsLiteral(kTextMime)) {
|
|
MOZ_CLIPBOARD_LOG(" try text data\n");
|
|
if (auto clipboardData = mContext->GetClipboardText(aWhichClipboard)) {
|
|
return true;
|
|
}
|
|
MOZ_CLIPBOARD_LOG(" no text data\n");
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#ifdef MOZ_LOGGING
|
|
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
|
|
MOZ_CLIPBOARD_LOG(" Clipboard content (target nums %zu):\n",
|
|
targets.AsSpan().Length());
|
|
for (const auto& target : targets.AsSpan()) {
|
|
GUniquePtr<gchar> 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
|
|
|
|
// Web custom format map: lazy-loaded only when a "web " query needs it.
|
|
mozilla::widget::WebCustomFormatMap webCustomFormatMap;
|
|
bool didLoadWebCustomFormatMap = false;
|
|
auto loadWebCustomFormatMap = [&]() {
|
|
if (didLoadWebCustomFormatMap) {
|
|
return;
|
|
}
|
|
didLoadWebCustomFormatMap = true;
|
|
webCustomFormatMap = GetWebCustomFormatMapFromClipboard(aWhichClipboard);
|
|
};
|
|
GdkAtom webMapAtom = gdk_atom_intern(kWebCustomFormatMapTarget, FALSE);
|
|
|
|
// Walk through the provided types and try to match it to a
|
|
// provided type.
|
|
for (auto& flavor : aFlavorList) {
|
|
if (flavor.EqualsLiteral(kWebCustomFormatMapType)) {
|
|
for (const auto& target : targets.AsSpan()) {
|
|
if (target == webMapAtom) {
|
|
return true;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (StringBeginsWith(flavor, nsLiteralCString(kWebCustomFormatPrefix))) {
|
|
loadWebCustomFormatMap();
|
|
nsDependentCSubstring essence(
|
|
Substring(flavor, strlen(kWebCustomFormatPrefix)));
|
|
if (webCustomFormatMap.Lookup(essence)) {
|
|
return true;
|
|
}
|
|
continue;
|
|
}
|
|
// We special case text/plain here.
|
|
if (flavor.EqualsLiteral(kTextMime) &&
|
|
gtk_targets_include_text(targets.AsSpan().data(),
|
|
targets.AsSpan().Length())) {
|
|
return true;
|
|
}
|
|
for (const auto& target : targets.AsSpan()) {
|
|
if (FlavorMatchesTarget(flavor, target)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" no matched targes at clipboard\n");
|
|
return false;
|
|
}
|
|
|
|
struct TargetCallbackHandler {
|
|
TargetCallbackHandler(
|
|
const nsTArray<nsCString>& aAcceptedFlavorList,
|
|
nsClipboard::HasMatchingFlavorsCallbackWithMap&& aCallback)
|
|
: mAcceptedFlavorList(aAcceptedFlavorList.Clone()),
|
|
mCallback(std::move(aCallback)) {
|
|
MOZ_CLIPBOARD_LOG("TargetCallbackHandler(%p) created", this);
|
|
}
|
|
~TargetCallbackHandler() {
|
|
MOZ_CLIPBOARD_LOG("TargetCallbackHandler(%p) deleted", this);
|
|
}
|
|
nsTArray<nsCString> mAcceptedFlavorList;
|
|
nsClipboard::HasMatchingFlavorsCallbackWithMap mCallback;
|
|
};
|
|
|
|
void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
|
|
const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
|
|
nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) {
|
|
// Thin adapter over the richer internal variant: callers of the
|
|
// base-class signature don't need the WebCustomFormatMap, so we forward
|
|
// their callback after dropping the map argument.
|
|
AsyncHasNativeClipboardDataMatchingFlavorsWithMap(
|
|
aFlavorList, aWhichClipboard,
|
|
[callback = std::move(aCallback)](
|
|
mozilla::Result<nsTArray<nsCString>, nsresult> aResultOrError,
|
|
mozilla::Maybe<mozilla::widget::WebCustomFormatMap>
|
|
/* aCachedMap */) mutable { callback(std::move(aResultOrError)); });
|
|
}
|
|
|
|
void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavorsWithMap(
|
|
const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard,
|
|
nsClipboard::HasMatchingFlavorsCallbackWithMap&& 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<TargetCallbackHandler> handler(
|
|
static_cast<TargetCallbackHandler*>(aData));
|
|
|
|
if (gtk_selection_data_get_length(aSelection) > 0) {
|
|
GdkAtom* targets = nullptr;
|
|
gint targetsNum = 0;
|
|
gtk_selection_data_get_targets(aSelection, &targets, &targetsNum);
|
|
|
|
if (targetsNum > 0) {
|
|
#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<gchar> 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
|
|
|
|
// Is the web custom format map atom published? Used below to
|
|
// decide whether web-related flavors need a chained async fetch
|
|
// of the map JSON.
|
|
GdkAtom webMapAtom =
|
|
gdk_atom_intern(kWebCustomFormatMapTarget, FALSE);
|
|
bool mapAtomPresent = false;
|
|
for (int i = 0; i < targetsNum; i++) {
|
|
if (targets[i] == webMapAtom) {
|
|
mapAtomPresent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
nsTArray<nsCString> results;
|
|
bool needsMap = false;
|
|
for (auto& flavor : handler->mAcceptedFlavorList) {
|
|
// When the map atom is on the clipboard, defer web-related
|
|
// flavors so the chained async fetch decides them based on
|
|
// the map JSON contents.
|
|
if (mapAtomPresent &&
|
|
(flavor.EqualsLiteral(kWebCustomFormatMapType) ||
|
|
StringBeginsWith(
|
|
flavor, nsLiteralCString(kWebCustomFormatPrefix)))) {
|
|
needsMap = true;
|
|
continue;
|
|
}
|
|
if (flavor.EqualsLiteral(kTextMime) &&
|
|
gtk_targets_include_text(targets, targetsNum)) {
|
|
results.AppendElement(flavor);
|
|
continue;
|
|
}
|
|
for (int i = 0; i < targetsNum; i++) {
|
|
if (FlavorMatchesTarget(flavor, targets[i])) {
|
|
results.AppendElement(flavor);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (needsMap) {
|
|
// aClipboard came from gtk_clipboard_get(GetSelectionAtom(...)),
|
|
// so it must round-trip back to a known ClipboardType.
|
|
mozilla::Maybe<nsIClipboard::ClipboardType> whichClipboard =
|
|
GetGeckoClipboardType(aClipboard);
|
|
MOZ_ASSERT(whichClipboard.isSome());
|
|
AsyncGetWebCustomFormatMapImpl(
|
|
*whichClipboard,
|
|
[handler = std::move(handler), results = std::move(results)](
|
|
mozilla::widget::WebCustomFormatMap&& map) mutable {
|
|
for (const auto& flavor : handler->mAcceptedFlavorList) {
|
|
if (flavor.EqualsLiteral(kWebCustomFormatMapType)) {
|
|
for (const auto& essence : map.Keys()) {
|
|
results.AppendElement(
|
|
nsLiteralCString(kWebCustomFormatPrefix) +
|
|
essence);
|
|
}
|
|
} else if (StringBeginsWith(
|
|
flavor, nsLiteralCString(
|
|
kWebCustomFormatPrefix))) {
|
|
nsDependentCSubstring essence(
|
|
Substring(flavor, strlen(kWebCustomFormatPrefix)));
|
|
if (map.Lookup(essence)) {
|
|
results.AppendElement(flavor);
|
|
}
|
|
}
|
|
}
|
|
// Pass the fetched map back so a chained read path
|
|
// (e.g. AsyncGetNativeClipboardData for a web flavor)
|
|
// can reuse it instead of fetching it again.
|
|
handler->mCallback(std::move(results),
|
|
mozilla::Some(std::move(map)));
|
|
});
|
|
return;
|
|
}
|
|
|
|
handler->mCallback(std::move(results), mozilla::Nothing());
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If TARGETS is not available, fallback to checking for text data
|
|
// directly, as the clipboard owner might not set TARGETS properly, but
|
|
// the text is still available.
|
|
MOZ_CLIPBOARD_LOG(" no targets found\n");
|
|
for (auto& flavor : handler->mAcceptedFlavorList) {
|
|
if (flavor.EqualsLiteral(kTextMime)) {
|
|
MOZ_CLIPBOARD_LOG(" try text data\n");
|
|
gtk_clipboard_request_text(
|
|
aClipboard,
|
|
[](GtkClipboard* aClipboard, const gchar* aText,
|
|
gpointer aData) -> void {
|
|
MOZ_CLIPBOARD_LOG(
|
|
"gtk_clipboard_request_text async handler (%p)", aData);
|
|
UniquePtr<TargetCallbackHandler> handler(
|
|
static_cast<TargetCallbackHandler*>(aData));
|
|
|
|
nsTArray<nsCString> results;
|
|
if (aText) {
|
|
results.AppendElement(kTextMime);
|
|
}
|
|
handler->mCallback(std::move(results), mozilla::Nothing());
|
|
},
|
|
handler.release());
|
|
return;
|
|
}
|
|
}
|
|
|
|
handler->mCallback(nsTArray<nsCString>{}, mozilla::Nothing());
|
|
},
|
|
new TargetCallbackHandler(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<nsITransferable> 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<nsISupports> item;
|
|
|
|
GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
|
|
MOZ_CLIPBOARD_LOG(" selection target %s\n",
|
|
GUniquePtr<gchar>(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<nsISupportsString> 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<const guchar*>(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<nsISupports> imageItem;
|
|
nsCOMPtr<imgIContainer> image;
|
|
for (auto imageMimeType : imageMimeTypes) {
|
|
rv = trans->GetTransferData(imageMimeType, getter_AddRefs(imageItem));
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_CLIPBOARD_LOG(" %s is missing at GetTransferData()\n",
|
|
imageMimeType);
|
|
continue;
|
|
}
|
|
|
|
image = do_QueryInterface(imageItem);
|
|
if (image) {
|
|
MOZ_CLIPBOARD_LOG(" %s is available at GetTransferData()\n",
|
|
imageMimeType);
|
|
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<GdkPixbuf> 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<gchar>(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<nsISupportsString> 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<gchar>(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<nsIFile> file = do_QueryInterface(item);
|
|
if (!file) {
|
|
MOZ_CLIPBOARD_LOG(" failed to get nsIFile interface!");
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> 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;
|
|
}
|
|
|
|
if (!StaticPrefs::clipboard_copyPrivateDataToClipboardCloudOrHistory() &&
|
|
selectionTarget == gdk_atom_intern(kKDEPasswordManagerHintMime, FALSE)) {
|
|
if (!trans->GetIsPrivateData()) {
|
|
MOZ_CLIPBOARD_LOG(
|
|
" requested %s, but the data isn't actually private!\n",
|
|
kKDEPasswordManagerHintMime);
|
|
return;
|
|
}
|
|
|
|
static const char* kSecret = "secret";
|
|
MOZ_CLIPBOARD_LOG(" Setting data to '%s' for %s\n", kSecret,
|
|
kKDEPasswordManagerHintMime);
|
|
gtk_selection_data_set(aSelectionData, selectionTarget, 8,
|
|
(const guchar*)kSecret, strlen(kSecret));
|
|
return;
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(" Try if we have anything at GetTransferData() for %s\n",
|
|
GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
|
|
|
|
// Try to match up the selection data target to something our
|
|
// transferable provides.
|
|
GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
|
|
if (!target_name) {
|
|
MOZ_CLIPBOARD_LOG(" Failed to get target name!\n");
|
|
return;
|
|
}
|
|
|
|
// Web custom format support. The map target is a synthetic flavor that
|
|
// publishes the essence -> target map as JSON. Each per-target serves the
|
|
// raw bytes of one "web foo/bar" entry stored in the transferable. Target
|
|
// names come from the W3C clipboard-apis spec, Appendix A.
|
|
const mozilla::widget::WebCustomFormatMap& webCustomFormatMap =
|
|
WebCustomFormatMapFor(whichClipboard);
|
|
if (!strcmp(target_name.get(), kWebCustomFormatMapTarget)) {
|
|
nsAutoCString json;
|
|
mozilla::widget::WebCustomFormatMapToJSON(webCustomFormatMap, json);
|
|
MOZ_CLIPBOARD_LOG(" Setting %zd bytes of web custom format map JSON\n",
|
|
json.Length());
|
|
gtk_selection_data_set(aSelectionData, selectionTarget, 8,
|
|
reinterpret_cast<const guchar*>(json.get()),
|
|
json.Length());
|
|
return;
|
|
}
|
|
if (!strncmp(target_name.get(), kWebCustomFormatTargetPrefix,
|
|
strlen(kWebCustomFormatTargetPrefix))) {
|
|
for (const auto& entry : webCustomFormatMap) {
|
|
if (!strcmp(entry.GetData().get(), target_name.get())) {
|
|
nsAutoCString webFlavor(kWebCustomFormatPrefix);
|
|
webFlavor.Append(entry.GetKey());
|
|
nsCOMPtr<nsISupports> targetData;
|
|
if (NS_FAILED(trans->GetTransferData(webFlavor.get(),
|
|
getter_AddRefs(targetData))) ||
|
|
!targetData) {
|
|
MOZ_CLIPBOARD_LOG(" Failed to get %s data\n", webFlavor.get());
|
|
return;
|
|
}
|
|
nsCOMPtr<nsISupportsCString> cstr = do_QueryInterface(targetData);
|
|
if (!cstr) {
|
|
MOZ_CLIPBOARD_LOG(" %s isn't an nsISupportsCString\n",
|
|
webFlavor.get());
|
|
return;
|
|
}
|
|
nsAutoCString bytes;
|
|
cstr->GetData(bytes);
|
|
MOZ_CLIPBOARD_LOG(" Setting %zd bytes for %s (target %s)\n",
|
|
bytes.Length(), webFlavor.get(), target_name.get());
|
|
gtk_selection_data_set(aSelectionData, selectionTarget, 8,
|
|
reinterpret_cast<const guchar*>(bytes.get()),
|
|
bytes.Length());
|
|
return;
|
|
}
|
|
}
|
|
MOZ_CLIPBOARD_LOG(" No web custom format mapped to %s\n",
|
|
target_name.get());
|
|
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 (mContext) {
|
|
mContext->ClearCachedTargets(aWhichClipboard);
|
|
}
|
|
}
|
|
|
|
void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
|
|
Maybe<ClipboardType> 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<ClipboardType> whichClipboard = GetGeckoClipboardType(aGtkClipboard);
|
|
if (whichClipboard.isNothing()) {
|
|
return;
|
|
}
|
|
|
|
bool shouldIncrementSequence = !IsOurOwnerClipboardChange(*whichClipboard);
|
|
MarkNextOwnerClipboardChange(*whichClipboard, /* aOurChange */ false);
|
|
|
|
if (shouldIncrementSequence) {
|
|
IncrementSequenceNumber(*whichClipboard);
|
|
}
|
|
|
|
MOZ_CLIPBOARD_LOG(
|
|
"nsClipboard::OwnerChangedEvent (%s) %s sequence %d\n",
|
|
*whichClipboard == kSelectionClipboard ? "primary" : "clipboard",
|
|
shouldIncrementSequence ? "external change" : "internal change",
|
|
*whichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
|
|
: 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<nsClipboard*>(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<nsClipboard*>(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<nsClipboard*>(aUserData);
|
|
clipboard->OwnerChangedEvent(aGtkClipboard, aEvent);
|
|
}
|