Bug 1826136 [wpt PR 39338] - Update wpt metadata, a=testonly
[gecko.git] / dom / events / DataTransferItem.cpp
blob94869b4c4ee1a9c21425403f97300c2b0a46a458
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 "DataTransferItem.h"
8 #include "DataTransferItemList.h"
10 #include "mozilla/Attributes.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/ContentEvents.h"
13 #include "mozilla/EventForwards.h"
14 #include "mozilla/StaticPrefs_dom.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/DataTransferItemBinding.h"
17 #include "mozilla/dom/Directory.h"
18 #include "mozilla/dom/Event.h"
19 #include "mozilla/dom/FileSystem.h"
20 #include "mozilla/dom/FileSystemDirectoryEntry.h"
21 #include "mozilla/dom/FileSystemFileEntry.h"
22 #include "imgIContainer.h"
23 #include "imgITools.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsIClipboard.h"
26 #include "nsIFile.h"
27 #include "nsIInputStream.h"
28 #include "nsISupportsPrimitives.h"
29 #include "nsIScriptObjectPrincipal.h"
30 #include "nsNetUtil.h"
31 #include "nsQueryObject.h"
32 #include "nsContentUtils.h"
33 #include "nsThreadUtils.h"
34 #include "nsVariant.h"
36 namespace {
38 struct FileMimeNameData {
39 const char* mMimeName;
40 const char* mFileName;
43 FileMimeNameData kFileMimeNameMap[] = {
44 {kFileMime, "GenericFileName"},
45 {kPNGImageMime, "GenericImageNamePNG"},
48 } // anonymous namespace
50 namespace mozilla::dom {
52 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItem, mData, mPrincipal,
53 mDataTransfer, mCachedFile)
54 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItem)
55 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItem)
57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItem)
58 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
59 NS_INTERFACE_MAP_ENTRY(nsISupports)
60 NS_INTERFACE_MAP_END
62 JSObject* DataTransferItem::WrapObject(JSContext* aCx,
63 JS::Handle<JSObject*> aGivenProto) {
64 return DataTransferItem_Binding::Wrap(aCx, this, aGivenProto);
67 already_AddRefed<DataTransferItem> DataTransferItem::Clone(
68 DataTransfer* aDataTransfer) const {
69 MOZ_ASSERT(aDataTransfer);
71 RefPtr<DataTransferItem> it = new DataTransferItem(aDataTransfer, mType);
73 // Copy over all of the fields
74 it->mKind = mKind;
75 it->mIndex = mIndex;
76 it->mData = mData;
77 it->mPrincipal = mPrincipal;
78 it->mChromeOnly = mChromeOnly;
80 return it.forget();
83 void DataTransferItem::SetData(nsIVariant* aData) {
84 // Invalidate our file cache, we will regenerate it with the new data
85 mCachedFile = nullptr;
87 if (!aData) {
88 // We are holding a temporary null which will later be filled.
89 // These are provided by the system, and have guaranteed properties about
90 // their kind based on their type.
91 MOZ_ASSERT(!mType.IsEmpty());
92 // This type should not be provided by the OS.
93 MOZ_ASSERT(!mType.EqualsASCII(kNativeImageMime));
95 mKind = KIND_STRING;
96 for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
97 if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
98 mKind = KIND_FILE;
99 break;
103 mData = nullptr;
104 return;
107 mData = aData;
108 mKind = KindFromData(mData);
111 /* static */ DataTransferItem::eKind DataTransferItem::KindFromData(
112 nsIVariant* aData) {
113 nsCOMPtr<nsISupports> supports;
114 nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
115 if (NS_SUCCEEDED(rv) && supports) {
116 // Check if we have one of the supported file data formats
117 if (RefPtr<Blob>(do_QueryObject(supports)) ||
118 nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
119 nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
120 return KIND_FILE;
123 if (StaticPrefs::dom_events_dataTransfer_imageAsFile_enabled()) {
124 // Firefox internally uses imgIContainer to represent images being
125 // copied/dragged. These need to be encoded to PNG files.
126 if (nsCOMPtr<imgIContainer>(do_QueryInterface(supports))) {
127 return KIND_FILE;
132 nsAutoString string;
133 // If we can't get the data type as a string, that means that the object
134 // should be considered to be of the "other" type. This is impossible
135 // through the APIs defined by the spec, but we provide extra Moz* APIs,
136 // which allow setting of non-string data. We determine whether we can
137 // consider it a string, by calling GetAsAString, and checking for success.
138 rv = aData->GetAsAString(string);
139 if (NS_SUCCEEDED(rv)) {
140 return KIND_STRING;
143 return KIND_OTHER;
146 void DataTransferItem::FillInExternalData() {
147 if (mData) {
148 return;
151 NS_ConvertUTF16toUTF8 utf8format(mType);
152 const char* format = utf8format.get();
153 if (strcmp(format, "text/uri-list") == 0) {
154 format = kURLDataMime;
157 nsCOMPtr<nsITransferable> trans = mDataTransfer->GetTransferable();
158 if (!trans) {
159 trans = do_CreateInstance("@mozilla.org/widget/transferable;1");
160 if (NS_WARN_IF(!trans)) {
161 return;
164 trans->Init(nullptr);
165 trans->AddDataFlavor(format);
167 if (mDataTransfer->GetEventMessage() == ePaste) {
168 MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
170 nsCOMPtr<nsIClipboard> clipboard =
171 do_GetService("@mozilla.org/widget/clipboard;1");
172 if (!clipboard || mDataTransfer->ClipboardType() < 0) {
173 return;
176 nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType());
177 if (NS_WARN_IF(NS_FAILED(rv))) {
178 return;
180 } else {
181 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
182 if (!dragSession) {
183 return;
186 nsresult rv = dragSession->GetData(trans, mIndex);
187 if (NS_WARN_IF(NS_FAILED(rv))) {
188 return;
193 nsCOMPtr<nsISupports> data;
194 nsresult rv = trans->GetTransferData(format, getter_AddRefs(data));
195 if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
196 return;
199 // Fill the variant
200 RefPtr<nsVariantCC> variant = new nsVariantCC();
202 eKind oldKind = Kind();
203 if (oldKind == KIND_FILE) {
204 // Because this is an external piece of data, mType is one of kFileMime,
205 // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
206 // are passed in as a nsIInputStream which must be converted to a
207 // dom::File before storing.
208 if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
209 RefPtr<File> file = CreateFileFromInputStream(istream);
210 if (NS_WARN_IF(!file)) {
211 return;
213 data = do_QueryObject(file);
215 variant->SetAsISupports(data);
216 } else {
217 // We have an external piece of string data. Extract it and store it in the
218 // variant
219 MOZ_ASSERT(oldKind == KIND_STRING);
221 nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
222 if (supportsstr) {
223 nsAutoString str;
224 supportsstr->GetData(str);
225 variant->SetAsAString(str);
226 } else {
227 nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
228 if (supportscstr) {
229 nsAutoCString str;
230 supportscstr->GetData(str);
231 variant->SetAsACString(str);
236 SetData(variant);
238 if (oldKind != Kind()) {
239 NS_WARNING(
240 "Clipboard data provided by the OS does not match predicted kind");
241 mDataTransfer->TypesListMayHaveChanged();
245 void DataTransferItem::GetType(nsAString& aType) {
246 // If we don't have a File, we can just put whatever our recorded internal
247 // type is.
248 if (Kind() != KIND_FILE) {
249 aType = mType;
250 return;
253 // If we do have a File, then we need to look at our File object to discover
254 // what its mime type is. We can use the System Principal here, as this
255 // information should be avaliable even if the data is currently inaccessible
256 // (for example during a dragover).
258 // XXX: This seems inefficient, as it seems like we should be able to get this
259 // data without getting the entire File object, which may require talking to
260 // the OS.
261 ErrorResult rv;
262 RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv);
263 MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal");
265 // If we don't actually have a file, fall back to returning the internal type.
266 if (NS_WARN_IF(!file)) {
267 aType = mType;
268 return;
271 file->GetType(aType);
274 already_AddRefed<File> DataTransferItem::GetAsFile(
275 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
276 // This is done even if we have an mCachedFile, as it performs the necessary
277 // permissions checks to ensure that we are allowed to access this type.
278 nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
279 if (NS_WARN_IF(!data || aRv.Failed())) {
280 return nullptr;
283 // We have to check our kind after getting the data, because if we have
284 // external data and the OS lied to us (which unfortunately does happen
285 // sometimes), then we might not have the same type of data as we did coming
286 // into this function.
287 if (NS_WARN_IF(mKind != KIND_FILE)) {
288 return nullptr;
291 // Generate the dom::File from the stored data, caching it so that the
292 // same object is returned in the future.
293 if (!mCachedFile) {
294 nsCOMPtr<nsISupports> supports;
295 aRv = data->GetAsISupports(getter_AddRefs(supports));
296 MOZ_ASSERT(!aRv.Failed() && supports,
297 "File objects should be stored as nsISupports variants");
298 if (aRv.Failed() || !supports) {
299 return nullptr;
302 if (RefPtr<Blob> blob = do_QueryObject(supports)) {
303 mCachedFile = blob->ToFile();
304 } else {
305 nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
306 if (NS_WARN_IF(!global)) {
307 return nullptr;
310 if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
311 MOZ_ASSERT(blobImpl->IsFile());
312 mCachedFile = File::Create(global, blobImpl);
313 if (NS_WARN_IF(!mCachedFile)) {
314 return nullptr;
316 } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
317 mCachedFile = File::CreateFromFile(global, ifile);
318 if (NS_WARN_IF(!mCachedFile)) {
319 return nullptr;
321 } else if (nsCOMPtr<imgIContainer> img = do_QueryInterface(supports)) {
322 nsCOMPtr<imgITools> imgTools =
323 do_CreateInstance("@mozilla.org/image/tools;1");
325 nsCOMPtr<nsIInputStream> inputStream;
326 nsresult rv = imgTools->EncodeImage(img, "image/png"_ns, u""_ns,
327 getter_AddRefs(inputStream));
328 if (NS_WARN_IF(NS_FAILED(rv))) {
329 return nullptr;
332 mCachedFile = CreateFileFromInputStream(
333 inputStream, "GenericImageNamePNG", u"image/png"_ns);
334 if (NS_WARN_IF(!mCachedFile)) {
335 return nullptr;
337 } else {
338 MOZ_ASSERT(false, "One of the above code paths should be taken");
339 return nullptr;
344 RefPtr<File> file = mCachedFile;
345 return file.forget();
348 already_AddRefed<FileSystemEntry> DataTransferItem::GetAsEntry(
349 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
350 RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv);
351 if (NS_WARN_IF(aRv.Failed()) || !file) {
352 return nullptr;
355 nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
356 if (NS_WARN_IF(!global)) {
357 return nullptr;
360 RefPtr<FileSystem> fs = FileSystem::Create(global);
361 RefPtr<FileSystemEntry> entry;
362 BlobImpl* impl = file->Impl();
363 MOZ_ASSERT(impl);
365 if (impl->IsDirectory()) {
366 nsAutoString fullpath;
367 impl->GetMozFullPathInternal(fullpath, aRv);
368 if (aRv.Failed()) {
369 aRv.SuppressException();
370 return nullptr;
373 nsCOMPtr<nsIFile> directoryFile;
374 // fullPath is already in unicode, we don't have to use
375 // NS_NewNativeLocalFile.
376 nsresult rv =
377 NS_NewLocalFile(fullpath, true, getter_AddRefs(directoryFile));
378 if (NS_WARN_IF(NS_FAILED(rv))) {
379 return nullptr;
382 RefPtr<Directory> directory = Directory::Create(global, directoryFile);
383 entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs);
384 } else {
385 entry = new FileSystemFileEntry(global, file, nullptr, fs);
388 Sequence<RefPtr<FileSystemEntry>> entries;
389 if (!entries.AppendElement(entry, fallible)) {
390 return nullptr;
393 fs->CreateRoot(entries);
394 return entry.forget();
397 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
398 nsIInputStream* aStream) {
399 const char* key = nullptr;
400 for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
401 if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
402 key = kFileMimeNameMap[i].mFileName;
403 break;
406 if (!key) {
407 MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
408 key = "GenericFileName";
411 return CreateFileFromInputStream(aStream, key, mType);
414 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
415 nsIInputStream* aStream, const char* aFileNameKey,
416 const nsAString& aContentType) {
417 nsAutoString fileName;
418 nsresult rv = nsContentUtils::GetLocalizedString(
419 nsContentUtils::eDOM_PROPERTIES, aFileNameKey, fileName);
420 if (NS_WARN_IF(NS_FAILED(rv))) {
421 return nullptr;
424 uint64_t available;
425 void* data = nullptr;
426 rv = NS_ReadInputStreamToBuffer(aStream, &data, -1, &available);
427 if (NS_WARN_IF(NS_FAILED(rv))) {
428 return nullptr;
431 nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
432 if (NS_WARN_IF(!global)) {
433 return nullptr;
436 return File::CreateMemoryFileWithLastModifiedNow(global, data, available,
437 fileName, aContentType);
440 void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
441 nsIPrincipal& aSubjectPrincipal,
442 ErrorResult& aRv) {
443 if (!aCallback) {
444 return;
447 // Theoretically this should be done inside of the runnable, as it might be an
448 // expensive operation on some systems, however we wouldn't get access to the
449 // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
450 nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
451 if (NS_WARN_IF(!data || aRv.Failed())) {
452 return;
455 // We have to check our kind after getting the data, because if we have
456 // external data and the OS lied to us (which unfortunately does happen
457 // sometimes), then we might not have the same type of data as we did coming
458 // into this function.
459 if (NS_WARN_IF(mKind != KIND_STRING)) {
460 return;
463 nsAutoString stringData;
464 nsresult rv = data->GetAsAString(stringData);
465 if (NS_WARN_IF(NS_FAILED(rv))) {
466 return;
469 // Dispatch the callback to the main thread
470 class GASRunnable final : public Runnable {
471 public:
472 GASRunnable(FunctionStringCallback* aCallback, const nsAString& aStringData)
473 : mozilla::Runnable("GASRunnable"),
474 mCallback(aCallback),
475 mStringData(aStringData) {}
477 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
478 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
479 MOZ_CAN_RUN_SCRIPT_BOUNDARY
480 NS_IMETHOD Run() override {
481 ErrorResult rv;
482 mCallback->Call(mStringData, rv);
483 NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
484 return rv.StealNSResult();
487 private:
488 const RefPtr<FunctionStringCallback> mCallback;
489 nsString mStringData;
492 RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
494 // DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
495 // nsPIDOMWindowOuter, null
496 nsISupports* parent = mDataTransfer->GetParentObject();
497 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(parent);
498 if (parent && !global) {
499 if (nsCOMPtr<dom::EventTarget> target = do_QueryInterface(parent)) {
500 global = target->GetOwnerGlobal();
501 } else if (RefPtr<Event> event = do_QueryObject(parent)) {
502 global = event->GetParentObject();
505 if (global) {
506 rv = global->Dispatch(TaskCategory::Other, runnable.forget());
507 } else {
508 rv = NS_DispatchToMainThread(runnable);
510 if (NS_FAILED(rv)) {
511 NS_WARNING(
512 "Dispatch to main thread Failed in "
513 "DataTransferItem::GetAsString!");
517 already_AddRefed<nsIVariant> DataTransferItem::DataNoSecurityCheck() {
518 if (!mData) {
519 FillInExternalData();
521 nsCOMPtr<nsIVariant> data = mData;
522 return data.forget();
525 already_AddRefed<nsIVariant> DataTransferItem::Data(nsIPrincipal* aPrincipal,
526 ErrorResult& aRv) {
527 MOZ_ASSERT(aPrincipal);
529 // If the inbound principal is system, we can skip the below checks, as
530 // they will trivially succeed.
531 if (aPrincipal->IsSystemPrincipal()) {
532 return DataNoSecurityCheck();
535 // We should not allow raw data to be accessed from a Protected DataTransfer.
536 // We don't prevent this access if the accessing document is Chrome.
537 if (mDataTransfer->IsProtected()) {
538 return nullptr;
541 nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck();
543 MOZ_ASSERT(!ChromeOnly(),
544 "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
545 if (ChromeOnly()) {
546 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
547 return nullptr;
550 bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() ||
551 (mDataTransfer->GetEventMessage() != eDrop &&
552 mDataTransfer->GetEventMessage() != ePaste &&
553 mDataTransfer->GetEventMessage() != eEditorInput);
555 // Check if the caller is allowed to access the drag data. Callers with
556 // chrome privileges can always read the data. During the
557 // drop event, allow retrieving the data except in the case where the
558 // source of the drag is in a child frame of the caller. In that case,
559 // we only allow access to data of the same principal. During other events,
560 // only allow access to the data with the same principal.
562 // We don't want to fail with an exception in this siutation, rather we want
563 // to just pretend as though the stored data is "nullptr". This is consistent
564 // with Chrome's behavior and is less surprising for web applications which
565 // don't expect execptions to be raised when performing certain operations.
566 if (Principal() && checkItemPrincipal && !aPrincipal->Subsumes(Principal())) {
567 return nullptr;
570 if (!variant) {
571 return nullptr;
574 nsCOMPtr<nsISupports> data;
575 nsresult rv = variant->GetAsISupports(getter_AddRefs(data));
576 if (NS_SUCCEEDED(rv) && data) {
577 nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
578 if (pt) {
579 nsIGlobalObject* go = pt->GetOwnerGlobal();
580 if (NS_WARN_IF(!go)) {
581 return nullptr;
584 nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
585 MOZ_ASSERT(sp, "This cannot fail on the main thread.");
587 nsIPrincipal* dataPrincipal = sp->GetPrincipal();
588 if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) {
589 return nullptr;
594 return variant.forget();
597 already_AddRefed<nsIGlobalObject>
598 DataTransferItem::GetGlobalFromDataTransfer() {
599 nsCOMPtr<nsIGlobalObject> global;
600 // This is annoying, but DataTransfer may have various things as parent.
601 nsCOMPtr<EventTarget> target =
602 do_QueryInterface(mDataTransfer->GetParentObject());
603 if (target) {
604 global = target->GetOwnerGlobal();
605 } else {
606 RefPtr<Event> event = do_QueryObject(mDataTransfer->GetParentObject());
607 if (event) {
608 global = event->GetParentObject();
612 return global.forget();
615 } // namespace mozilla::dom