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 if (mIgnoreEmptyNotification
) {
273 MOZ_DIAGNOSTIC_ASSERT(false, "How did we get here?");
277 const auto& clipboardCache
= mCaches
[aWhichClipboard
];
278 MOZ_ASSERT(clipboardCache
);
279 clipboardCache
->Clear();
285 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray
<nsCString
>& aFlavorList
,
286 int32_t aWhichClipboard
,
288 CLIPBOARD_LOG("%s: clipboard=%d", __FUNCTION__
, aWhichClipboard
);
289 if (CLIPBOARD_LOG_ENABLED()) {
290 CLIPBOARD_LOG(" Asking for content clipboard=%i:\n", aWhichClipboard
);
291 for (const auto& flavor
: aFlavorList
) {
292 CLIPBOARD_LOG(" MIME %s", flavor
.get());
298 if (mozilla::StaticPrefs::widget_clipboard_use_cached_data_enabled()) {
299 if (auto* clipboardCache
= GetClipboardCacheIfValid(aWhichClipboard
)) {
300 MOZ_ASSERT(clipboardCache
->GetTransferable());
302 // first see if we have data for this in our cached transferable
303 nsTArray
<nsCString
> transferableFlavors
;
305 clipboardCache
->GetTransferable()->FlavorsTransferableCanImport(
306 transferableFlavors
);
307 if (NS_SUCCEEDED(rv
)) {
308 if (CLIPBOARD_LOG_ENABLED()) {
309 CLIPBOARD_LOG(" Cached transferable types (nums %zu)\n",
310 transferableFlavors
.Length());
311 for (const auto& transferableFlavor
: transferableFlavors
) {
312 CLIPBOARD_LOG(" MIME %s", transferableFlavor
.get());
316 for (const auto& transferableFlavor
: transferableFlavors
) {
317 for (const auto& flavor
: aFlavorList
) {
318 if (transferableFlavor
.Equals(flavor
)) {
319 CLIPBOARD_LOG(" has %s", flavor
.get());
330 HasNativeClipboardDataMatchingFlavors(aFlavorList
, aWhichClipboard
);
331 if (resultOrError
.isErr()) {
332 CLIPBOARD_LOG("%s: checking native clipboard data matching flavors falied.",
334 return resultOrError
.unwrapErr();
337 *aOutResult
= resultOrError
.unwrap();
341 RefPtr
<DataFlavorsPromise
> nsBaseClipboard::AsyncHasDataMatchingFlavors(
342 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
) {
343 nsTArray
<nsCString
> results
;
344 for (const auto& flavor
: aFlavorList
) {
345 bool hasMatchingFlavor
= false;
346 nsresult rv
= HasDataMatchingFlavors(AutoTArray
<nsCString
, 1>{flavor
},
347 aWhichClipboard
, &hasMatchingFlavor
);
348 if (NS_SUCCEEDED(rv
) && hasMatchingFlavor
) {
349 results
.AppendElement(flavor
);
353 return DataFlavorsPromise::CreateAndResolve(std::move(results
), __func__
);
357 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard
,
359 NS_ENSURE_ARG_POINTER(aRetval
);
360 switch (aWhichClipboard
) {
361 case kGlobalClipboard
:
362 // We always support the global clipboard.
365 case kSelectionClipboard
:
366 *aRetval
= mClipboardCaps
.supportsSelectionClipboard();
369 *aRetval
= mClipboardCaps
.supportsFindClipboard();
371 case kSelectionCache
:
372 *aRetval
= mClipboardCaps
.supportsSelectionCache();
380 nsBaseClipboard::ClipboardCache
* nsBaseClipboard::GetClipboardCacheIfValid(
381 int32_t aClipboardType
) {
382 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType
));
384 const mozilla::UniquePtr
<ClipboardCache
>& cache
= mCaches
[aClipboardType
];
387 if (!cache
->GetTransferable()) {
388 MOZ_ASSERT(cache
->GetSequenceNumber() == -1);
392 auto changeCountOrError
= GetNativeClipboardSequenceNumber(aClipboardType
);
393 if (changeCountOrError
.isErr()) {
397 if (changeCountOrError
.unwrap() != cache
->GetSequenceNumber()) {
398 // Clipboard cache is invalid, clear it.
406 void nsBaseClipboard::ClipboardCache::Clear() {
407 if (mClipboardOwner
) {
408 mClipboardOwner
->LosingOwnership(mTransferable
);
409 mClipboardOwner
= nullptr;
411 mTransferable
= nullptr;
412 mSequenceNumber
= -1;