Merge mozilla-central to autoland on a CLOSED TREE
[gecko.git] / widget / nsBaseClipboard.cpp
blobbd96d253a9e648c86952a737d392c1134e8d5c87
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"
10 #include "nsError.h"
11 #include "nsXPCOM.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));
31 NS_IMETHODIMP
32 ClipboardSetDataHelper::AsyncSetClipboardData::SetData(
33 nsITransferable* aTransferable, nsIClipboardOwner* aOwner) {
34 if (!IsValid()) {
35 return NS_ERROR_FAILURE;
38 MOZ_ASSERT(mClipboard);
39 MOZ_ASSERT(mClipboard->IsClipboardTypeSupported(mClipboardType));
40 MOZ_DIAGNOSTIC_ASSERT(mClipboard->mPendingWriteRequests[mClipboardType] ==
41 this);
43 RefPtr<AsyncSetClipboardData> request =
44 std::move(mClipboard->mPendingWriteRequests[mClipboardType]);
45 nsresult rv = mClipboard->SetData(aTransferable, aOwner, mClipboardType);
46 MaybeNotifyCallback(rv);
48 return rv;
51 NS_IMETHODIMP
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);
61 return NS_OK;
64 void ClipboardSetDataHelper::AsyncSetClipboardData::MaybeNotifyCallback(
65 nsresult aResult) {
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 =
71 mCallback.forget()) {
72 callback->OnComplete(aResult);
74 // Once the callback is notified, setData should not be allowed, so invalidate
75 // this request.
76 mClipboard = nullptr;
79 NS_IMPL_ISUPPORTS(ClipboardSetDataHelper, nsIClipboard)
81 ClipboardSetDataHelper::~ClipboardSetDataHelper() {
82 for (auto& request : mPendingWriteRequests) {
83 if (request) {
84 request->Abort(NS_ERROR_ABORT);
85 request = nullptr;
90 void ClipboardSetDataHelper::RejectPendingAsyncSetDataRequestIfAny(
91 int32_t aClipboardType) {
92 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
93 auto& request = mPendingWriteRequests[aClipboardType];
94 if (request) {
95 request->Abort(NS_ERROR_ABORT);
96 request = nullptr;
100 NS_IMETHODIMP
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) {
118 *_retval = nullptr;
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,
129 aCallback);
130 mPendingWriteRequests[aWhichClipboard] = request;
131 request.forget(_retval);
132 return NS_OK;
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__,
166 aWhichClipboard);
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__);
175 return NS_OK;
178 clipboardCache->Clear();
180 nsresult rv = NS_ERROR_FAILURE;
181 if (aTransferable) {
182 mIgnoreEmptyNotification = true;
183 rv = ClipboardSetDataHelper::SetData(aTransferable, anOwner,
184 aWhichClipboard);
185 mIgnoreEmptyNotification = false;
187 if (NS_FAILED(rv)) {
188 CLIPBOARD_LOG("%s: setting native clipboard data failed.", __FUNCTION__);
189 return rv;
192 auto result = GetNativeClipboardSequenceNumber(aWhichClipboard);
193 if (result.isErr()) {
194 CLIPBOARD_LOG("%s: getting native clipboard change count failed.",
195 __FUNCTION__);
196 return result.unwrapErr();
199 clipboardCache->Update(aTransferable, anOwner, result.unwrap());
200 return NS_OK;
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);
226 if (NS_FAILED(rv)) {
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__,
236 flavor.get());
237 aTransferable->SetTransferData(flavor.get(), dataSupports);
238 // maybe try to fill in more types? Is there a point?
239 return NS_OK;
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);
254 if (NS_FAILED(rv)) {
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__,
266 aWhichClipboard);
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?");
280 return NS_OK;
283 clipboardCache->Clear();
285 return NS_OK;
288 NS_IMETHODIMP
289 nsBaseClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
290 int32_t aWhichClipboard,
291 bool* aOutResult) {
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());
300 *aOutResult = false;
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;
308 nsresult rv =
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());
324 *aOutResult = true;
325 return NS_OK;
333 auto resultOrError =
334 HasNativeClipboardDataMatchingFlavors(aFlavorList, aWhichClipboard);
335 if (resultOrError.isErr()) {
336 CLIPBOARD_LOG("%s: checking native clipboard data matching flavors falied.",
337 __FUNCTION__);
338 return resultOrError.unwrapErr();
341 *aOutResult = resultOrError.unwrap();
342 return NS_OK;
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__);
360 NS_IMETHODIMP
361 nsBaseClipboard::IsClipboardTypeSupported(int32_t aWhichClipboard,
362 bool* aRetval) {
363 NS_ENSURE_ARG_POINTER(aRetval);
364 switch (aWhichClipboard) {
365 case kGlobalClipboard:
366 // We always support the global clipboard.
367 *aRetval = true;
368 return NS_OK;
369 case kSelectionClipboard:
370 *aRetval = mClipboardCaps.supportsSelectionClipboard();
371 return NS_OK;
372 case kFindClipboard:
373 *aRetval = mClipboardCaps.supportsFindClipboard();
374 return NS_OK;
375 case kSelectionCache:
376 *aRetval = mClipboardCaps.supportsSelectionCache();
377 return NS_OK;
378 default:
379 *aRetval = false;
380 return NS_OK;
384 nsBaseClipboard::ClipboardCache* nsBaseClipboard::GetClipboardCacheIfValid(
385 int32_t aClipboardType) {
386 MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
388 const mozilla::UniquePtr<ClipboardCache>& cache = mCaches[aClipboardType];
389 MOZ_ASSERT(cache);
391 if (!cache->GetTransferable()) {
392 MOZ_ASSERT(cache->GetSequenceNumber() == -1);
393 return nullptr;
396 auto changeCountOrError = GetNativeClipboardSequenceNumber(aClipboardType);
397 if (changeCountOrError.isErr()) {
398 return nullptr;
401 if (changeCountOrError.unwrap() != cache->GetSequenceNumber()) {
402 // Clipboard cache is invalid, clear it.
403 cache->Clear();
404 return nullptr;
407 return cache.get();
410 void nsBaseClipboard::ClipboardCache::Clear() {
411 if (mClipboardOwner) {
412 mClipboardOwner->LosingOwnership(mTransferable);
413 mClipboardOwner = nullptr;
415 mTransferable = nullptr;
416 mSequenceNumber = -1;