Bug 1753067 - Cleanup handling of clipboard data lifetime. r=stransky

This is a bit harder to mess up, and a bit simpler to read too.

Differential Revision: https://phabricator.services.mozilla.com/D137549
This commit is contained in:
Emilio Cobos Álvarez 2022-02-02 14:55:12 +00:00
parent e9c325b945
commit 85f8575823
7 changed files with 324 additions and 353 deletions

26
widget/gtk/GUniquePtr.h Normal file
View File

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
#ifndef GUniquePtr_h_
#define GUniquePtr_h_
// Provides GUniquePtr to g_free a given pointer.
#include <glib.h>
#include "mozilla/UniquePtr.h"
namespace mozilla {
struct GFreeDeleter {
constexpr GFreeDeleter() = default;
void operator()(void* aPtr) const { g_free(aPtr); }
};
template <typename T>
using GUniquePtr = UniquePtr<T, GFreeDeleter>;
} // namespace mozilla
#endif

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- 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
@ -66,12 +66,42 @@ void clipboard_get_cb(GtkClipboard* aGtkClipboard,
// Callback when someone asks us to clear a clipboard
void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
static bool ConvertHTMLtoUCS2(Span<const char> aData,
nsCString& charset, char16_t** unicodeData,
int32_t& outUnicodeLen);
static bool GetHTMLCharset(const char* data, int32_t dataLength,
nsCString& str);
static bool GetHTMLCharset(Span<const char> aData, nsCString& str);
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(ClipboardTargets aTargets) {
mLength = aTargets.mCount;
mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
}
ClipboardTargets ClipboardData::ExtractTargets() {
GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
uint32_t length = std::exchange(mLength, 0);
return ClipboardTargets{std::move(targets), length};
}
GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
@ -269,17 +299,14 @@ bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
nsTArray<nsCString>& aFlavors) {
LOGCLIP("nsClipboard::FilterImportedFlavors");
int targetNums;
GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
auto releaseTargets = MakeScopeExit([&] { g_free(targets); });
if (!targets) {
auto targets = mContext->GetTargets(aWhichClipboard);
if (!targets.mTargets) {
LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
return true;
}
for (int i = 0; i < targetNums; i++) {
gchar* atom_name = gdk_atom_name(targets[i]);
for (const auto& atom : targets.AsSpan()) {
gchar* atom_name = gdk_atom_name(atom);
if (!atom_name) {
continue;
}
@ -305,8 +332,8 @@ bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
// So make sure we offer only types we have at clipboard.
nsTArray<nsCString> clipboardFlavors;
for (int i = 0; i < targetNums; i++) {
gchar* atom_name = gdk_atom_name(targets[i]);
for (const auto& atom : targets.AsSpan()) {
gchar* atom_name = gdk_atom_name(atom);
if (!atom_name) {
continue;
}
@ -370,22 +397,18 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
uint32_t clipboardDataLength;
const char* clipboardData = mContext->GetClipboardData(
flavorStr.get(), aWhichClipboard, &clipboardDataLength);
auto clipboardData =
mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
if (!clipboardData) {
LOGCLIP(" %s type is missing\n", flavorStr.get());
continue;
}
nsCOMPtr<nsIInputStream> byteStream;
NS_NewByteInputStream(getter_AddRefs(byteStream),
Span(clipboardData, clipboardDataLength),
NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
NS_ASSIGNMENT_COPY);
aTransferable->SetTransferData(flavorStr.get(), byteStream);
LOGCLIP(" got %s MIME data\n", flavorStr.get());
mContext->ReleaseClipboardData(&clipboardData);
return NS_OK;
}
@ -394,7 +417,7 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
if (flavorStr.EqualsLiteral(kUnicodeMime)) {
LOGCLIP(" Getting unicode %s MIME clipboard data\n", flavorStr.get());
const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
if (!clipboardData) {
LOGCLIP(" failed to get unicode data\n");
// If the type was text/unicode and we couldn't get
@ -404,31 +427,26 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
}
// Convert utf-8 into our unicode format.
NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
uint32_t unicodeDataLength = ucs2string.Length() * 2;
SetTransferableData(aTransferable, flavorStr, unicodeData,
unicodeDataLength);
free((void*)unicodeData);
NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
SetTransferableData(aTransferable, flavorStr,
(const char*)ucs2string.BeginReading(),
ucs2string.Length() * 2);
LOGCLIP(" got unicode data, length %zd\n", ucs2string.Length());
mContext->ReleaseClipboardData(&clipboardData);
return NS_OK;
}
if (flavorStr.EqualsLiteral(kFileMime)) {
LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
uint32_t clipboardDataLength;
const char* clipboardData = mContext->GetClipboardData(
kURIListMime, aWhichClipboard, &clipboardDataLength);
auto clipboardData =
mContext->GetClipboardData(kURIListMime, aWhichClipboard);
if (!clipboardData) {
LOGCLIP(" text/uri-list type is missing\n");
continue;
}
nsDependentCSubstring data(clipboardData, clipboardDataLength);
nsDependentCSubstring data(clipboardData.AsSpan());
nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(data);
if (!uris.IsEmpty()) {
nsCOMPtr<nsIURI> fileURI;
@ -442,16 +460,13 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
}
}
}
mContext->ReleaseClipboardData(&clipboardData);
return NS_OK;
}
LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
uint32_t clipboardDataLength;
const char* clipboardData = mContext->GetClipboardData(
flavorStr.get(), aWhichClipboard, &clipboardDataLength);
auto clipboardData =
mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
#ifdef MOZ_LOGGING
if (!clipboardData) {
@ -468,15 +483,14 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
int32_t htmlBodyLen = 0;
// Convert text/html into our unicode format
nsAutoCString charset;
if (!GetHTMLCharset(clipboardData, clipboardDataLength, charset)) {
if (!GetHTMLCharset(clipboardData.AsSpan(), charset)) {
// Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
charset.AssignLiteral("utf-8");
}
if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
if (!ConvertHTMLtoUCS2(clipboardData.AsSpan(), charset,
&htmlBody, htmlBodyLen)) {
LOGCLIP(" failed to convert text/html to UCS2.\n");
mContext->ReleaseClipboardData(&clipboardData);
continue;
}
@ -484,11 +498,9 @@ nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
htmlBodyLen * 2);
free(htmlBody);
} else {
SetTransferableData(aTransferable, flavorStr, clipboardData,
clipboardDataLength);
SetTransferableData(aTransferable, flavorStr, clipboardData.mData.get(),
clipboardData.mLength);
}
mContext->ReleaseClipboardData(&clipboardData);
return NS_OK;
}
}
@ -548,9 +560,7 @@ nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
return NS_ERROR_FAILURE;
}
int targetNums;
GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
auto releaseTargets = MakeScopeExit([&] { g_free(targets); });
auto targets = mContext->GetTargets(aWhichClipboard);
if (!targets) {
LOGCLIP(" no targes at clipboard (null)\n");
@ -558,18 +568,20 @@ nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
}
#ifdef MOZ_LOGGING
LOGCLIP(" Clipboard content (target nums %d):\n", targetNums);
for (int32_t j = 0; j < targetNums; j++) {
gchar* atom_name = gdk_atom_name(targets[j]);
if (!atom_name) {
LOGCLIP(" failed to get MIME\n");
continue;
if (LOGCLIP_ENABLED()) {
LOGCLIP(" Clipboard content (target nums %d):\n", targets.mCount);
for (const auto& target : targets.AsSpan()) {
gchar* atom_name = gdk_atom_name(target);
if (!atom_name) {
LOGCLIP(" failed to get MIME\n");
continue;
}
LOGCLIP(" MIME %s\n", atom_name);
}
LOGCLIP(" Asking for content:\n");
for (auto& flavor : aFlavorList) {
LOGCLIP(" MIME %s\n", flavor.get());
}
LOGCLIP(" MIME %s\n", atom_name);
}
LOGCLIP(" Asking for content:\n");
for (auto& flavor : aFlavorList) {
LOGCLIP(" MIME %s\n", flavor.get());
}
#endif
@ -578,15 +590,17 @@ nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
for (auto& flavor : aFlavorList) {
// We special case text/unicode here.
if (flavor.EqualsLiteral(kUnicodeMime) &&
gtk_targets_include_text(targets, targetNums)) {
gtk_targets_include_text(targets.mTargets.get(), targets.mCount)) {
*_retval = true;
LOGCLIP(" has kUnicodeMime\n");
break;
}
for (int32_t j = 0; j < targetNums; j++) {
gchar* atom_name = gdk_atom_name(targets[j]);
if (!atom_name) continue;
for (const auto& target : targets.AsSpan()) {
gchar* atom_name = gdk_atom_name(target);
if (!atom_name) {
continue;
}
if (flavor.Equals(atom_name)) {
*_retval = true;
@ -870,13 +884,13 @@ void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
* body : pass to Mozilla
* bodyLength: pass to Mozilla
*/
bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
bool ConvertHTMLtoUCS2(Span<const char> aData, nsCString& charset,
char16_t** unicodeData, int32_t& outUnicodeLen) {
if (charset.EqualsLiteral("UTF-16")) { // current mozilla
outUnicodeLen = (dataLength / 2) - 1;
outUnicodeLen = (aData.Length() / 2) - 1;
*unicodeData = reinterpret_cast<char16_t*>(
moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
memcpy(*unicodeData, data + sizeof(char16_t),
memcpy(*unicodeData, aData.data() + sizeof(char16_t),
outUnicodeLen * sizeof(char16_t));
(*unicodeData)[outUnicodeLen] = '\0';
return true;
@ -894,17 +908,16 @@ bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
return false;
}
auto dataSpan = Span(data, dataLength);
// Remove kHTMLMarkupPrefix again, it won't necessarily cause any
// issues, but might confuse other users.
const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
if (dataSpan.Length() >= prefixLen &&
Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
dataSpan = dataSpan.From(prefixLen);
if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
.EqualsLiteral(kHTMLMarkupPrefix)) {
aData = aData.From(prefixLen);
}
auto decoder = encoding->NewDecoder();
CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(dataSpan.Length());
CheckedInt<size_t> needed = decoder->MaxUTF16BufferLength(aData.Length());
if (!needed.isValid() || needed.value() > INT32_MAX) {
outUnicodeLen = 0;
return false;
@ -918,9 +931,9 @@ bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
size_t read;
size_t written;
std::tie(result, read, written, std::ignore) = decoder->DecodeToUTF16(
AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
AsBytes(aData), Span(*unicodeData, needed.value()), true);
MOZ_ASSERT(result == kInputEmpty);
MOZ_ASSERT(read == size_t(dataSpan.Length()));
MOZ_ASSERT(read == size_t(aData.Length()));
MOZ_ASSERT(written <= needed.value());
outUnicodeLen = written;
// null terminate.
@ -937,16 +950,19 @@ bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
* 2. "UNKNOWN": mozilla can't detect what encode it use
* 3. other: "text/html" with other charset than utf-16
*/
bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
bool GetHTMLCharset(Span<const char> aData, nsCString& str) {
// if detect "FFFE" or "FEFF", assume UTF-16
char16_t* beginChar = (char16_t*)data;
if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
str.AssignLiteral("UTF-16");
LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
return true;
{
char16_t* beginChar = (char16_t*)aData.data();
if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
str.AssignLiteral("UTF-16");
LOGCLIP("GetHTMLCharset: Charset of HTML is UTF-16\n");
return true;
}
}
// no "FFFE" and "FEFF", assume ASCII first to find "charset" info
const nsDependentCSubstring htmlStr(data, dataLength);
const nsDependentCSubstring htmlStr(aData);
nsACString::const_iterator start, end;
htmlStr.BeginReading(start);
htmlStr.EndReading(end);

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- 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
@ -9,8 +9,11 @@
#define __nsClipboard_h_
#include "mozilla/UniquePtr.h"
#include "mozilla/Span.h"
#include "nsIClipboard.h"
#include "nsIObserver.h"
#include "nsCOMPtr.h"
#include "GUniquePtr.h"
#include <gtk/gtk.h>
#ifdef MOZ_LOGGING
@ -20,11 +23,36 @@
extern mozilla::LazyLogModule gClipboardLog;
# define LOGCLIP(...) \
MOZ_LOG(gClipboardLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
# define LOGCLIP_ENABLED() \
MOZ_LOG_TEST(gClipboardLog, mozilla::LogLevel::Debug)
#else
# define LOGCLIP(...)
# define LOGCLIP_ENABLED() false
#endif /* MOZ_LOGGING */
enum ClipboardDataType { CLIPBOARD_DATA, CLIPBOARD_TEXT, CLIPBOARD_TARGETS };
struct ClipboardTargets {
mozilla::GUniquePtr<GdkAtom> mTargets;
uint32_t mCount = 0;
mozilla::Span<GdkAtom> AsSpan() const { return {mTargets.get(), mCount}; }
explicit operator bool() const { return bool(mTargets); }
};
struct ClipboardData {
mozilla::GUniquePtr<char> mData;
uint32_t mLength = 0;
void SetData(mozilla::Span<const uint8_t>);
void SetText(mozilla::Span<const char>);
void SetTargets(ClipboardTargets);
ClipboardTargets ExtractTargets();
mozilla::Span<char> AsSpan() const { return {mData.get(), mLength}; }
explicit operator bool() const { return bool(mData); }
};
enum class ClipboardDataType { Data, Text, Targets };
class nsRetrievalContext {
public:
@ -32,17 +60,15 @@ class nsRetrievalContext {
// main thread only.
NS_INLINE_DECL_REFCOUNTING(nsRetrievalContext)
// Get actual clipboard content (GetClipboardData/GetClipboardText)
// which has to be released by ReleaseClipboardData().
virtual const char* GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard,
uint32_t* aContentLength) = 0;
virtual const char* GetClipboardText(int32_t aWhichClipboard) = 0;
virtual void ReleaseClipboardData(const char** aClipboardData) = 0;
// Get actual clipboard content (GetClipboardData/GetClipboardText).
virtual ClipboardData GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard) = 0;
virtual mozilla::GUniquePtr<char> GetClipboardText(
int32_t aWhichClipboard) = 0;
// Get data mime types which can be obtained from clipboard.
// The returned array has to be released by g_free().
virtual GdkAtom* GetTargets(int32_t aWhichClipboard, int* aTargetNum) = 0;
// Get data mime types which can be obtained from clipboard. The returned
// array has to be released by g_free().
virtual ClipboardTargets GetTargets(int32_t aWhichClipboard) = 0;
virtual bool HasSelectionSupport(void) = 0;

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- 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
@ -28,12 +28,8 @@
using namespace mozilla;
using namespace mozilla::widget;
nsRetrievalContextWayland::nsRetrievalContextWayland(void)
: mClipboardRequestNumber(0),
mClipboardDataReceived(),
mClipboardData(nullptr),
mClipboardDataLength(0),
mMutex("nsRetrievalContextWayland") {}
nsRetrievalContextWayland::nsRetrievalContextWayland()
: mMutex("nsRetrievalContextWayland") {}
struct AsyncClipboardData {
AsyncClipboardData(ClipboardDataType aDataType, int aClipboardRequestNumber,
@ -50,7 +46,7 @@ static void wayland_clipboard_contents_received_async(
GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
LOGCLIP("wayland_clipboard_contents_received_async() selection_data = %p\n",
selection_data);
AsyncClipboardData* fastTrack = static_cast<AsyncClipboardData*>(data);
auto* fastTrack = static_cast<AsyncClipboardData*>(data);
fastTrack->mRetrievalContex->TransferClipboardData(
fastTrack->mDataType, fastTrack->mClipboardRequestNumber, selection_data);
delete fastTrack;
@ -59,7 +55,7 @@ static void wayland_clipboard_contents_received_async(
static void wayland_clipboard_text_received(GtkClipboard* clipboard,
const gchar* text, gpointer data) {
LOGCLIP("wayland_clipboard_text_received() text = %p\n", text);
AsyncClipboardData* fastTrack = static_cast<AsyncClipboardData*>(data);
auto* fastTrack = static_cast<AsyncClipboardData*>(data);
fastTrack->mRetrievalContex->TransferClipboardData(
fastTrack->mDataType, fastTrack->mClipboardRequestNumber, (void*)text);
delete fastTrack;
@ -79,17 +75,18 @@ void nsRetrievalContextWayland::TransferClipboardData(
}
LOGCLIP(" request number matches\n");
MOZ_RELEASE_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
MOZ_RELEASE_ASSERT(mClipboardData.isNothing(),
"Clipboard contains old data?");
int dataLength = 0;
if (aDataType == CLIPBOARD_TARGETS || aDataType == CLIPBOARD_DATA) {
gint dataLength = 0;
if (aDataType == ClipboardDataType::Targets ||
aDataType == ClipboardDataType::Data) {
dataLength = gtk_selection_data_get_length((GtkSelectionData*)aData);
} else {
dataLength = aData ? strlen((const char*)aData) : 0;
}
mClipboardDataReceived = true;
mClipboardData = Some(ClipboardData());
// Negative size means no data or data error.
if (dataLength <= 0) {
@ -98,119 +95,81 @@ void nsRetrievalContextWayland::TransferClipboardData(
}
switch (aDataType) {
case CLIPBOARD_TARGETS: {
case ClipboardDataType::Targets: {
LOGCLIP(" getting %d bytes of clipboard targets.\n", dataLength);
gint n_targets = 0;
GdkAtom* targets = nullptr;
if (!gtk_selection_data_get_targets((GtkSelectionData*)aData, &targets,
&n_targets) ||
!n_targets) {
// We failed to get targes
// We failed to get targets
return;
}
mClipboardData = reinterpret_cast<char*>(targets);
mClipboardDataLength = n_targets;
mClipboardData->SetTargets(
ClipboardTargets{GUniquePtr<GdkAtom>(targets), uint32_t(n_targets)});
break;
}
case CLIPBOARD_TEXT: {
case ClipboardDataType::Text: {
LOGCLIP(" getting %d bytes of text.\n", dataLength);
mClipboardDataLength = dataLength;
mClipboardData = reinterpret_cast<char*>(
g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
memcpy(mClipboardData, aData, sizeof(char) * mClipboardDataLength);
mClipboardData[mClipboardDataLength] = '\0';
LOGCLIP(" done, mClipboardData = %p\n", mClipboardData);
mClipboardData->SetText(
Span(static_cast<const char*>(aData), dataLength));
LOGCLIP(" done, mClipboardData = %p\n", mClipboardData->mData.get());
break;
}
case CLIPBOARD_DATA: {
case ClipboardDataType::Data: {
LOGCLIP(" getting %d bytes of data.\n", dataLength);
mClipboardDataLength = dataLength;
mClipboardData = reinterpret_cast<char*>(
g_malloc(sizeof(char) * mClipboardDataLength));
memcpy(mClipboardData,
gtk_selection_data_get_data((GtkSelectionData*)aData),
sizeof(char) * mClipboardDataLength);
LOGCLIP(" done, mClipboardData = %p\n", mClipboardData);
mClipboardData->SetData(Span(
gtk_selection_data_get_data((GtkSelectionData*)aData), dataLength));
LOGCLIP(" done, mClipboardData = %p\n", mClipboardData->mData.get());
break;
}
}
}
GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
int* aTargetNum) {
ClipboardTargets nsRetrievalContextWayland::GetTargets(
int32_t aWhichClipboard) {
LOGCLIP("nsRetrievalContextWayland::GetTargets()\n");
if (!mMutex.TryLock()) {
LOGCLIP(" nsRetrievalContextWayland is already used!\n");
*aTargetNum = 0;
return nullptr;
return {};
}
auto releaseLock = MakeScopeExit([&] { mMutex.Unlock(); });
MOZ_RELEASE_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
"Clipboard contains old data?");
int clipboardRequest = PrepareNewClipboardRequest();
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
mClipboardDataReceived = false;
mClipboardRequestNumber++;
gtk_clipboard_request_contents(
gtk_clipboard_get(selection), gdk_atom_intern("TARGETS", FALSE),
wayland_clipboard_contents_received_async,
new AsyncClipboardData(CLIPBOARD_TARGETS, mClipboardRequestNumber, this));
new AsyncClipboardData(ClipboardDataType::Targets, clipboardRequest,
this));
if (!WaitForClipboardContent()) {
*aTargetNum = 0;
return nullptr;
}
// mClipboardDataLength is only signed integer, see
// nsRetrievalContextWayland::TransferClipboardData()
*aTargetNum = (int)mClipboardDataLength;
GdkAtom* targets = static_cast<GdkAtom*>((void*)mClipboardData);
mClipboardDataLength = 0;
mClipboardData = nullptr;
return targets;
return WaitForClipboardContent().ExtractTargets();
}
const char* nsRetrievalContextWayland::GetClipboardData(
const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
ClipboardData nsRetrievalContextWayland::GetClipboardData(
const char* aMimeType, int32_t aWhichClipboard) {
LOGCLIP("nsRetrievalContextWayland::GetClipboardData() mime %s\n", aMimeType);
if (!mMutex.TryLock()) {
LOGCLIP(" nsRetrievalContextWayland is already used!\n");
*aContentLength = 0;
return nullptr;
return {};
}
auto releaseLock = MakeScopeExit([&] { mMutex.Unlock(); });
MOZ_RELEASE_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
"Clipboard contains old data?");
int clipboardRequest = PrepareNewClipboardRequest();
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
mClipboardDataReceived = false;
mClipboardRequestNumber++;
gtk_clipboard_request_contents(
gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
wayland_clipboard_contents_received_async,
new AsyncClipboardData(CLIPBOARD_DATA, mClipboardRequestNumber, this));
new AsyncClipboardData(ClipboardDataType::Data, clipboardRequest, this));
if (!WaitForClipboardContent()) {
*aContentLength = 0;
return nullptr;
}
*aContentLength = mClipboardDataLength;
const char* data = mClipboardData;
mClipboardDataLength = 0;
mClipboardData = nullptr;
return data;
return WaitForClipboardContent();
}
const char* nsRetrievalContextWayland::GetClipboardText(
GUniquePtr<char> nsRetrievalContextWayland::GetClipboardText(
int32_t aWhichClipboard) {
GdkAtom selection = GetSelectionAtom(aWhichClipboard);
@ -223,32 +182,26 @@ const char* nsRetrievalContextWayland::GetClipboardText(
}
auto releaseLock = MakeScopeExit([&] { mMutex.Unlock(); });
MOZ_RELEASE_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
"Clipboard contains old data?");
mClipboardDataReceived = false;
mClipboardRequestNumber++;
int clipboardRequest = PrepareNewClipboardRequest();
gtk_clipboard_request_text(
gtk_clipboard_get(selection), wayland_clipboard_text_received,
new AsyncClipboardData(CLIPBOARD_TEXT, mClipboardRequestNumber, this));
new AsyncClipboardData(ClipboardDataType::Text, clipboardRequest, this));
if (!WaitForClipboardContent()) {
return nullptr;
}
const char* data = mClipboardData;
mClipboardDataLength = 0;
mClipboardData = nullptr;
return data;
return WaitForClipboardContent().mData;
}
bool nsRetrievalContextWayland::WaitForClipboardContent() {
int nsRetrievalContextWayland::PrepareNewClipboardRequest() {
MOZ_DIAGNOSTIC_ASSERT(mClipboardData.isNothing(),
"Clipboard contains old data?");
mClipboardData.reset();
return ++mClipboardRequestNumber;
}
ClipboardData nsRetrievalContextWayland::WaitForClipboardContent() {
int iteration = 1;
PRTime entryTime = PR_Now();
while (!mClipboardDataReceived) {
while (mClipboardData.isNothing()) {
if (iteration++ > kClipboardFastIterationNum) {
/* sleep for 10 ms/iteration */
PR_Sleep(PR_MillisecondsToInterval(10));
@ -262,15 +215,8 @@ bool nsRetrievalContextWayland::WaitForClipboardContent() {
gtk_main_iteration();
}
if (!mClipboardDataReceived) {
mClipboardDataLength = 0;
mClipboardData = nullptr;
if (mClipboardData.isNothing()) {
return {};
}
return mClipboardDataReceived;
}
void nsRetrievalContextWayland::ReleaseClipboardData(
const char** aClipboardData) {
g_free((void*)*aClipboardData);
*aClipboardData = nullptr;
return mClipboardData.extract();
}

View File

@ -13,27 +13,24 @@
#include <nsTArray.h>
#include "mozilla/Mutex.h"
#include "mozilla/Maybe.h"
#include "nsIThread.h"
#include "mozilla/UniquePtr.h"
#include "nsClipboard.h"
#include "nsWaylandDisplay.h"
class nsRetrievalContextWayland : public nsRetrievalContext {
class nsRetrievalContextWayland final : public nsRetrievalContext {
public:
nsRetrievalContextWayland();
// Successful call of GetClipboardData()/GetClipboardText() needs to be paired
// with ReleaseClipboardData().
virtual const char* GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard,
uint32_t* aContentLength) override;
virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
virtual void ReleaseClipboardData(const char** aClipboardData) override;
ClipboardData GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard) override;
mozilla::GUniquePtr<char> GetClipboardText(int32_t aWhichClipboard) override;
// GetTargets() uses clipboard data internally so it can't be used between
// GetClipboardData()/GetClipboardText() and ReleaseClipboardData() calls.
virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
int* aTargetNum) override;
ClipboardTargets GetTargets(int32_t aWhichClipboard) override;
void TransferClipboardData(ClipboardDataType aDataType,
int aClipboardRequestNumber, const void* aData);
@ -41,13 +38,11 @@ class nsRetrievalContextWayland : public nsRetrievalContext {
bool HasSelectionSupport(void) override { return true; }
private:
bool WaitForClipboardContent();
ClipboardData WaitForClipboardContent();
int PrepareNewClipboardRequest();
private:
int mClipboardRequestNumber;
bool mClipboardDataReceived;
char* mClipboardData;
uint32_t mClipboardDataLength;
int mClipboardRequestNumber = 0;
mozilla::Maybe<ClipboardData> mClipboardData;
mozilla::Mutex mMutex;
};

View File

@ -1,5 +1,5 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=4:tabstop=4:
/* -*- 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
@ -40,11 +40,7 @@ bool nsRetrievalContextX11::HasSelectionSupport(void) {
}
nsRetrievalContextX11::nsRetrievalContextX11()
: mState(INITIAL),
mClipboardRequestNumber(0),
mClipboardData(nullptr),
mClipboardDataLength(0),
mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE)) {}
: mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE)) {}
static void DispatchSelectionNotifyEvent(GtkWidget* widget, XEvent* xevent) {
GdkEvent event = {};
@ -99,9 +95,9 @@ static Bool checkEventProc(Display* display, XEvent* event, XPointer arg) {
return X11False;
}
bool nsRetrievalContextX11::WaitForX11Content() {
if (mState == COMPLETED) { // the request completed synchronously
return true;
ClipboardData nsRetrievalContextX11::WaitForX11Content() {
if (mState == State::Completed) { // the request completed synchronously
return mClipboardData.extract();
}
GdkDisplay* gdkDisplay = gdk_display_get_default();
@ -134,8 +130,8 @@ bool nsRetrievalContextX11::WaitForX11Content() {
else
DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
if (mState == COMPLETED) {
return true;
if (mState == State::Completed) {
return mClipboardData.extract();
}
}
@ -149,8 +145,8 @@ bool nsRetrievalContextX11::WaitForX11Content() {
#ifdef DEBUG_CLIPBOARD
printf("exceeded clipboard timeout\n");
#endif
mState = TIMED_OUT;
return false;
mState = State::TimedOut;
return {};
}
// Call this when data has been retrieved.
@ -165,60 +161,57 @@ void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
return;
}
if (mState == INITIAL) {
mState = COMPLETED;
MOZ_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
"We're leaking clipboard data!");
switch (aDataType) {
case CLIPBOARD_TEXT: {
LOGCLIP(" got text data %p\n", aData);
const char* text = static_cast<const char*>(aData);
if (text) {
mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
mClipboardData = g_malloc(mClipboardDataLength);
memcpy(mClipboardData, text, mClipboardDataLength);
}
} break;
case CLIPBOARD_TARGETS: {
const GtkSelectionData* selection =
static_cast<const GtkSelectionData*>(aData);
gint n_targets = 0;
GdkAtom* targets = nullptr;
if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
!n_targets) {
return;
}
LOGCLIP(" got %d targets\n", n_targets);
mClipboardData = targets;
mClipboardDataLength = n_targets;
} break;
case CLIPBOARD_DATA: {
const GtkSelectionData* selection =
static_cast<const GtkSelectionData*>(aData);
gint dataLength = gtk_selection_data_get_length(selection);
const guchar* data = gtk_selection_data_get_data(selection);
#ifdef MOZ_LOGGING
GdkAtom target = gtk_selection_data_get_target(selection);
LOGCLIP(" got data %p len %d MIME %s\n", data, dataLength,
gdk_atom_name(target));
#endif
if (dataLength > 0) {
mClipboardDataLength = dataLength;
mClipboardData = g_malloc(dataLength);
memcpy(mClipboardData, data, dataLength);
}
} break;
}
} else {
if (mState != State::Initial) {
// Already timed out
MOZ_ASSERT(mState == TIMED_OUT);
MOZ_ASSERT(mState == State::TimedOut);
return;
}
mState = State::Completed;
MOZ_ASSERT(!mClipboardData, "We're leaking clipboard data!");
mClipboardData = Some(ClipboardData());
switch (aDataType) {
case ClipboardDataType::Text: {
LOGCLIP(" got text data %p\n", aData);
if (auto* data = static_cast<const char*>(aData)) {
mClipboardData->SetText(Span(data, strlen(data)));
}
} break;
case ClipboardDataType::Targets: {
auto* selection = static_cast<const GtkSelectionData*>(aData);
gint n_targets = 0;
GdkAtom* targets = nullptr;
if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
!n_targets) {
return;
}
LOGCLIP(" got %d targets\n", n_targets);
mClipboardData->SetTargets(
ClipboardTargets{GUniquePtr<GdkAtom>(targets), uint32_t(n_targets)});
} break;
case ClipboardDataType::Data: {
auto* selection = static_cast<const GtkSelectionData*>(aData);
gint len = gtk_selection_data_get_length(selection);
if (len > 0) {
mClipboardData->SetData(
Span(gtk_selection_data_get_data(selection), len));
}
#ifdef MOZ_LOGGING
if (LOGCLIP_ENABLED()) {
GdkAtom target = gtk_selection_data_get_target(selection);
LOGCLIP(" got data %p len %lu MIME %s\n",
mClipboardData->AsSpan().data(),
mClipboardData->AsSpan().Length(),
GUniquePtr<gchar>(gdk_atom_name(target)).get());
}
#endif
} break;
}
}
@ -243,40 +236,41 @@ static void clipboard_text_received(GtkClipboard* clipboard, const gchar* text,
whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
: "clipboard");
ClipboardRequestHandler* handler =
static_cast<ClipboardRequestHandler*>(data);
auto* handler = static_cast<ClipboardRequestHandler*>(data);
handler->Complete(text);
delete handler;
}
bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
GtkClipboard* clipboard,
const char* aMimeType) {
ClipboardData nsRetrievalContextX11::WaitForClipboardData(
ClipboardDataType aDataType, GtkClipboard* clipboard,
const char* aMimeType) {
LOGCLIP("nsRetrievalContextX11::WaitForClipboardData, MIME %s\n", aMimeType);
mState = INITIAL;
NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
mState = State::Initial;
NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
mClipboardData.reset();
// Call ClipboardRequestHandler() with unique clipboard request number.
// The request number pairs gtk_clipboard_request_contents() data request
// with clipboard_contents_received() callback where the data
// is provided by Gtk.
mClipboardRequestNumber++;
ClipboardRequestHandler* handler =
new ClipboardRequestHandler(this, aDataType, mClipboardRequestNumber);
switch (aDataType) {
case CLIPBOARD_DATA:
case ClipboardDataType::Data:
LOGCLIP(" getting DATA MIME %s\n", aMimeType);
gtk_clipboard_request_contents(clipboard,
gdk_atom_intern(aMimeType, FALSE),
clipboard_contents_received, handler);
break;
case CLIPBOARD_TEXT:
case ClipboardDataType::Text:
LOGCLIP(" getting TEXT\n");
gtk_clipboard_request_text(clipboard, clipboard_text_received, handler);
break;
case CLIPBOARD_TARGETS:
case ClipboardDataType::Targets:
LOGCLIP(" getting TARGETS\n");
gtk_clipboard_request_contents(clipboard, mTargetMIMEType,
clipboard_contents_received, handler);
@ -286,8 +280,7 @@ bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
return WaitForX11Content();
}
GdkAtom* nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard,
int* aTargetNums) {
ClipboardTargets nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard) {
LOGCLIP("nsRetrievalContextX11::GetTargets(%s)\n",
aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
: "clipboard");
@ -295,61 +288,31 @@ GdkAtom* nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard,
GtkClipboard* clipboard =
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard)) {
LOGCLIP(" WaitForClipboardData() failed!\n");
return nullptr;
}
*aTargetNums = mClipboardDataLength;
GdkAtom* targets = static_cast<GdkAtom*>(mClipboardData);
mClipboardData = nullptr;
mClipboardDataLength = 0;
LOGCLIP(" returned %d targets\n", *aTargetNums);
return targets;
return WaitForClipboardData(ClipboardDataType::Targets, clipboard)
.ExtractTargets();
}
const char* nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard,
uint32_t* aContentLength) {
ClipboardData nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard) {
LOGCLIP("nsRetrievalContextX11::GetClipboardData(%s) MIME %s\n",
aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
: "clipboard",
aMimeType);
GtkClipboard* clipboard;
clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
return nullptr;
GtkClipboard* clipboard =
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
const char* data = (const char*)mClipboardData;
*aContentLength = mClipboardDataLength;
mClipboardDataLength = 0;
mClipboardData = nullptr;
return data;
return WaitForClipboardData(ClipboardDataType::Data, clipboard, aMimeType);
}
const char* nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard) {
GUniquePtr<char> nsRetrievalContextX11::GetClipboardText(
int32_t aWhichClipboard) {
LOGCLIP("nsRetrievalContextX11::GetClipboardText(%s)\n",
aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
: "clipboard");
GtkClipboard* clipboard;
clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard)) return nullptr;
GtkClipboard* clipboard =
gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
const char* data = (const char*)mClipboardData;
mClipboardData = nullptr;
mClipboardDataLength = 0;
return data;
}
void nsRetrievalContextX11::ReleaseClipboardData(const char** aClipboardData) {
g_free((void*)*aClipboardData);
*aClipboardData = nullptr;
return WaitForClipboardData(ClipboardDataType::Text, clipboard).mData;
}

View File

@ -10,20 +10,20 @@
#include <gtk/gtk.h>
#include "mozilla/Maybe.h"
#include "nsClipboard.h"
class nsRetrievalContextX11 : public nsRetrievalContext {
public:
enum State { INITIAL, COMPLETED, TIMED_OUT };
enum class State { Initial, Completed, TimedOut };
virtual const char* GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard,
uint32_t* aContentLength) override;
virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
virtual void ReleaseClipboardData(const char** aClipboardData) override;
ClipboardData GetClipboardData(const char* aMimeType,
int32_t aWhichClipboard) override;
mozilla::GUniquePtr<char> GetClipboardText(int32_t aWhichClipboard) override;
virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
int* aTargetNums) override;
ClipboardTargets GetTargets(int32_t aWhichClipboard) override;
virtual bool HasSelectionSupport(void) override;
bool HasSelectionSupport(void) override;
// Call this when data or text has been retrieved.
void Complete(ClipboardDataType aDataType, const void* aData,
@ -32,21 +32,20 @@ class nsRetrievalContextX11 : public nsRetrievalContext {
nsRetrievalContextX11();
private:
bool WaitForClipboardData(ClipboardDataType aDataType,
GtkClipboard* clipboard,
const char* aMimeType = nullptr);
ClipboardData WaitForClipboardData(ClipboardDataType aDataType,
GtkClipboard* clipboard,
const char* aMimeType = nullptr);
/**
* Spins X event loop until timing out or being completed. Returns
* null if we time out, otherwise returns the completed data (passing
* ownership to caller).
*/
bool WaitForX11Content();
ClipboardData WaitForX11Content();
State mState;
int mClipboardRequestNumber;
void* mClipboardData;
uint32_t mClipboardDataLength;
State mState = State::Initial;
int mClipboardRequestNumber = 0;
mozilla::Maybe<ClipboardData> mClipboardData;
GdkAtom mTargetMIMEType;
};