Bug 1686668 [wpt PR 27185] - Update wpt metadata, a=testonly
[gecko.git] / dom / events / DataTransfer.cpp
blob51df5133e807521f1d7a267f9deea7f8b1e0c840
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/ArrayUtils.h"
8 #include "mozilla/BasePrincipal.h"
9 #include "mozilla/BasicEvents.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/Span.h"
12 #include "mozilla/StaticPrefs_dom.h"
13 #include "DataTransfer.h"
15 #include "nsISupportsPrimitives.h"
16 #include "nsIScriptSecurityManager.h"
17 #include "mozilla/dom/DOMStringList.h"
18 #include "nsArray.h"
19 #include "nsError.h"
20 #include "nsIDragService.h"
21 #include "nsIClipboard.h"
22 #include "nsIXPConnect.h"
23 #include "nsContentUtils.h"
24 #include "nsIContent.h"
25 #include "nsIObjectInputStream.h"
26 #include "nsIObjectOutputStream.h"
27 #include "nsIStorageStream.h"
28 #include "nsStringStream.h"
29 #include "nsCRT.h"
30 #include "nsIScriptObjectPrincipal.h"
31 #include "nsIScriptContext.h"
32 #include "mozilla/dom/Document.h"
33 #include "nsIScriptGlobalObject.h"
34 #include "nsVariant.h"
35 #include "mozilla/dom/ContentChild.h"
36 #include "mozilla/dom/DataTransferBinding.h"
37 #include "mozilla/dom/DataTransferItemList.h"
38 #include "mozilla/dom/Directory.h"
39 #include "mozilla/dom/Element.h"
40 #include "mozilla/dom/FileList.h"
41 #include "mozilla/dom/BindingUtils.h"
42 #include "mozilla/dom/OSFileSystem.h"
43 #include "mozilla/dom/Promise.h"
44 #include "nsComponentManagerUtils.h"
45 #include "nsNetUtil.h"
46 #include "nsReadableUtils.h"
48 namespace mozilla::dom {
50 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
53 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
54 NS_IMPL_CYCLE_COLLECTION_UNLINK(mItems)
55 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget)
56 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage)
57 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
58 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer)
60 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
61 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mItems)
62 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget)
63 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage)
64 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
65 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer)
67 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer)
68 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransfer)
70 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransfer)
71 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
72 NS_INTERFACE_MAP_ENTRY(mozilla::dom::DataTransfer)
73 NS_INTERFACE_MAP_ENTRY(nsISupports)
74 NS_INTERFACE_MAP_END
76 // the size of the array
77 const char DataTransfer::sEffects[8][9] = {
78 "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"};
80 // Used for custom clipboard types.
81 enum CustomClipboardTypeId {
82 eCustomClipboardTypeId_None,
83 eCustomClipboardTypeId_String
86 static DataTransfer::Mode ModeForEvent(EventMessage aEventMessage) {
87 switch (aEventMessage) {
88 case eCut:
89 case eCopy:
90 case eDragStart:
91 // For these events, we want to be able to add data to the data transfer,
92 // Otherwise, the data is already present.
93 return DataTransfer::Mode::ReadWrite;
94 case eDrop:
95 case ePaste:
96 case ePasteNoFormatting:
97 case eEditorInput:
98 // For these events we want to be able to read the data which is stored in
99 // the DataTransfer, rather than just the type information.
100 return DataTransfer::Mode::ReadOnly;
101 default:
102 return StaticPrefs::dom_events_dataTransfer_protected_enabled()
103 ? DataTransfer::Mode::Protected
104 : DataTransfer::Mode::ReadOnly;
108 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
109 bool aIsExternal, int32_t aClipboardType)
110 : mParent(aParent),
111 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
112 mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
113 mEventMessage(aEventMessage),
114 mCursorState(false),
115 mMode(ModeForEvent(aEventMessage)),
116 mIsExternal(aIsExternal),
117 mUserCancelled(false),
118 mIsCrossDomainSubFrameDrop(false),
119 mClipboardType(aClipboardType),
120 mDragImageX(0),
121 mDragImageY(0) {
122 mItems = new DataTransferItemList(this);
124 // For external usage, cache the data from the native clipboard or drag.
125 if (mIsExternal && mMode != Mode::ReadWrite) {
126 if (aEventMessage == ePasteNoFormatting) {
127 mEventMessage = ePaste;
128 CacheExternalClipboardFormats(true);
129 } else if (aEventMessage == ePaste) {
130 CacheExternalClipboardFormats(false);
131 } else if (aEventMessage >= eDragDropEventFirst &&
132 aEventMessage <= eDragDropEventLast) {
133 CacheExternalDragFormats();
138 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
139 nsITransferable* aTransferable)
140 : mParent(aParent),
141 mTransferable(aTransferable),
142 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
143 mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
144 mEventMessage(aEventMessage),
145 mCursorState(false),
146 mMode(ModeForEvent(aEventMessage)),
147 mIsExternal(true),
148 mUserCancelled(false),
149 mIsCrossDomainSubFrameDrop(false),
150 mClipboardType(-1),
151 mDragImageX(0),
152 mDragImageY(0) {
153 mItems = new DataTransferItemList(this);
155 // XXX Currently, we cannot make DataTransfer grabs mTransferable for long
156 // time because nsITransferable is not cycle collectable but this may
157 // be grabbed by JS. Additionally, the data initializing path is too
158 // complicated (too optimized) for D&D and clipboard. They are cached
159 // only formats first, then, data of all items will be filled by the
160 // items later and by themselves. However, we shouldn't duplicate such
161 // path for saving the maintenance cost. Therefore, we need to treat
162 // that DataTransfer and its items are in external mode. Finally,
163 // release mTransferable and make them in internal mode.
164 CacheTransferableFormats();
165 FillAllExternalData();
166 // Now, we have all necessary data of mTransferable. So, we can work as
167 // internal mode.
168 mIsExternal = false;
169 // Release mTransferable because it won't be referred anymore.
170 mTransferable = nullptr;
173 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
174 const nsAString& aString)
175 : mParent(aParent),
176 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
177 mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
178 mEventMessage(aEventMessage),
179 mCursorState(false),
180 mMode(ModeForEvent(aEventMessage)),
181 mIsExternal(false),
182 mUserCancelled(false),
183 mIsCrossDomainSubFrameDrop(false),
184 mClipboardType(-1),
185 mDragImageX(0),
186 mDragImageY(0) {
187 mItems = new DataTransferItemList(this);
189 nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
191 RefPtr<nsVariantCC> variant = new nsVariantCC();
192 variant->SetAsAString(aString);
193 DebugOnly<nsresult> rvIgnored =
194 SetDataWithPrincipal(u"text/plain"_ns, variant, 0, sysPrincipal, false);
195 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
196 "Failed to set given string to the DataTransfer object");
199 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
200 const uint32_t aEffectAllowed, bool aCursorState,
201 bool aIsExternal, bool aUserCancelled,
202 bool aIsCrossDomainSubFrameDrop,
203 int32_t aClipboardType, DataTransferItemList* aItems,
204 Element* aDragImage, uint32_t aDragImageX,
205 uint32_t aDragImageY)
206 : mParent(aParent),
207 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
208 mEffectAllowed(aEffectAllowed),
209 mEventMessage(aEventMessage),
210 mCursorState(aCursorState),
211 mMode(ModeForEvent(aEventMessage)),
212 mIsExternal(aIsExternal),
213 mUserCancelled(aUserCancelled),
214 mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop),
215 mClipboardType(aClipboardType),
216 mDragImage(aDragImage),
217 mDragImageX(aDragImageX),
218 mDragImageY(aDragImageY) {
219 MOZ_ASSERT(mParent);
220 MOZ_ASSERT(aItems);
222 // We clone the items array after everything else, so that it has a valid
223 // mParent value
224 mItems = aItems->Clone(this);
225 // The items are copied from aItems into mItems. There is no need to copy
226 // the actual data in the items as the data transfer will be read only. The
227 // dragstart event is the only time when items are
228 // modifiable, but those events should have been using the first constructor
229 // above.
230 NS_ASSERTION(aEventMessage != eDragStart,
231 "invalid event type for DataTransfer constructor");
234 DataTransfer::~DataTransfer() = default;
236 // static
237 already_AddRefed<DataTransfer> DataTransfer::Constructor(
238 const GlobalObject& aGlobal) {
239 RefPtr<DataTransfer> transfer =
240 new DataTransfer(aGlobal.GetAsSupports(), eCopy, /* is external */ false,
241 /* clipboard type */ -1);
242 transfer->mEffectAllowed = nsIDragService::DRAGDROP_ACTION_NONE;
243 return transfer.forget();
246 JSObject* DataTransfer::WrapObject(JSContext* aCx,
247 JS::Handle<JSObject*> aGivenProto) {
248 return DataTransfer_Binding::Wrap(aCx, this, aGivenProto);
251 void DataTransfer::SetDropEffect(const nsAString& aDropEffect) {
252 // the drop effect can only be 'none', 'copy', 'move' or 'link'.
253 for (uint32_t e = 0; e <= nsIDragService::DRAGDROP_ACTION_LINK; e++) {
254 if (aDropEffect.EqualsASCII(sEffects[e])) {
255 // don't allow copyMove
256 if (e != (nsIDragService::DRAGDROP_ACTION_COPY |
257 nsIDragService::DRAGDROP_ACTION_MOVE)) {
258 mDropEffect = e;
260 break;
265 void DataTransfer::SetEffectAllowed(const nsAString& aEffectAllowed) {
266 if (aEffectAllowed.EqualsLiteral("uninitialized")) {
267 mEffectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
268 return;
271 static_assert(nsIDragService::DRAGDROP_ACTION_NONE == 0,
272 "DRAGDROP_ACTION_NONE constant is wrong");
273 static_assert(nsIDragService::DRAGDROP_ACTION_COPY == 1,
274 "DRAGDROP_ACTION_COPY constant is wrong");
275 static_assert(nsIDragService::DRAGDROP_ACTION_MOVE == 2,
276 "DRAGDROP_ACTION_MOVE constant is wrong");
277 static_assert(nsIDragService::DRAGDROP_ACTION_LINK == 4,
278 "DRAGDROP_ACTION_LINK constant is wrong");
280 for (uint32_t e = 0; e < ArrayLength(sEffects); e++) {
281 if (aEffectAllowed.EqualsASCII(sEffects[e])) {
282 mEffectAllowed = e;
283 break;
288 void DataTransfer::GetMozTriggeringPrincipalURISpec(
289 nsAString& aPrincipalURISpec) {
290 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
291 if (!dragSession) {
292 aPrincipalURISpec.Truncate(0);
293 return;
296 nsCOMPtr<nsIPrincipal> principal;
297 dragSession->GetTriggeringPrincipal(getter_AddRefs(principal));
298 if (!principal) {
299 aPrincipalURISpec.Truncate(0);
300 return;
303 nsAutoCString spec;
304 principal->GetAsciiSpec(spec);
305 CopyUTF8toUTF16(spec, aPrincipalURISpec);
308 nsIContentSecurityPolicy* DataTransfer::GetMozCSP() {
309 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
310 if (!dragSession) {
311 return nullptr;
313 nsCOMPtr<nsIContentSecurityPolicy> csp;
314 dragSession->GetCsp(getter_AddRefs(csp));
315 return csp;
318 already_AddRefed<FileList> DataTransfer::GetFiles(
319 nsIPrincipal& aSubjectPrincipal) {
320 return mItems->Files(&aSubjectPrincipal);
323 void DataTransfer::GetTypes(nsTArray<nsString>& aTypes,
324 CallerType aCallerType) const {
325 // When called from bindings, aTypes will be empty, but since we might have
326 // Gecko-internal callers too, clear it to be safe.
327 aTypes.Clear();
329 return mItems->GetTypes(aTypes, aCallerType);
332 bool DataTransfer::HasType(const nsAString& aType) const {
333 return mItems->HasType(aType);
336 bool DataTransfer::HasFile() const { return mItems->HasFile(); }
338 void DataTransfer::GetData(const nsAString& aFormat, nsAString& aData,
339 nsIPrincipal& aSubjectPrincipal,
340 ErrorResult& aRv) const {
341 // return an empty string if data for the format was not found
342 aData.Truncate();
344 nsCOMPtr<nsIVariant> data;
345 nsresult rv =
346 GetDataAtInternal(aFormat, 0, &aSubjectPrincipal, getter_AddRefs(data));
347 if (NS_FAILED(rv)) {
348 if (rv != NS_ERROR_DOM_INDEX_SIZE_ERR) {
349 aRv.Throw(rv);
351 return;
354 if (data) {
355 nsAutoString stringdata;
356 data->GetAsAString(stringdata);
358 // for the URL type, parse out the first URI from the list. The URIs are
359 // separated by newlines
360 nsAutoString lowercaseFormat;
361 nsContentUtils::ASCIIToLower(aFormat, lowercaseFormat);
363 if (lowercaseFormat.EqualsLiteral("url")) {
364 int32_t lastidx = 0, idx;
365 int32_t length = stringdata.Length();
366 while (lastidx < length) {
367 idx = stringdata.FindChar('\n', lastidx);
368 // lines beginning with # are comments
369 if (stringdata[lastidx] == '#') {
370 if (idx == -1) {
371 break;
373 } else {
374 if (idx == -1) {
375 aData.Assign(Substring(stringdata, lastidx));
376 } else {
377 aData.Assign(Substring(stringdata, lastidx, idx - lastidx));
379 aData =
380 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData, true);
381 return;
383 lastidx = idx + 1;
385 } else {
386 aData = stringdata;
391 void DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData,
392 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
393 RefPtr<nsVariantCC> variant = new nsVariantCC();
394 variant->SetAsAString(aData);
396 aRv = SetDataAtInternal(aFormat, variant, 0, &aSubjectPrincipal);
399 void DataTransfer::ClearData(const Optional<nsAString>& aFormat,
400 nsIPrincipal& aSubjectPrincipal,
401 ErrorResult& aRv) {
402 if (IsReadOnly()) {
403 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
404 return;
407 if (MozItemCount() == 0) {
408 return;
411 if (aFormat.WasPassed()) {
412 MozClearDataAtHelper(aFormat.Value(), 0, aSubjectPrincipal, aRv);
413 } else {
414 MozClearDataAtHelper(u""_ns, 0, aSubjectPrincipal, aRv);
418 void DataTransfer::SetMozCursor(const nsAString& aCursorState) {
419 // Lock the cursor to an arrow during the drag.
420 mCursorState = aCursorState.EqualsLiteral("default");
423 already_AddRefed<nsINode> DataTransfer::GetMozSourceNode() {
424 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
425 if (!dragSession) {
426 return nullptr;
429 nsCOMPtr<nsINode> sourceNode;
430 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
431 if (sourceNode && !nsContentUtils::LegacyIsCallerNativeCode() &&
432 !nsContentUtils::CanCallerAccess(sourceNode)) {
433 return nullptr;
436 return sourceNode.forget();
439 already_AddRefed<DOMStringList> DataTransfer::MozTypesAt(
440 uint32_t aIndex, CallerType aCallerType, ErrorResult& aRv) const {
441 // Only the first item is valid for clipboard events
442 if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
443 mEventMessage == ePaste)) {
444 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
445 return nullptr;
448 RefPtr<DOMStringList> types = new DOMStringList();
449 if (aIndex < MozItemCount()) {
450 // note that you can retrieve the types regardless of their principal
451 const nsTArray<RefPtr<DataTransferItem>>& items =
452 *mItems->MozItemsAt(aIndex);
454 bool addFile = false;
455 for (uint32_t i = 0; i < items.Length(); i++) {
456 if (items[i]->ChromeOnly() && aCallerType != CallerType::System) {
457 continue;
460 // NOTE: The reason why we get the internal type here is because we want
461 // kFileMime to appear in the types list for backwards compatibility
462 // reasons.
463 nsAutoString type;
464 items[i]->GetInternalType(type);
465 if (NS_WARN_IF(!types->Add(type))) {
466 aRv.Throw(NS_ERROR_FAILURE);
467 return nullptr;
470 if (items[i]->Kind() == DataTransferItem::KIND_FILE) {
471 addFile = true;
475 if (addFile) {
476 types->Add(u"Files"_ns);
480 return types.forget();
483 nsresult DataTransfer::GetDataAtNoSecurityCheck(const nsAString& aFormat,
484 uint32_t aIndex,
485 nsIVariant** aData) const {
486 return GetDataAtInternal(aFormat, aIndex,
487 nsContentUtils::GetSystemPrincipal(), aData);
490 nsresult DataTransfer::GetDataAtInternal(const nsAString& aFormat,
491 uint32_t aIndex,
492 nsIPrincipal* aSubjectPrincipal,
493 nsIVariant** aData) const {
494 *aData = nullptr;
496 if (aFormat.IsEmpty()) {
497 return NS_OK;
500 if (aIndex >= MozItemCount()) {
501 return NS_ERROR_DOM_INDEX_SIZE_ERR;
504 // Only the first item is valid for clipboard events
505 if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
506 mEventMessage == ePaste)) {
507 return NS_ERROR_DOM_INDEX_SIZE_ERR;
510 nsAutoString format;
511 GetRealFormat(aFormat, format);
513 MOZ_ASSERT(aSubjectPrincipal);
515 RefPtr<DataTransferItem> item = mItems->MozItemByTypeAt(format, aIndex);
516 if (!item) {
517 // The index exists but there's no data for the specified format, in this
518 // case we just return undefined
519 return NS_OK;
522 // If we have chrome only content, and we aren't chrome, don't allow access
523 if (!aSubjectPrincipal->IsSystemPrincipal() && item->ChromeOnly()) {
524 return NS_OK;
527 // DataTransferItem::Data() handles the principal checks
528 ErrorResult result;
529 nsCOMPtr<nsIVariant> data = item->Data(aSubjectPrincipal, result);
530 if (NS_WARN_IF(!data || result.Failed())) {
531 return result.StealNSResult();
534 data.forget(aData);
535 return NS_OK;
538 void DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
539 uint32_t aIndex,
540 JS::MutableHandle<JS::Value> aRetval,
541 nsIPrincipal& aSubjectPrincipal,
542 mozilla::ErrorResult& aRv) {
543 nsCOMPtr<nsIVariant> data;
544 aRv = GetDataAtInternal(aFormat, aIndex, &aSubjectPrincipal,
545 getter_AddRefs(data));
546 if (aRv.Failed()) {
547 return;
550 if (!data) {
551 aRetval.setNull();
552 return;
555 JS::Rooted<JS::Value> result(aCx);
556 if (!VariantToJsval(aCx, data, aRetval)) {
557 aRv = NS_ERROR_FAILURE;
558 return;
562 /* static */
563 bool DataTransfer::PrincipalMaySetData(const nsAString& aType,
564 nsIVariant* aData,
565 nsIPrincipal* aPrincipal) {
566 if (!aPrincipal->IsSystemPrincipal()) {
567 DataTransferItem::eKind kind = DataTransferItem::KindFromData(aData);
568 if (kind == DataTransferItem::KIND_OTHER) {
569 NS_WARNING("Disallowing adding non string/file types to DataTransfer");
570 return false;
573 if (aType.EqualsASCII(kFileMime) || aType.EqualsASCII(kFilePromiseMime)) {
574 NS_WARNING(
575 "Disallowing adding x-moz-file or x-moz-file-promize types to "
576 "DataTransfer");
577 return false;
580 // Disallow content from creating x-moz-place flavors, so that it cannot
581 // create fake Places smart queries exposing user data, but give a free
582 // pass to WebExtensions.
583 auto principal = BasePrincipal::Cast(aPrincipal);
584 if (!principal->AddonPolicy() &&
585 StringBeginsWith(aType, u"text/x-moz-place"_ns)) {
586 NS_WARNING("Disallowing adding moz-place types to DataTransfer");
587 return false;
591 return true;
594 void DataTransfer::TypesListMayHaveChanged() {
595 DataTransfer_Binding::ClearCachedTypesValue(this);
598 already_AddRefed<DataTransfer> DataTransfer::MozCloneForEvent(
599 const nsAString& aEvent, ErrorResult& aRv) {
600 RefPtr<nsAtom> atomEvt = NS_Atomize(aEvent);
601 if (!atomEvt) {
602 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
603 return nullptr;
605 EventMessage eventMessage = nsContentUtils::GetEventMessage(atomEvt);
607 RefPtr<DataTransfer> dt;
608 nsresult rv = Clone(mParent, eventMessage, false, false, getter_AddRefs(dt));
609 if (NS_FAILED(rv)) {
610 aRv.Throw(rv);
611 return nullptr;
613 return dt.forget();
616 /* static */
617 void DataTransfer::GetExternalClipboardFormats(const int32_t& aWhichClipboard,
618 const bool& aPlainTextOnly,
619 nsTArray<nsCString>* aResult) {
620 MOZ_ASSERT(aResult);
622 // NOTE: When you change this method, you may need to change
623 // GetExternalTransferableFormats() too since those methods should
624 // work similarly.
626 nsCOMPtr<nsIClipboard> clipboard =
627 do_GetService("@mozilla.org/widget/clipboard;1");
628 if (!clipboard || aWhichClipboard < 0) {
629 return;
632 if (aPlainTextOnly) {
633 bool hasType;
634 AutoTArray<nsCString, 1> unicodeMime = {nsDependentCString(kUnicodeMime)};
635 nsresult rv = clipboard->HasDataMatchingFlavors(unicodeMime,
636 aWhichClipboard, &hasType);
637 NS_SUCCEEDED(rv);
638 if (hasType) {
639 aResult->AppendElement(kUnicodeMime);
641 return;
644 // If not plain text only, then instead check all the other types
645 static const char* formats[] = {kCustomTypesMime, kFileMime, kHTMLMime,
646 kRTFMime, kURLMime, kURLDataMime,
647 kUnicodeMime, kPNGImageMime};
649 for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
650 bool hasType;
651 AutoTArray<nsCString, 1> format = {nsDependentCString(formats[f])};
652 nsresult rv =
653 clipboard->HasDataMatchingFlavors(format, aWhichClipboard, &hasType);
654 NS_SUCCEEDED(rv);
655 if (hasType) {
656 aResult->AppendElement(formats[f]);
661 /* static */
662 void DataTransfer::GetExternalTransferableFormats(
663 nsITransferable* aTransferable, bool aPlainTextOnly,
664 nsTArray<nsCString>* aResult) {
665 MOZ_ASSERT(aTransferable);
666 MOZ_ASSERT(aResult);
668 aResult->Clear();
670 // NOTE: When you change this method, you may need to change
671 // GetExternalClipboardFormats() too since those methods should
672 // work similarly.
674 AutoTArray<nsCString, 10> flavors;
675 aTransferable->FlavorsTransferableCanExport(flavors);
677 if (aPlainTextOnly) {
678 auto index = flavors.IndexOf(nsLiteralCString(kUnicodeMime));
679 if (index != flavors.NoIndex) {
680 aResult->AppendElement(nsLiteralCString(kUnicodeMime));
682 return;
685 // If not plain text only, then instead check all the other types
686 static const char* formats[] = {kCustomTypesMime, kFileMime, kHTMLMime,
687 kRTFMime, kURLMime, kURLDataMime,
688 kUnicodeMime, kPNGImageMime};
690 for (const char* format : formats) {
691 auto index = flavors.IndexOf(nsCString(format));
692 if (index != flavors.NoIndex) {
693 aResult->AppendElement(nsCString(format));
698 nsresult DataTransfer::SetDataAtInternal(const nsAString& aFormat,
699 nsIVariant* aData, uint32_t aIndex,
700 nsIPrincipal* aSubjectPrincipal) {
701 if (aFormat.IsEmpty()) {
702 return NS_OK;
705 if (IsReadOnly()) {
706 return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
709 // Specifying an index less than the current length will replace an existing
710 // item. Specifying an index equal to the current length will add a new item.
711 if (aIndex > MozItemCount()) {
712 return NS_ERROR_DOM_INDEX_SIZE_ERR;
715 // Only the first item is valid for clipboard events
716 if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
717 mEventMessage == ePaste)) {
718 return NS_ERROR_DOM_INDEX_SIZE_ERR;
721 // Don't allow the custom type to be assigned.
722 if (aFormat.EqualsLiteral(kCustomTypesMime)) {
723 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
726 if (!PrincipalMaySetData(aFormat, aData, aSubjectPrincipal)) {
727 return NS_ERROR_DOM_SECURITY_ERR;
730 return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
733 void DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
734 JS::Handle<JS::Value> aData, uint32_t aIndex,
735 nsIPrincipal& aSubjectPrincipal,
736 ErrorResult& aRv) {
737 nsCOMPtr<nsIVariant> data;
738 aRv = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData,
739 getter_AddRefs(data));
740 if (!aRv.Failed()) {
741 aRv = SetDataAtInternal(aFormat, data, aIndex, &aSubjectPrincipal);
745 void DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
746 nsIPrincipal& aSubjectPrincipal,
747 ErrorResult& aRv) {
748 if (IsReadOnly()) {
749 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
750 return;
753 if (aIndex >= MozItemCount()) {
754 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
755 return;
758 // Only the first item is valid for clipboard events
759 if (aIndex > 0 && (mEventMessage == eCut || mEventMessage == eCopy ||
760 mEventMessage == ePaste)) {
761 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
762 return;
765 MozClearDataAtHelper(aFormat, aIndex, aSubjectPrincipal, aRv);
767 // If we just cleared the 0-th index, and there are still more than 1 indexes
768 // remaining, MozClearDataAt should cause the 1st index to become the 0th
769 // index. This should _only_ happen when the MozClearDataAt function is
770 // explicitly called by script, as this behavior is inconsistent with spec.
771 // (however, so is the MozClearDataAt API)
773 if (aIndex == 0 && mItems->MozItemCount() > 1 &&
774 mItems->MozItemsAt(0)->Length() == 0) {
775 mItems->PopIndexZero();
779 void DataTransfer::MozClearDataAtHelper(const nsAString& aFormat,
780 uint32_t aIndex,
781 nsIPrincipal& aSubjectPrincipal,
782 ErrorResult& aRv) {
783 MOZ_ASSERT(!IsReadOnly());
784 MOZ_ASSERT(aIndex < MozItemCount());
785 MOZ_ASSERT(aIndex == 0 || (mEventMessage != eCut && mEventMessage != eCopy &&
786 mEventMessage != ePaste));
788 nsAutoString format;
789 GetRealFormat(aFormat, format);
791 mItems->MozRemoveByTypeAt(format, aIndex, aSubjectPrincipal, aRv);
794 void DataTransfer::SetDragImage(Element& aImage, int32_t aX, int32_t aY) {
795 if (!IsReadOnly()) {
796 mDragImage = &aImage;
797 mDragImageX = aX;
798 mDragImageY = aY;
802 void DataTransfer::UpdateDragImage(Element& aImage, int32_t aX, int32_t aY) {
803 if (mEventMessage < eDragDropEventFirst ||
804 mEventMessage > eDragDropEventLast) {
805 return;
808 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
809 if (dragSession) {
810 dragSession->UpdateDragImage(&aImage, aX, aY);
814 already_AddRefed<Promise> DataTransfer::GetFilesAndDirectories(
815 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
816 nsCOMPtr<nsINode> parentNode = do_QueryInterface(mParent);
817 if (!parentNode) {
818 aRv.Throw(NS_ERROR_FAILURE);
819 return nullptr;
822 nsCOMPtr<nsIGlobalObject> global = parentNode->OwnerDoc()->GetScopeObject();
823 MOZ_ASSERT(global);
824 if (!global) {
825 aRv.Throw(NS_ERROR_FAILURE);
826 return nullptr;
829 RefPtr<Promise> p = Promise::Create(global, aRv);
830 if (NS_WARN_IF(aRv.Failed())) {
831 return nullptr;
834 RefPtr<FileList> files = mItems->Files(&aSubjectPrincipal);
835 if (NS_WARN_IF(!files)) {
836 return nullptr;
839 Sequence<RefPtr<File>> filesSeq;
840 files->ToSequence(filesSeq, aRv);
841 if (NS_WARN_IF(aRv.Failed())) {
842 return nullptr;
845 p->MaybeResolve(filesSeq);
847 return p.forget();
850 already_AddRefed<Promise> DataTransfer::GetFiles(
851 bool aRecursiveFlag, nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
852 // Currently we don't support directories.
853 return GetFilesAndDirectories(aSubjectPrincipal, aRv);
856 void DataTransfer::AddElement(Element& aElement, ErrorResult& aRv) {
857 if (IsReadOnly()) {
858 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
859 return;
862 mDragTarget = &aElement;
865 nsresult DataTransfer::Clone(nsISupports* aParent, EventMessage aEventMessage,
866 bool aUserCancelled,
867 bool aIsCrossDomainSubFrameDrop,
868 DataTransfer** aNewDataTransfer) {
869 RefPtr<DataTransfer> newDataTransfer = new DataTransfer(
870 aParent, aEventMessage, mEffectAllowed, mCursorState, mIsExternal,
871 aUserCancelled, aIsCrossDomainSubFrameDrop, mClipboardType, mItems,
872 mDragImage, mDragImageX, mDragImageY);
874 newDataTransfer.forget(aNewDataTransfer);
875 return NS_OK;
878 already_AddRefed<nsIArray> DataTransfer::GetTransferables(
879 nsINode* aDragTarget) {
880 MOZ_ASSERT(aDragTarget);
882 Document* doc = aDragTarget->GetComposedDoc();
883 if (!doc) {
884 return nullptr;
887 return GetTransferables(doc->GetLoadContext());
890 already_AddRefed<nsIArray> DataTransfer::GetTransferables(
891 nsILoadContext* aLoadContext) {
892 nsCOMPtr<nsIMutableArray> transArray = nsArray::Create();
893 if (!transArray) {
894 return nullptr;
897 uint32_t count = MozItemCount();
898 for (uint32_t i = 0; i < count; i++) {
899 nsCOMPtr<nsITransferable> transferable = GetTransferable(i, aLoadContext);
900 if (transferable) {
901 transArray->AppendElement(transferable);
905 return transArray.forget();
908 already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
909 uint32_t aIndex, nsILoadContext* aLoadContext) {
910 if (aIndex >= MozItemCount()) {
911 return nullptr;
914 const nsTArray<RefPtr<DataTransferItem>>& item = *mItems->MozItemsAt(aIndex);
915 uint32_t count = item.Length();
916 if (!count) {
917 return nullptr;
920 nsCOMPtr<nsITransferable> transferable =
921 do_CreateInstance("@mozilla.org/widget/transferable;1");
922 if (!transferable) {
923 return nullptr;
925 transferable->Init(aLoadContext);
927 nsCOMPtr<nsIStorageStream> storageStream;
928 nsCOMPtr<nsIObjectOutputStream> stream;
930 bool added = false;
931 bool handlingCustomFormats = true;
933 // When writing the custom data, we need to ensure that there is sufficient
934 // space for a (uint32_t) data ending type, and the null byte character at
935 // the end of the nsCString. We claim that space upfront and store it in
936 // baseLength. This value will be set to zero if a write error occurs
937 // indicating that the data and length are no longer valid.
938 const uint32_t baseLength = sizeof(uint32_t) + 1;
939 uint32_t totalCustomLength = baseLength;
941 const char* knownFormats[] = {kTextMime,
942 kHTMLMime,
943 kNativeHTMLMime,
944 kRTFMime,
945 kURLMime,
946 kURLDataMime,
947 kURLDescriptionMime,
948 kURLPrivateMime,
949 kPNGImageMime,
950 kJPEGImageMime,
951 kGIFImageMime,
952 kNativeImageMime,
953 kFileMime,
954 kFilePromiseMime,
955 kFilePromiseURLMime,
956 kFilePromiseDestFilename,
957 kFilePromiseDirectoryMime,
958 kMozTextInternal,
959 kHTMLContext,
960 kHTMLInfo,
961 kImageRequestMime};
964 * Two passes are made here to iterate over all of the types. First, look for
965 * any types that are not in the list of known types. For this pass,
966 * handlingCustomFormats will be true. Data that corresponds to unknown types
967 * will be pulled out and inserted into a single type (kCustomTypesMime) by
968 * writing the data into a stream.
970 * The second pass will iterate over the formats looking for known types.
971 * These are added as is. The unknown types are all then inserted as a single
972 * type (kCustomTypesMime) in the same position of the first custom type. This
973 * model is used to maintain the format order as best as possible.
975 * The format of the kCustomTypesMime type is one or more of the following
976 * stored sequentially:
977 * <32-bit> type (only none or string is supported)
978 * <32-bit> length of format
979 * <wide string> format
980 * <32-bit> length of data
981 * <wide string> data
982 * A type of eCustomClipboardTypeId_None ends the list, without any following
983 * data.
985 do {
986 for (uint32_t f = 0; f < count; f++) {
987 RefPtr<DataTransferItem> formatitem = item[f];
988 nsCOMPtr<nsIVariant> variant = formatitem->DataNoSecurityCheck();
989 if (!variant) { // skip empty items
990 continue;
993 nsAutoString type;
994 formatitem->GetInternalType(type);
996 // If the data is of one of the well-known formats, use it directly.
997 bool isCustomFormat = true;
998 for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) {
999 if (type.EqualsASCII(knownFormats[f])) {
1000 isCustomFormat = false;
1001 break;
1005 uint32_t lengthInBytes;
1006 nsCOMPtr<nsISupports> convertedData;
1008 if (handlingCustomFormats) {
1009 if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
1010 &lengthInBytes)) {
1011 continue;
1014 // When handling custom types, add the data to the stream if this is a
1015 // custom type. If totalCustomLength is 0, then a write error occurred
1016 // on a previous item, so ignore any others.
1017 if (isCustomFormat && totalCustomLength > 0) {
1018 // If it isn't a string, just ignore it. The dataTransfer is cached in
1019 // the drag sesion during drag-and-drop, so non-strings will be
1020 // available when dragging locally.
1021 nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
1022 if (str) {
1023 nsAutoString data;
1024 str->GetData(data);
1026 if (!stream) {
1027 // Create a storage stream to write to.
1028 NS_NewStorageStream(1024, UINT32_MAX,
1029 getter_AddRefs(storageStream));
1031 nsCOMPtr<nsIOutputStream> outputStream;
1032 storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
1034 stream = NS_NewObjectOutputStream(outputStream);
1037 CheckedInt<uint32_t> formatLength =
1038 CheckedInt<uint32_t>(type.Length()) *
1039 sizeof(nsString::char_type);
1041 // The total size of the stream is the format length, the data
1042 // length, two integers to hold the lengths and one integer for
1043 // the string flag. Guard against large data by ignoring any that
1044 // don't fit.
1045 CheckedInt<uint32_t> newSize = formatLength + totalCustomLength +
1046 lengthInBytes +
1047 (sizeof(uint32_t) * 3);
1048 if (newSize.isValid()) {
1049 // If a write error occurs, set totalCustomLength to 0 so that
1050 // further processing gets ignored.
1051 nsresult rv = stream->Write32(eCustomClipboardTypeId_String);
1052 if (NS_WARN_IF(NS_FAILED(rv))) {
1053 totalCustomLength = 0;
1054 continue;
1056 rv = stream->Write32(formatLength.value());
1057 if (NS_WARN_IF(NS_FAILED(rv))) {
1058 totalCustomLength = 0;
1059 continue;
1061 MOZ_ASSERT(formatLength.isValid() &&
1062 formatLength.value() ==
1063 type.Length() * sizeof(nsString::char_type),
1064 "Why is formatLength off?");
1065 rv = stream->WriteBytes(
1066 AsBytes(Span(type.BeginReading(), type.Length())));
1067 if (NS_WARN_IF(NS_FAILED(rv))) {
1068 totalCustomLength = 0;
1069 continue;
1071 rv = stream->Write32(lengthInBytes);
1072 if (NS_WARN_IF(NS_FAILED(rv))) {
1073 totalCustomLength = 0;
1074 continue;
1076 // XXXbz it's not obvious to me that lengthInBytes is the actual
1077 // length of "data" if the variant contained an nsISupportsString
1078 // as VTYPE_INTERFACE, say. We used lengthInBytes above for
1079 // sizing, so just keep doing that.
1080 rv = stream->WriteBytes(
1081 Span(reinterpret_cast<const uint8_t*>(data.BeginReading()),
1082 lengthInBytes));
1083 if (NS_WARN_IF(NS_FAILED(rv))) {
1084 totalCustomLength = 0;
1085 continue;
1088 totalCustomLength = newSize.value();
1092 } else if (isCustomFormat && stream) {
1093 // This is the second pass of the loop (handlingCustomFormats is false).
1094 // When encountering the first custom format, append all of the stream
1095 // at this position. If totalCustomLength is 0 indicating a write error
1096 // occurred, or no data has been added to it, don't output anything,
1097 if (totalCustomLength > baseLength) {
1098 // Write out an end of data terminator.
1099 nsresult rv = stream->Write32(eCustomClipboardTypeId_None);
1100 if (NS_SUCCEEDED(rv)) {
1101 nsCOMPtr<nsIInputStream> inputStream;
1102 storageStream->NewInputStream(0, getter_AddRefs(inputStream));
1104 RefPtr<nsStringBuffer> stringBuffer =
1105 nsStringBuffer::Alloc(totalCustomLength);
1107 // Subtract off the null terminator when reading.
1108 totalCustomLength--;
1110 // Read the data from the stream and add a null-terminator as
1111 // ToString needs it.
1112 uint32_t amountRead;
1113 rv = inputStream->Read(static_cast<char*>(stringBuffer->Data()),
1114 totalCustomLength, &amountRead);
1115 if (NS_SUCCEEDED(rv)) {
1116 static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
1118 nsCString str;
1119 stringBuffer->ToString(totalCustomLength, str);
1120 nsCOMPtr<nsISupportsCString> strSupports(
1121 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
1122 strSupports->SetData(str);
1124 nsresult rv =
1125 transferable->SetTransferData(kCustomTypesMime, strSupports);
1126 if (NS_FAILED(rv)) {
1127 return nullptr;
1130 added = true;
1135 // Clear the stream so it doesn't get used again.
1136 stream = nullptr;
1137 } else {
1138 // This is the second pass of the loop and a known type is encountered.
1139 // Add it as is.
1140 if (!ConvertFromVariant(variant, getter_AddRefs(convertedData),
1141 &lengthInBytes)) {
1142 continue;
1145 // The underlying drag code uses text/unicode, so use that instead of
1146 // text/plain
1147 const char* format;
1148 NS_ConvertUTF16toUTF8 utf8format(type);
1149 if (utf8format.EqualsLiteral(kTextMime)) {
1150 format = kUnicodeMime;
1151 } else {
1152 format = utf8format.get();
1155 // If a converter is set for a format, set the converter for the
1156 // transferable and don't add the item
1157 nsCOMPtr<nsIFormatConverter> converter =
1158 do_QueryInterface(convertedData);
1159 if (converter) {
1160 transferable->AddDataFlavor(format);
1161 transferable->SetConverter(converter);
1162 continue;
1165 nsresult rv = transferable->SetTransferData(format, convertedData);
1166 if (NS_FAILED(rv)) {
1167 return nullptr;
1170 added = true;
1174 handlingCustomFormats = !handlingCustomFormats;
1175 } while (!handlingCustomFormats);
1177 // only return the transferable if data was successfully added to it
1178 if (added) {
1179 return transferable.forget();
1182 return nullptr;
1185 bool DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
1186 nsISupports** aSupports,
1187 uint32_t* aLength) const {
1188 *aSupports = nullptr;
1189 *aLength = 0;
1191 uint16_t type = aVariant->GetDataType();
1192 if (type == nsIDataType::VTYPE_INTERFACE ||
1193 type == nsIDataType::VTYPE_INTERFACE_IS) {
1194 nsCOMPtr<nsISupports> data;
1195 if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data)))) {
1196 return false;
1199 nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
1200 if (fdp) {
1201 // For flavour data providers, use 0 as the length.
1202 fdp.forget(aSupports);
1203 *aLength = 0;
1204 } else {
1205 data.forget(aSupports);
1206 *aLength = sizeof(nsISupports*);
1209 return true;
1212 nsAutoString str;
1213 nsresult rv = aVariant->GetAsAString(str);
1214 if (NS_FAILED(rv)) {
1215 return false;
1218 nsCOMPtr<nsISupportsString> strSupports(
1219 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
1220 if (!strSupports) {
1221 return false;
1224 strSupports->SetData(str);
1226 strSupports.forget(aSupports);
1228 // each character is two bytes
1229 *aLength = str.Length() * 2;
1231 return true;
1234 void DataTransfer::Disconnect() {
1235 SetMode(Mode::Protected);
1236 if (StaticPrefs::dom_events_dataTransfer_protected_enabled()) {
1237 ClearAll();
1241 void DataTransfer::ClearAll() { mItems->ClearAllItems(); }
1243 uint32_t DataTransfer::MozItemCount() const { return mItems->MozItemCount(); }
1245 nsresult DataTransfer::SetDataWithPrincipal(const nsAString& aFormat,
1246 nsIVariant* aData, uint32_t aIndex,
1247 nsIPrincipal* aPrincipal,
1248 bool aHidden) {
1249 nsAutoString format;
1250 GetRealFormat(aFormat, format);
1252 ErrorResult rv;
1253 RefPtr<DataTransferItem> item =
1254 mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
1255 /* aInsertOnly = */ false, aHidden, rv);
1256 return rv.StealNSResult();
1259 void DataTransfer::SetDataWithPrincipalFromOtherProcess(
1260 const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex,
1261 nsIPrincipal* aPrincipal, bool aHidden) {
1262 if (aFormat.EqualsLiteral(kCustomTypesMime)) {
1263 FillInExternalCustomTypes(aData, aIndex, aPrincipal);
1264 } else {
1265 nsAutoString format;
1266 GetRealFormat(aFormat, format);
1268 ErrorResult rv;
1269 RefPtr<DataTransferItem> item =
1270 mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
1271 /* aInsertOnly = */ false, aHidden, rv);
1272 if (NS_WARN_IF(rv.Failed())) {
1273 rv.SuppressException();
1278 void DataTransfer::GetRealFormat(const nsAString& aInFormat,
1279 nsAString& aOutFormat) const {
1280 // treat text/unicode as equivalent to text/plain
1281 nsAutoString lowercaseFormat;
1282 nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
1283 if (lowercaseFormat.EqualsLiteral("text") ||
1284 lowercaseFormat.EqualsLiteral("text/unicode")) {
1285 aOutFormat.AssignLiteral("text/plain");
1286 return;
1289 if (lowercaseFormat.EqualsLiteral("url")) {
1290 aOutFormat.AssignLiteral("text/uri-list");
1291 return;
1294 aOutFormat.Assign(lowercaseFormat);
1297 nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
1298 nsIPrincipal* aPrincipal,
1299 bool aHidden) {
1300 ErrorResult rv;
1301 RefPtr<DataTransferItem> item;
1303 if (strcmp(aFormat, kUnicodeMime) == 0) {
1304 item = mItems->SetDataWithPrincipal(u"text/plain"_ns, nullptr, aIndex,
1305 aPrincipal, false, aHidden, rv);
1306 if (NS_WARN_IF(rv.Failed())) {
1307 return rv.StealNSResult();
1309 return NS_OK;
1312 if (strcmp(aFormat, kURLDataMime) == 0) {
1313 item = mItems->SetDataWithPrincipal(u"text/uri-list"_ns, nullptr, aIndex,
1314 aPrincipal, false, aHidden, rv);
1315 if (NS_WARN_IF(rv.Failed())) {
1316 return rv.StealNSResult();
1318 return NS_OK;
1321 nsAutoString format;
1322 GetRealFormat(NS_ConvertUTF8toUTF16(aFormat), format);
1323 item = mItems->SetDataWithPrincipal(format, nullptr, aIndex, aPrincipal,
1324 false, aHidden, rv);
1325 if (NS_WARN_IF(rv.Failed())) {
1326 return rv.StealNSResult();
1328 return NS_OK;
1331 void DataTransfer::CacheExternalDragFormats() {
1332 // Called during the constructor to cache the formats available from an
1333 // external drag. The data associated with each format will be set to null.
1334 // This data will instead only be retrieved in FillInExternalDragData when
1335 // asked for, as it may be time consuming for the source application to
1336 // generate it.
1338 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1339 if (!dragSession) {
1340 return;
1343 // make sure that the system principal is used for external drags
1344 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1345 nsCOMPtr<nsIPrincipal> sysPrincipal;
1346 ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
1348 // there isn't a way to get a list of the formats that might be available on
1349 // all platforms, so just check for the types that can actually be imported
1350 // XXXndeakin there are some other formats but those are platform specific.
1351 // NOTE: kFileMime must have index 0
1352 const char* formats[] = {kFileMime, kHTMLMime, kURLMime,
1353 kURLDataMime, kUnicodeMime, kPNGImageMime};
1355 uint32_t count;
1356 dragSession->GetNumDropItems(&count);
1357 for (uint32_t c = 0; c < count; c++) {
1358 bool hasFileData = false;
1359 dragSession->IsDataFlavorSupported(kFileMime, &hasFileData);
1361 // First, check for the special format that holds custom types.
1362 bool supported;
1363 dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported);
1364 if (supported) {
1365 FillInExternalCustomTypes(c, sysPrincipal);
1368 for (uint32_t f = 0; f < ArrayLength(formats); f++) {
1369 // IsDataFlavorSupported doesn't take an index as an argument and just
1370 // checks if any of the items support a particular flavor, even though
1371 // the GetData method does take an index. Here, we just assume that
1372 // every item being dragged has the same set of flavors.
1373 bool supported;
1374 dragSession->IsDataFlavorSupported(formats[f], &supported);
1375 // if the format is supported, add an item to the array with null as
1376 // the data. When retrieved, GetRealData will read the data.
1377 if (supported) {
1378 CacheExternalData(formats[f], c, sysPrincipal,
1379 /* hidden = */ f && hasFileData);
1385 void DataTransfer::CacheExternalClipboardFormats(bool aPlainTextOnly) {
1386 // Called during the constructor for paste events to cache the formats
1387 // available on the clipboard. As with CacheExternalDragFormats, the
1388 // data will only be retrieved when needed.
1389 NS_ASSERTION(mEventMessage == ePaste,
1390 "caching clipboard data for invalid event");
1392 nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
1394 nsTArray<nsCString> typesArray;
1396 if (XRE_IsContentProcess()) {
1397 ContentChild::GetSingleton()->SendGetExternalClipboardFormats(
1398 mClipboardType, aPlainTextOnly, &typesArray);
1399 } else {
1400 GetExternalClipboardFormats(mClipboardType, aPlainTextOnly, &typesArray);
1403 if (aPlainTextOnly) {
1404 // The only thing that will be in types is kUnicodeMime
1405 MOZ_ASSERT(typesArray.IsEmpty() || typesArray.Length() == 1);
1406 if (typesArray.Length() == 1) {
1407 CacheExternalData(kUnicodeMime, 0, sysPrincipal, false);
1409 return;
1412 CacheExternalData(typesArray, sysPrincipal);
1415 void DataTransfer::CacheTransferableFormats() {
1416 nsCOMPtr<nsIPrincipal> sysPrincipal = nsContentUtils::GetSystemPrincipal();
1418 AutoTArray<nsCString, 10> typesArray;
1419 GetExternalTransferableFormats(mTransferable, false, &typesArray);
1421 CacheExternalData(typesArray, sysPrincipal);
1424 void DataTransfer::CacheExternalData(const nsTArray<nsCString>& aTypes,
1425 nsIPrincipal* aPrincipal) {
1426 bool hasFileData = false;
1427 for (const nsCString& type : aTypes) {
1428 if (type.EqualsLiteral(kCustomTypesMime)) {
1429 FillInExternalCustomTypes(0, aPrincipal);
1430 } else if (type.EqualsLiteral(kFileMime) && XRE_IsContentProcess()) {
1431 // We will be ignoring any application/x-moz-file files found in the paste
1432 // datatransfer within e10s, as they will fail top be sent over IPC.
1433 // Because of that, we will unset hasFileData, whether or not it would
1434 // have been set. (bug 1308007)
1435 hasFileData = false;
1436 continue;
1437 } else {
1438 // We expect that if kFileMime is supported, then it will be the either at
1439 // index 0 or at index 1 in the aTypes returned by
1440 // GetExternalClipboardFormats
1441 if (type.EqualsLiteral(kFileMime) && !XRE_IsContentProcess()) {
1442 hasFileData = true;
1444 // If we aren't the file data, and we have file data, we want to be hidden
1445 CacheExternalData(
1446 type.get(), 0, aPrincipal,
1447 /* hidden = */ !type.EqualsLiteral(kFileMime) && hasFileData);
1452 void DataTransfer::FillAllExternalData() {
1453 if (mIsExternal) {
1454 for (uint32_t i = 0; i < MozItemCount(); ++i) {
1455 const nsTArray<RefPtr<DataTransferItem>>& items = *mItems->MozItemsAt(i);
1456 for (uint32_t j = 0; j < items.Length(); ++j) {
1457 MOZ_ASSERT(items[j]->Index() == i);
1459 items[j]->FillInExternalData();
1465 void DataTransfer::FillInExternalCustomTypes(uint32_t aIndex,
1466 nsIPrincipal* aPrincipal) {
1467 RefPtr<DataTransferItem> item = new DataTransferItem(
1468 this, NS_LITERAL_STRING_FROM_CSTRING(kCustomTypesMime),
1469 DataTransferItem::KIND_STRING);
1470 item->SetIndex(aIndex);
1472 nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck();
1473 if (!variant) {
1474 return;
1477 FillInExternalCustomTypes(variant, aIndex, aPrincipal);
1480 void DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex,
1481 nsIPrincipal* aPrincipal) {
1482 char* chrs;
1483 uint32_t len = 0;
1484 nsresult rv = aData->GetAsStringWithSize(&len, &chrs);
1485 if (NS_FAILED(rv)) {
1486 return;
1489 CheckedInt<int32_t> checkedLen(len);
1490 if (!checkedLen.isValid()) {
1491 return;
1494 nsCOMPtr<nsIInputStream> stringStream;
1495 NS_NewByteInputStream(getter_AddRefs(stringStream),
1496 Span(chrs, checkedLen.value()), NS_ASSIGNMENT_ADOPT);
1498 nsCOMPtr<nsIObjectInputStream> stream = NS_NewObjectInputStream(stringStream);
1500 uint32_t type;
1501 do {
1502 rv = stream->Read32(&type);
1503 NS_ENSURE_SUCCESS_VOID(rv);
1504 if (type == eCustomClipboardTypeId_String) {
1505 uint32_t formatLength;
1506 rv = stream->Read32(&formatLength);
1507 NS_ENSURE_SUCCESS_VOID(rv);
1508 char* formatBytes;
1509 rv = stream->ReadBytes(formatLength, &formatBytes);
1510 NS_ENSURE_SUCCESS_VOID(rv);
1511 nsAutoString format;
1512 format.Adopt(reinterpret_cast<char16_t*>(formatBytes),
1513 formatLength / sizeof(char16_t));
1515 uint32_t dataLength;
1516 rv = stream->Read32(&dataLength);
1517 NS_ENSURE_SUCCESS_VOID(rv);
1518 char* dataBytes;
1519 rv = stream->ReadBytes(dataLength, &dataBytes);
1520 NS_ENSURE_SUCCESS_VOID(rv);
1521 nsAutoString data;
1522 data.Adopt(reinterpret_cast<char16_t*>(dataBytes),
1523 dataLength / sizeof(char16_t));
1525 RefPtr<nsVariantCC> variant = new nsVariantCC();
1526 rv = variant->SetAsAString(data);
1527 NS_ENSURE_SUCCESS_VOID(rv);
1529 SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
1531 } while (type != eCustomClipboardTypeId_None);
1534 void DataTransfer::SetMode(DataTransfer::Mode aMode) {
1535 if (!StaticPrefs::dom_events_dataTransfer_protected_enabled() &&
1536 aMode == Mode::Protected) {
1537 mMode = Mode::ReadOnly;
1538 } else {
1539 mMode = aMode;
1543 } // namespace mozilla::dom