Bug 1880216 - Migrate Fenix docs into Sphinx. r=owlish,geckoview-reviewers,android...
[gecko.git] / dom / events / ClipboardItem.cpp
blob76196be3b6b0d2a4b4af929898abd36f4eab288c
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)
26 NS_INTERFACE_MAP_END
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,
33 ErrorResult& aRv) {
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);
40 return;
43 MaybeResolvePendingPromises(std::move(clipboardData));
46 void ClipboardItem::ItemEntry::RejectedCallback(JSContext* aCx,
47 JS::Handle<JS::Value> aValue,
48 ErrorResult& aRv) {
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.
58 if (mIsLoadingData) {
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);
89 return NS_OK;
92 MOZ_ASSERT(trans);
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);
98 return NS_OK;
101 RefPtr<Blob> blob;
102 if (nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data)) {
103 nsAutoString str;
104 supportsstr->GetData(str);
106 blob = Blob::CreateStringBlob(mGlobal, NS_ConvertUTF16toUTF8(str), mType);
107 } else if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
108 uint64_t available;
109 void* data = nullptr;
110 rv = NS_ReadInputStreamToBuffer(istream, &data, -1, &available);
111 if (NS_WARN_IF(NS_FAILED(rv))) {
112 RejectPendingPromises(rv);
113 return NS_OK;
116 blob = Blob::CreateMemoryBlob(mGlobal, data, available, mType);
117 } else if (nsCOMPtr<nsISupportsCString> supportscstr =
118 do_QueryInterface(data)) {
119 nsAutoCString str;
120 supportscstr->GetData(str);
122 blob = Blob::CreateStringBlob(mGlobal, str, mType);
125 if (!blob) {
126 RejectPendingPromises(NS_ERROR_DOM_DATA_ERR);
127 return NS_OK;
130 OwningStringOrBlob clipboardData;
131 clipboardData.SetAsBlob() = std::move(blob);
132 MaybeResolvePendingPromises(std::move(clipboardData));
133 return NS_OK;
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);
152 return;
155 mTransferable->Init(nullptr);
156 mTransferable->AddDataFlavor(NS_ConvertUTF16toUTF8(mType).get());
158 nsresult rv = aDataGetter->GetData(mTransferable, this);
159 if (NS_FAILED(rv)) {
160 OnComplete(rv);
161 return;
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);
184 return;
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);
193 return;
196 MaybeResolveGetTypePromise(mData, aPromise);
199 void ClipboardItem::ItemEntry::MaybeResolveGetTypePromise(
200 const OwningStringOrBlob& aData, Promise& aPromise) {
201 if (aData.IsBlob()) {
202 aPromise.MaybeResolve(aData);
203 return;
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);
213 return;
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)
264 : mOwner(aOwner),
265 mPresentationStyle(aPresentationStyle),
266 mItems(std::move(aItems)) {}
268 // static
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");
275 return nullptr;
278 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
279 MOZ_ASSERT(global);
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,
300 ErrorResult& aRv) {
301 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
302 RefPtr<Promise> p = Promise::Create(global, aRv);
303 if (aRv.Failed()) {
304 return nullptr;
307 for (auto& item : mItems) {
308 MOZ_ASSERT(item);
310 const nsAString& type = item->Type();
311 if (type == aType) {
312 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
313 if (NS_WARN_IF(!global)) {
314 p->MaybeReject(NS_ERROR_UNEXPECTED);
315 return p.forget();
318 item->ReactGetTypePromise(*p);
319 return p.forget();
323 p->MaybeRejectWithNotFoundError(
324 "The type '"_ns + NS_ConvertUTF16toUTF8(aType) + "' was not found"_ns);
325 return p.forget();
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