1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/ClipboardItem.h"
9 #include "mozilla/dom/Promise.h"
10 #include "mozilla/dom/Record.h"
11 #include "nsComponentManagerUtils.h"
12 #include "nsIClipboard.h"
13 #include "nsIInputStream.h"
14 #include "nsISupportsPrimitives.h"
15 #include "nsNetUtil.h"
16 #include "nsServiceManagerUtils.h"
18 namespace mozilla::dom
{
20 NS_IMPL_CYCLE_COLLECTION(ClipboardItem::ItemEntry
, mGlobal
, mData
,
21 mPendingGetTypeRequests
)
23 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClipboardItem::ItemEntry
)
24 NS_INTERFACE_MAP_ENTRY(nsIAsyncClipboardRequestCallback
)
25 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, PromiseNativeHandler
)
28 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClipboardItem::ItemEntry
)
29 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClipboardItem::ItemEntry
)
31 void ClipboardItem::ItemEntry::ResolvedCallback(JSContext
* aCx
,
32 JS::Handle
<JS::Value
> aValue
,
34 MOZ_ASSERT(!mTransferable
);
35 mIsLoadingData
= false;
36 OwningStringOrBlob clipboardData
;
37 if (!clipboardData
.Init(aCx
, aValue
)) {
38 JS_ClearPendingException(aCx
);
39 RejectPendingPromises(NS_ERROR_DOM_DATA_ERR
);
43 MaybeResolvePendingPromises(std::move(clipboardData
));
46 void ClipboardItem::ItemEntry::RejectedCallback(JSContext
* aCx
,
47 JS::Handle
<JS::Value
> aValue
,
49 MOZ_ASSERT(!mTransferable
);
50 mIsLoadingData
= false;
51 RejectPendingPromises(NS_ERROR_DOM_DATA_ERR
);
54 RefPtr
<ClipboardItem::ItemEntry::GetDataPromise
>
55 ClipboardItem::ItemEntry::GetData() {
56 // Data is still being loaded, either from the system clipboard or the data
57 // promise provided to the ClipboardItem constructor.
59 MOZ_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
60 "Data should be uninitialized");
61 MOZ_ASSERT(mLoadResult
.isNothing(), "Should have no load result");
63 MozPromiseHolder
<GetDataPromise
> holder
;
64 RefPtr
<GetDataPromise
> promise
= holder
.Ensure(__func__
);
65 mPendingGetDataRequests
.AppendElement(std::move(holder
));
66 return promise
.forget();
69 if (NS_FAILED(mLoadResult
.value())) {
70 MOZ_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
71 "Data should be uninitialized");
72 // We are not able to load data, so reject the promise directly.
73 return GetDataPromise::CreateAndReject(mLoadResult
.value(), __func__
);
76 MOZ_ASSERT(mData
.IsString() || mData
.IsBlob(), "Data should be initialized");
77 OwningStringOrBlob
data(mData
);
78 return GetDataPromise::CreateAndResolve(std::move(data
), __func__
);
81 NS_IMETHODIMP
ClipboardItem::ItemEntry::OnComplete(nsresult aResult
) {
82 MOZ_ASSERT(mIsLoadingData
);
84 mIsLoadingData
= false;
85 nsCOMPtr
<nsITransferable
> trans
= std::move(mTransferable
);
87 if (NS_FAILED(aResult
)) {
88 RejectPendingPromises(aResult
);
93 nsCOMPtr
<nsISupports
> data
;
94 nsresult rv
= trans
->GetTransferData(NS_ConvertUTF16toUTF8(mType
).get(),
95 getter_AddRefs(data
));
96 if (NS_WARN_IF(NS_FAILED(rv
))) {
97 RejectPendingPromises(rv
);
102 if (nsCOMPtr
<nsISupportsString
> supportsstr
= do_QueryInterface(data
)) {
104 supportsstr
->GetData(str
);
106 blob
= Blob::CreateStringBlob(mGlobal
, NS_ConvertUTF16toUTF8(str
), mType
);
107 } else if (nsCOMPtr
<nsIInputStream
> istream
= do_QueryInterface(data
)) {
109 void* data
= nullptr;
110 rv
= NS_ReadInputStreamToBuffer(istream
, &data
, -1, &available
);
111 if (NS_WARN_IF(NS_FAILED(rv
))) {
112 RejectPendingPromises(rv
);
116 blob
= Blob::CreateMemoryBlob(mGlobal
, data
, available
, mType
);
117 } else if (nsCOMPtr
<nsISupportsCString
> supportscstr
=
118 do_QueryInterface(data
)) {
120 supportscstr
->GetData(str
);
122 blob
= Blob::CreateStringBlob(mGlobal
, str
, mType
);
126 RejectPendingPromises(NS_ERROR_DOM_DATA_ERR
);
130 OwningStringOrBlob clipboardData
;
131 clipboardData
.SetAsBlob() = std::move(blob
);
132 MaybeResolvePendingPromises(std::move(clipboardData
));
136 void ClipboardItem::ItemEntry::LoadDataFromSystemClipboard(
137 nsIAsyncGetClipboardData
* aDataGetter
) {
138 MOZ_ASSERT(aDataGetter
);
139 // XXX maybe we could consider adding a method to check whether the union
140 // object is uninitialized or initialized.
141 MOZ_DIAGNOSTIC_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
142 "Data should be uninitialized.");
143 MOZ_DIAGNOSTIC_ASSERT(mLoadResult
.isNothing(), "Should have no load result");
144 MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData
&& !mTransferable
,
145 "Should not be in the process of loading data");
147 mIsLoadingData
= true;
149 mTransferable
= do_CreateInstance("@mozilla.org/widget/transferable;1");
150 if (NS_WARN_IF(!mTransferable
)) {
151 OnComplete(NS_ERROR_FAILURE
);
155 mTransferable
->Init(nullptr);
156 mTransferable
->AddDataFlavor(NS_ConvertUTF16toUTF8(mType
).get());
158 nsresult rv
= aDataGetter
->GetData(mTransferable
, this);
165 void ClipboardItem::ItemEntry::LoadDataFromDataPromise(Promise
& aDataPromise
) {
166 MOZ_DIAGNOSTIC_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
167 "Data should be uninitialized");
168 MOZ_DIAGNOSTIC_ASSERT(mLoadResult
.isNothing(), "Should have no load result");
169 MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData
&& !mTransferable
,
170 "Should not be in the process of loading data");
172 mIsLoadingData
= true;
173 aDataPromise
.AppendNativeHandler(this);
176 void ClipboardItem::ItemEntry::ReactGetTypePromise(Promise
& aPromise
) {
177 // Data is still being loaded, either from the system clipboard or the data
178 // promise provided to the ClipboardItem constructor.
179 if (mIsLoadingData
) {
180 MOZ_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
181 "Data should be uninitialized");
182 MOZ_ASSERT(mLoadResult
.isNothing(), "Should have no load result.");
183 mPendingGetTypeRequests
.AppendElement(&aPromise
);
187 if (NS_FAILED(mLoadResult
.value())) {
188 MOZ_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
189 "Data should be uninitialized");
190 aPromise
.MaybeRejectWithDataError("The data for type '"_ns
+
191 NS_ConvertUTF16toUTF8(mType
) +
192 "' was not found"_ns
);
196 MaybeResolveGetTypePromise(mData
, aPromise
);
199 void ClipboardItem::ItemEntry::MaybeResolveGetTypePromise(
200 const OwningStringOrBlob
& aData
, Promise
& aPromise
) {
201 if (aData
.IsBlob()) {
202 aPromise
.MaybeResolve(aData
);
206 // XXX This is for the case that data is from ClipboardItem constructor,
207 // maybe we should also load that into a Blob earlier. But Safari returns
208 // different `Blob` instances for each `getTypes` call if the string is from
209 // ClipboardItem constructor, which is more like our current setup.
210 if (RefPtr
<Blob
> blob
= Blob::CreateStringBlob(
211 mGlobal
, NS_ConvertUTF16toUTF8(aData
.GetAsString()), mType
)) {
212 aPromise
.MaybeResolve(blob
);
216 aPromise
.MaybeRejectWithDataError("The data for type '"_ns
+
217 NS_ConvertUTF16toUTF8(mType
) +
218 "' was not found"_ns
);
221 void ClipboardItem::ItemEntry::RejectPendingPromises(nsresult aRv
) {
222 MOZ_ASSERT(NS_FAILED(aRv
), "Should have a failure code here");
223 MOZ_DIAGNOSTIC_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
224 "Data should be uninitialized");
225 MOZ_DIAGNOSTIC_ASSERT(mLoadResult
.isNothing(), "Should not have load result");
226 MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData
&& !mTransferable
,
227 "Should not be in the process of loading data");
228 mLoadResult
.emplace(aRv
);
229 auto promiseHolders
= std::move(mPendingGetDataRequests
);
230 for (auto& promiseHolder
: promiseHolders
) {
231 promiseHolder
.Reject(aRv
, __func__
);
233 auto getTypePromises
= std::move(mPendingGetTypeRequests
);
234 for (auto& promise
: getTypePromises
) {
235 promise
->MaybeReject(aRv
);
239 void ClipboardItem::ItemEntry::MaybeResolvePendingPromises(
240 OwningStringOrBlob
&& aData
) {
241 MOZ_DIAGNOSTIC_ASSERT(!mData
.IsString() && !mData
.IsBlob(),
242 "Data should be uninitialized");
243 MOZ_DIAGNOSTIC_ASSERT(mLoadResult
.isNothing(), "Should not have load result");
244 MOZ_DIAGNOSTIC_ASSERT(!mIsLoadingData
&& !mTransferable
,
245 "Should not be in the process of loading data");
246 mLoadResult
.emplace(NS_OK
);
247 mData
= std::move(aData
);
248 auto getDataPromiseHolders
= std::move(mPendingGetDataRequests
);
249 for (auto& promiseHolder
: getDataPromiseHolders
) {
250 OwningStringOrBlob
data(mData
);
251 promiseHolder
.Resolve(std::move(data
), __func__
);
253 auto promises
= std::move(mPendingGetTypeRequests
);
254 for (auto& promise
: promises
) {
255 MaybeResolveGetTypePromise(mData
, *promise
);
259 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ClipboardItem
, mOwner
, mItems
)
261 ClipboardItem::ClipboardItem(nsISupports
* aOwner
,
262 const dom::PresentationStyle aPresentationStyle
,
263 nsTArray
<RefPtr
<ItemEntry
>>&& aItems
)
265 mPresentationStyle(aPresentationStyle
),
266 mItems(std::move(aItems
)) {}
269 already_AddRefed
<ClipboardItem
> ClipboardItem::Constructor(
270 const GlobalObject
& aGlobal
,
271 const Record
<nsString
, OwningNonNull
<Promise
>>& aItems
,
272 const ClipboardItemOptions
& aOptions
, ErrorResult
& aRv
) {
273 if (aItems
.Entries().IsEmpty()) {
274 aRv
.ThrowTypeError("At least one entry required");
278 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
281 nsTArray
<RefPtr
<ItemEntry
>> items
;
282 for (const auto& entry
: aItems
.Entries()) {
283 RefPtr
<ItemEntry
> item
= MakeRefPtr
<ItemEntry
>(global
, entry
.mKey
);
284 item
->LoadDataFromDataPromise(*entry
.mValue
);
285 items
.AppendElement(std::move(item
));
288 RefPtr
<ClipboardItem
> item
= MakeRefPtr
<ClipboardItem
>(
289 global
, aOptions
.mPresentationStyle
, std::move(items
));
290 return item
.forget();
293 void ClipboardItem::GetTypes(nsTArray
<nsString
>& aTypes
) const {
294 for (const auto& item
: mItems
) {
295 aTypes
.AppendElement(item
->Type());
299 already_AddRefed
<Promise
> ClipboardItem::GetType(const nsAString
& aType
,
301 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(GetParentObject());
302 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
307 for (auto& item
: mItems
) {
310 const nsAString
& type
= item
->Type();
312 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(GetParentObject());
313 if (NS_WARN_IF(!global
)) {
314 p
->MaybeReject(NS_ERROR_UNEXPECTED
);
318 item
->ReactGetTypePromise(*p
);
323 p
->MaybeRejectWithNotFoundError(
324 "The type '"_ns
+ NS_ConvertUTF16toUTF8(aType
) + "' was not found"_ns
);
328 JSObject
* ClipboardItem::WrapObject(JSContext
* aCx
,
329 JS::Handle
<JSObject
*> aGivenProto
) {
330 return mozilla::dom::ClipboardItem_Binding::Wrap(aCx
, this, aGivenProto
);
333 } // namespace mozilla::dom