1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsBaseClipboard.h"
8 #include "mozilla/StaticPrefs_widget.h"
9 #include "nsIClipboardOwner.h"
13 using mozilla::GenericPromise
;
14 using mozilla::LogLevel
;
15 using mozilla::UniquePtr
;
16 using mozilla::dom::ClipboardCapabilities
;
18 NS_IMPL_ISUPPORTS(ClipboardSetDataHelper::AsyncSetClipboardData
,
19 nsIAsyncSetClipboardData
)
21 ClipboardSetDataHelper::AsyncSetClipboardData::AsyncSetClipboardData(
22 int32_t aClipboardType
, ClipboardSetDataHelper
* aClipboard
,
23 nsIAsyncSetClipboardDataCallback
* aCallback
)
24 : mClipboardType(aClipboardType
),
25 mClipboard(aClipboard
),
26 mCallback(aCallback
) {
27 MOZ_ASSERT(mClipboard
);
28 MOZ_ASSERT(mClipboard
->IsClipboardTypeSupported(mClipboardType
));
32 ClipboardSetDataHelper::AsyncSetClipboardData::SetData(
33 nsITransferable
* aTransferable
, nsIClipboardOwner
* aOwner
) {
35 return NS_ERROR_FAILURE
;
38 MOZ_ASSERT(mClipboard
);
39 MOZ_ASSERT(mClipboard
->IsClipboardTypeSupported(mClipboardType
));
40 MOZ_DIAGNOSTIC_ASSERT(mClipboard
->mPendingWriteRequests
[mClipboardType
] ==
43 RefPtr
<AsyncSetClipboardData
> request
=
44 std::move(mClipboard
->mPendingWriteRequests
[mClipboardType
]);
45 nsresult rv
= mClipboard
->SetData(aTransferable
, aOwner
, mClipboardType
);
46 MaybeNotifyCallback(rv
);
52 ClipboardSetDataHelper::AsyncSetClipboardData::Abort(nsresult aReason
) {
53 // Note: This may be called during destructor, so it should not attempt to
54 // take a reference to mClipboard.
56 if (!IsValid() || !NS_FAILED(aReason
)) {
57 return NS_ERROR_FAILURE
;
60 MaybeNotifyCallback(aReason
);
64 void ClipboardSetDataHelper::AsyncSetClipboardData::MaybeNotifyCallback(
66 // Note: This may be called during destructor, so it should not attempt to
67 // take a reference to mClipboard.
69 MOZ_ASSERT(IsValid());
70 if (nsCOMPtr
<nsIAsyncSetClipboardDataCallback
> callback
=
72 callback
->OnComplete(aResult
);
74 // Once the callback is notified, setData should not be allowed, so invalidate
79 NS_IMPL_ISUPPORTS(ClipboardSetDataHelper
, nsIClipboard
)
81 ClipboardSetDataHelper::~ClipboardSetDataHelper() {
82 for (auto& request
: mPendingWriteRequests
) {
84 request
->Abort(NS_ERROR_ABORT
);
90 void ClipboardSetDataHelper::RejectPendingAsyncSetDataRequestIfAny(
91 int32_t aClipboardType
) {
92 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
93 auto& request
= mPendingWriteRequests
[aClipboardType
];
95 request
->Abort(NS_ERROR_ABORT
);
101 ClipboardSetDataHelper::SetData(nsITransferable
* aTransferable
,
102 nsIClipboardOwner
* aOwner
,
103 int32_t aWhichClipboard
) {
104 NS_ENSURE_ARG(aTransferable
);
105 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
106 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
109 // Reject existing pending asyncSetData request if any.
110 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
112 return SetNativeClipboardData(aTransferable
, aOwner
, aWhichClipboard
);
115 NS_IMETHODIMP
ClipboardSetDataHelper::AsyncSetData(
116 int32_t aWhichClipboard
, nsIAsyncSetClipboardDataCallback
* aCallback
,
117 nsIAsyncSetClipboardData
** _retval
) {
119 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
120 return NS_ERROR_DOM_NOT_SUPPORTED_ERR
;
123 // Reject existing pending AsyncSetData request if any.
124 RejectPendingAsyncSetDataRequestIfAny(aWhichClipboard
);
126 // Create a new AsyncSetClipboardData.
127 RefPtr
<AsyncSetClipboardData
> request
=
128 mozilla::MakeRefPtr
<AsyncSetClipboardData
>(aWhichClipboard
, this,
130 mPendingWriteRequests
[aWhichClipboard
] = request
;
131 request
.forget(_retval
);
135 nsBaseClipboard::nsBaseClipboard(const ClipboardCapabilities
& aClipboardCaps
)
136 : mClipboardCaps(aClipboardCaps
) {
137 using mozilla::MakeUnique
;
138 // Initialize clipboard cache.
139 mCaches
[kGlobalClipboard
] = MakeUnique
<ClipboardCache
>();
140 if (mClipboardCaps
.supportsSelectionClipboard()) {
141 mCaches
[kSelectionClipboard
] = MakeUnique
<ClipboardCache
>();
143 if (mClipboardCaps
.supportsFindClipboard()) {
144 mCaches
[kFindClipboard
] = MakeUnique
<ClipboardCache
>();
146 if (mClipboardCaps
.supportsSelectionCache()) {
147 mCaches
[kSelectionCache
] = MakeUnique
<ClipboardCache
>();
151 NS_IMPL_ISUPPORTS_INHERITED0(nsBaseClipboard
, ClipboardSetDataHelper
)
154 * Sets the transferable object
157 NS_IMETHODIMP
nsBaseClipboard::SetData(nsITransferable
* aTransferable
,
158 nsIClipboardOwner
* anOwner
,
159 int32_t aWhichClipboard
) {
160 NS_ASSERTION(aTransferable
, "clipboard given a null transferable");
162 CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
164 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
165 CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
167 return NS_ERROR_FAILURE
;
170 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
171 MOZ_ASSERT(clipboardCache
);
172 if (aTransferable
== clipboardCache
->GetTransferable() &&
173 anOwner
== clipboardCache
->GetClipboardOwner()) {
174 CLIPBOARD_LOG("%s: skipping update.", __FUNCTION__
);
178 clipboardCache
->Clear();
180 nsresult rv
= NS_ERROR_FAILURE
;
182 mIgnoreEmptyNotification
= true;
183 rv
= ClipboardSetDataHelper::SetData(aTransferable
, anOwner
,
185 mIgnoreEmptyNotification
= false;
188 CLIPBOARD_LOG("%s: setting native clipboard data failed.", __FUNCTION__
);
192 auto result
= GetNativeClipboardSequenceNumber(aWhichClipboard
);
193 if (result
.isErr()) {
194 CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
196 return result
.unwrapErr();
199 clipboardCache
->Update(aTransferable
, anOwner
, result
.unwrap());
204 * Gets the transferable object from system clipboard.
206 NS_IMETHODIMP
nsBaseClipboard::GetData(nsITransferable
* aTransferable
,
207 int32_t aWhichClipboard
) {
208 CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
210 if (!aTransferable
) {
211 NS_ASSERTION(false, "clipboard given a null transferable");
212 return NS_ERROR_FAILURE
;
215 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
216 // If we were the last ones to put something on the navtive clipboard, then
217 // just use the cached transferable. Otherwise clear it because it isn't
218 // relevant any more.
219 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
220 MOZ_ASSERT(clipboardCache
->GetTransferable());
222 // get flavor list that includes all acceptable flavors (including ones
223 // obtained through conversion)
224 nsTArray
<nsCString
> flavors
;
225 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
227 return NS_ERROR_FAILURE
;
230 for (const auto& flavor
: flavors
) {
231 nsCOMPtr
<nsISupports
> dataSupports
;
232 rv
= clipboardCache
->GetTransferable()->GetTransferData(
233 flavor
.get(), getter_AddRefs(dataSupports
));
234 if (NS_SUCCEEDED(rv
)) {
235 CLIPBOARD_LOG("%s: getting %s from cache.", __FUNCTION__
,
237 aTransferable
->SetTransferData(flavor
.get(), dataSupports
);
238 // maybe try to fill in more types? Is there a point?
244 // at this point we can't satisfy the request from cache data so let's look
245 // for things other people put on the system clipboard
248 return GetNativeClipboardData(aTransferable
, aWhichClipboard
);
251 RefPtr
<GenericPromise
> nsBaseClipboard::AsyncGetData(
252 nsITransferable
* aTransferable
, int32_t aWhichClipboard
) {
253 nsresult rv
= GetData(aTransferable
, aWhichClipboard
);
255 return GenericPromise::CreateAndReject(rv
, __func__
);
258 return GenericPromise::CreateAndResolve(true, __func__
);
261 NS_IMETHODIMP
nsBaseClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
262 CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
264 if (!nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
)) {
265 CLIPBOARD_LOG("%s: clipboard %d is not supported.", __FUNCTION__
,
267 return NS_ERROR_FAILURE
;
270 EmptyNativeClipboardData(aWhichClipboard
);
272 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
273 MOZ_ASSERT(clipboardCache
);
275 if (mIgnoreEmptyNotification
) {
276 MOZ_DIAGNOSTIC_ASSERT(!clipboardCache
->GetTransferable() &&
277 !clipboardCache
->GetClipboardOwner() &&
278 clipboardCache
->GetSequenceNumber() == -1,
279 "How did we have data in clipboard cache here?");
283 clipboardCache
->Clear();
289 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
290 int32_t aWhichClipboard
,
292 CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
293 if (CLIPBOARD_LOG_ENABLED()) {
294 CLIPBOARD_LOG(" Asking for content clipboard=%i:\n", aWhichClipboard
);
295 for (const auto& flavor
: aFlavorList
) {
296 CLIPBOARD_LOG(" MIME %s", flavor
.get());
302 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
303 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
304 MOZ_ASSERT(clipboardCache
->GetTransferable());
306 // first see if we have data for this in our cached transferable
307 nsTArray
<nsCString
> transferableFlavors
;
309 clipboardCache
->GetTransferable()->FlavorsTransferableCanImport(
310 transferableFlavors
);
311 if (NS_SUCCEEDED(rv
)) {
312 if (CLIPBOARD_LOG_ENABLED()) {
313 CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
314 transferableFlavors
.Length());
315 for (const auto& transferableFlavor
: transferableFlavors
) {
316 CLIPBOARD_LOG(" MIME %s", transferableFlavor
.get());
320 for (const auto& transferableFlavor
: transferableFlavors
) {
321 for (const auto& flavor
: aFlavorList
) {
322 if (transferableFlavor
.Equals(flavor
)) {
323 CLIPBOARD_LOG(" has %s", flavor
.get());
334 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
335 if (resultOrError
.isErr()) {
336 CLIPBOARD_LOG("%s: checking native clipboard data matching flavors falied.",
338 return resultOrError
.unwrapErr();
341 *aOutResult
= resultOrError
.unwrap();
345 RefPtr
<DataFlavorsPromise
> nsBaseClipboard::AsyncHasDataMatchingFlavors(
346 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
) {
347 nsTArray
<nsCString
> results
;
348 for (const auto& flavor
: aFlavorList
) {
349 bool hasMatchingFlavor
= false;
350 nsresult rv
= HasDataMatchingFlavors(AutoTArray
<nsCString
, 1>{flavor
},
351 aWhichClipboard
, &hasMatchingFlavor
);
352 if (NS_SUCCEEDED(rv
) && hasMatchingFlavor
) {
353 results
.AppendElement(flavor
);
357 return DataFlavorsPromise::CreateAndResolve(std::move(results
), __func__
);
361 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
363 NS_ENSURE_ARG_POINTER(aRetval
);
364 switch (aWhichClipboard
) {
365 case kGlobalClipboard
:
366 // We always support the global clipboard.
369 case kSelectionClipboard
:
370 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
373 *aRetval
= mClipboardCaps
.supportsFindClipboard();
375 case kSelectionCache
:
376 *aRetval
= mClipboardCaps
.supportsSelectionCache();
384 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
385 int32_t aClipboardType
) {
386 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
388 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
391 if (!cache
->GetTransferable()) {
392 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
396 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
397 if (changeCountOrError
.isErr()) {
401 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
402 // Clipboard cache is invalid, clear it.
410 void nsBaseClipboard::ClipboardCache::Clear() {
411 if (mClipboardOwner
) {
412 mClipboardOwner
->LosingOwnership(mTransferable
);
413 mClipboardOwner
= nullptr;
415 mTransferable
= nullptr;
416 mSequenceNumber
= -1;