Bug 1895153 - Implement "Find in page..." menu functionality r=android-reviewers...
[gecko.git] / dom / events / DataTransferItem.cpp
blobc5a9c306f972a53d837b0ad5a42673d73b56d7c0
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/FileSystem.h"
19 #include "mozilla/dom/FileSystemDirectoryEntry.h"
20 #include "mozilla/dom/FileSystemFileEntry.h"
21 #include "imgIContainer.h"
22 #include "imgITools.h"
23 #include "nsComponentManagerUtils.h"
24 #include "nsGlobalWindowInner.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;
79 it->mDoNotAttemptToLoadData = mDoNotAttemptToLoadData;
81 return it.forget();
84 void DataTransferItem::SetData(nsIVariant* aData) {
85 // Invalidate our file cache, we will regenerate it with the new data
86 mCachedFile = nullptr;
88 if (!aData) {
89 // We are holding a temporary null which will later be filled.
90 // These are provided by the system, and have guaranteed properties about
91 // their kind based on their type.
92 MOZ_ASSERT(!mType.IsEmpty());
93 // This type should not be provided by the OS.
94 MOZ_ASSERT(!mType.EqualsASCII(kNativeImageMime));
96 mKind = KIND_STRING;
97 for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
98 if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
99 mKind = KIND_FILE;
100 break;
104 mData = nullptr;
105 return;
108 mData = aData;
109 mKind = KindFromData(mData);
112 /* static */ DataTransferItem::eKind DataTransferItem::KindFromData(
113 nsIVariant* aData) {
114 nsCOMPtr<nsISupports> supports;
115 nsresult rv = aData->GetAsISupports(getter_AddRefs(supports));
116 if (NS_SUCCEEDED(rv) && supports) {
117 // Check if we have one of the supported file data formats
118 if (RefPtr<Blob>(do_QueryObject(supports)) ||
119 nsCOMPtr<BlobImpl>(do_QueryInterface(supports)) ||
120 nsCOMPtr<nsIFile>(do_QueryInterface(supports))) {
121 return KIND_FILE;
124 if (StaticPrefs::dom_events_dataTransfer_imageAsFile_enabled()) {
125 // Firefox internally uses imgIContainer to represent images being
126 // copied/dragged. These need to be encoded to PNG files.
127 if (nsCOMPtr<imgIContainer>(do_QueryInterface(supports))) {
128 return KIND_FILE;
133 nsAutoString string;
134 // If we can't get the data type as a string, that means that the object
135 // should be considered to be of the "other" type. This is impossible
136 // through the APIs defined by the spec, but we provide extra Moz* APIs,
137 // which allow setting of non-string data. We determine whether we can
138 // consider it a string, by calling GetAsAString, and checking for success.
139 rv = aData->GetAsAString(string);
140 if (NS_SUCCEEDED(rv)) {
141 return KIND_STRING;
144 return KIND_OTHER;
147 void DataTransferItem::FillInExternalData() {
148 if (mData || mDoNotAttemptToLoadData) {
149 return;
152 NS_ConvertUTF16toUTF8 utf8format(mType);
153 const char* format = utf8format.get();
154 if (strcmp(format, "text/uri-list") == 0) {
155 format = kURLDataMime;
158 nsCOMPtr<nsITransferable> trans = mDataTransfer->GetTransferable();
159 if (!trans) {
160 trans = do_CreateInstance("@mozilla.org/widget/transferable;1");
161 if (NS_WARN_IF(!trans)) {
162 return;
165 trans->Init(nullptr);
166 trans->AddDataFlavor(format);
168 if (mDataTransfer->GetEventMessage() == ePaste) {
169 MOZ_ASSERT(mIndex == 0, "index in clipboard must be 0");
171 nsCOMPtr<nsIClipboard> clipboard =
172 do_GetService("@mozilla.org/widget/clipboard;1");
173 if (!clipboard || mDataTransfer->ClipboardType() < 0) {
174 return;
177 nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
178 WindowContext* windowContext = nullptr;
179 if (global) {
180 const auto* innerWindow = global->GetAsInnerWindow();
181 windowContext = innerWindow ? innerWindow->GetWindowContext() : nullptr;
183 MOZ_ASSERT(windowContext);
184 nsresult rv = clipboard->GetData(trans, mDataTransfer->ClipboardType(),
185 windowContext);
186 if (NS_WARN_IF(NS_FAILED(rv))) {
187 if (rv == NS_ERROR_CONTENT_BLOCKED) {
188 // If the load of this content was blocked by Content Analysis,
189 // do not attempt to load it again.
190 mDoNotAttemptToLoadData = true;
192 return;
194 } else {
195 nsCOMPtr<nsIDragSession> dragSession =
196 mDataTransfer->GetOwnerDragSession();
197 if (!dragSession) {
198 return;
201 nsresult rv = dragSession->GetData(trans, mIndex);
202 if (NS_WARN_IF(NS_FAILED(rv))) {
203 return;
208 nsCOMPtr<nsISupports> data;
209 nsresult rv = trans->GetTransferData(format, getter_AddRefs(data));
210 if (NS_WARN_IF(NS_FAILED(rv) || !data)) {
211 return;
214 // Fill the variant
215 RefPtr<nsVariantCC> variant = new nsVariantCC();
217 eKind oldKind = Kind();
218 if (oldKind == KIND_FILE) {
219 // Because this is an external piece of data, mType is one of kFileMime,
220 // kPNGImageMime, kJPEGImageMime, or kGIFImageMime. Some of these types
221 // are passed in as a nsIInputStream which must be converted to a
222 // dom::File before storing.
223 if (nsCOMPtr<nsIInputStream> istream = do_QueryInterface(data)) {
224 RefPtr<File> file = CreateFileFromInputStream(istream);
225 if (NS_WARN_IF(!file)) {
226 return;
228 data = do_QueryObject(file);
230 variant->SetAsISupports(data);
231 } else {
232 // We have an external piece of string data. Extract it and store it in the
233 // variant
234 MOZ_ASSERT(oldKind == KIND_STRING);
236 nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
237 if (supportsstr) {
238 nsAutoString str;
239 supportsstr->GetData(str);
240 variant->SetAsAString(str);
241 } else {
242 nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
243 if (supportscstr) {
244 nsAutoCString str;
245 supportscstr->GetData(str);
246 variant->SetAsACString(str);
251 SetData(variant);
253 if (oldKind != Kind()) {
254 NS_WARNING(
255 "Clipboard data provided by the OS does not match predicted kind");
256 mDataTransfer->TypesListMayHaveChanged();
260 void DataTransferItem::GetType(nsAString& aType) {
261 // If we don't have a File, we can just put whatever our recorded internal
262 // type is.
263 if (Kind() != KIND_FILE) {
264 aType = mType;
265 return;
268 // If we do have a File, then we need to look at our File object to discover
269 // what its mime type is. We can use the System Principal here, as this
270 // information should be avaliable even if the data is currently inaccessible
271 // (for example during a dragover).
273 // XXX: This seems inefficient, as it seems like we should be able to get this
274 // data without getting the entire File object, which may require talking to
275 // the OS.
276 ErrorResult rv;
277 RefPtr<File> file = GetAsFile(*nsContentUtils::GetSystemPrincipal(), rv);
278 MOZ_ASSERT(!rv.Failed(), "Failed to get file data with system principal");
280 // If we don't actually have a file, fall back to returning the internal type.
281 if (NS_WARN_IF(!file)) {
282 aType = mType;
283 return;
286 file->GetType(aType);
289 already_AddRefed<File> DataTransferItem::GetAsFile(
290 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
291 // This is done even if we have an mCachedFile, as it performs the necessary
292 // permissions checks to ensure that we are allowed to access this type.
293 nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
294 if (NS_WARN_IF(!data || aRv.Failed())) {
295 return nullptr;
298 // We have to check our kind after getting the data, because if we have
299 // external data and the OS lied to us (which unfortunately does happen
300 // sometimes), then we might not have the same type of data as we did coming
301 // into this function.
302 if (NS_WARN_IF(mKind != KIND_FILE)) {
303 return nullptr;
306 // Generate the dom::File from the stored data, caching it so that the
307 // same object is returned in the future.
308 if (!mCachedFile) {
309 nsCOMPtr<nsISupports> supports;
310 aRv = data->GetAsISupports(getter_AddRefs(supports));
311 MOZ_ASSERT(!aRv.Failed() && supports,
312 "File objects should be stored as nsISupports variants");
313 if (aRv.Failed() || !supports) {
314 return nullptr;
317 if (RefPtr<Blob> blob = do_QueryObject(supports)) {
318 mCachedFile = blob->ToFile();
319 } else {
320 nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
321 if (NS_WARN_IF(!global)) {
322 return nullptr;
325 if (nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports)) {
326 MOZ_ASSERT(blobImpl->IsFile());
327 mCachedFile = File::Create(global, blobImpl);
328 if (NS_WARN_IF(!mCachedFile)) {
329 return nullptr;
331 } else if (nsCOMPtr<nsIFile> ifile = do_QueryInterface(supports)) {
332 mCachedFile = File::CreateFromFile(global, ifile);
333 if (NS_WARN_IF(!mCachedFile)) {
334 return nullptr;
336 } else if (nsCOMPtr<imgIContainer> img = do_QueryInterface(supports)) {
337 nsCOMPtr<imgITools> imgTools =
338 do_CreateInstance("@mozilla.org/image/tools;1");
340 nsCOMPtr<nsIInputStream> inputStream;
341 nsresult rv = imgTools->EncodeImage(img, "image/png"_ns, u""_ns,
342 getter_AddRefs(inputStream));
343 if (NS_WARN_IF(NS_FAILED(rv))) {
344 return nullptr;
347 mCachedFile = CreateFileFromInputStream(
348 inputStream, "GenericImageNamePNG", u"image/png"_ns);
349 if (NS_WARN_IF(!mCachedFile)) {
350 return nullptr;
352 } else {
353 MOZ_ASSERT(false, "One of the above code paths should be taken");
354 return nullptr;
359 RefPtr<File> file = mCachedFile;
360 return file.forget();
363 already_AddRefed<FileSystemEntry> DataTransferItem::GetAsEntry(
364 nsIPrincipal& aSubjectPrincipal, ErrorResult& aRv) {
365 RefPtr<File> file = GetAsFile(aSubjectPrincipal, aRv);
366 if (NS_WARN_IF(aRv.Failed()) || !file) {
367 return nullptr;
370 nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
371 if (NS_WARN_IF(!global)) {
372 return nullptr;
375 RefPtr<FileSystem> fs = FileSystem::Create(global);
376 RefPtr<FileSystemEntry> entry;
377 BlobImpl* impl = file->Impl();
378 MOZ_ASSERT(impl);
380 if (impl->IsDirectory()) {
381 nsAutoString fullpath;
382 impl->GetMozFullPathInternal(fullpath, aRv);
383 if (aRv.Failed()) {
384 aRv.SuppressException();
385 return nullptr;
388 nsCOMPtr<nsIFile> directoryFile;
389 // fullPath is already in unicode, we don't have to use
390 // NS_NewNativeLocalFile.
391 nsresult rv =
392 NS_NewLocalFile(fullpath, true, getter_AddRefs(directoryFile));
393 if (NS_WARN_IF(NS_FAILED(rv))) {
394 return nullptr;
397 RefPtr<Directory> directory = Directory::Create(global, directoryFile);
398 entry = new FileSystemDirectoryEntry(global, directory, nullptr, fs);
399 } else {
400 entry = new FileSystemFileEntry(global, file, nullptr, fs);
403 Sequence<RefPtr<FileSystemEntry>> entries;
404 if (!entries.AppendElement(entry, fallible)) {
405 return nullptr;
408 fs->CreateRoot(entries);
409 return entry.forget();
412 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
413 nsIInputStream* aStream) {
414 const char* key = nullptr;
415 for (uint32_t i = 0; i < ArrayLength(kFileMimeNameMap); ++i) {
416 if (mType.EqualsASCII(kFileMimeNameMap[i].mMimeName)) {
417 key = kFileMimeNameMap[i].mFileName;
418 break;
421 if (!key) {
422 MOZ_ASSERT_UNREACHABLE("Unsupported mime type");
423 key = "GenericFileName";
426 return CreateFileFromInputStream(aStream, key, mType);
429 already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
430 nsIInputStream* aStream, const char* aFileNameKey,
431 const nsAString& aContentType) {
432 nsAutoString fileName;
433 nsresult rv = nsContentUtils::GetLocalizedString(
434 nsContentUtils::eDOM_PROPERTIES, aFileNameKey, fileName);
435 if (NS_WARN_IF(NS_FAILED(rv))) {
436 return nullptr;
439 uint64_t available;
440 void* data = nullptr;
441 rv = NS_ReadInputStreamToBuffer(aStream, &data, -1, &available);
442 if (NS_WARN_IF(NS_FAILED(rv))) {
443 return nullptr;
446 nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
447 if (NS_WARN_IF(!global)) {
448 return nullptr;
451 return File::CreateMemoryFileWithLastModifiedNow(global, data, available,
452 fileName, aContentType);
455 void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
456 nsIPrincipal& aSubjectPrincipal,
457 ErrorResult& aRv) {
458 if (!aCallback) {
459 return;
462 // Theoretically this should be done inside of the runnable, as it might be an
463 // expensive operation on some systems, however we wouldn't get access to the
464 // NS_ERROR_DOM_SECURITY_ERROR messages which may be raised by this method.
465 nsCOMPtr<nsIVariant> data = Data(&aSubjectPrincipal, aRv);
466 if (NS_WARN_IF(!data || aRv.Failed())) {
467 return;
470 // We have to check our kind after getting the data, because if we have
471 // external data and the OS lied to us (which unfortunately does happen
472 // sometimes), then we might not have the same type of data as we did coming
473 // into this function.
474 if (NS_WARN_IF(mKind != KIND_STRING)) {
475 return;
478 nsAutoString stringData;
479 nsresult rv = data->GetAsAString(stringData);
480 if (NS_WARN_IF(NS_FAILED(rv))) {
481 return;
484 // Dispatch the callback to the main thread
485 class GASRunnable final : public Runnable {
486 public:
487 GASRunnable(FunctionStringCallback* aCallback, const nsAString& aStringData)
488 : mozilla::Runnable("GASRunnable"),
489 mCallback(aCallback),
490 mStringData(aStringData) {}
492 // MOZ_CAN_RUN_SCRIPT_BOUNDARY until runnables are opted into
493 // MOZ_CAN_RUN_SCRIPT. See bug 1535398.
494 MOZ_CAN_RUN_SCRIPT_BOUNDARY
495 NS_IMETHOD Run() override {
496 ErrorResult rv;
497 mCallback->Call(mStringData, rv);
498 NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
499 return rv.StealNSResult();
502 private:
503 const RefPtr<FunctionStringCallback> mCallback;
504 nsString mStringData;
507 RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
509 if (nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal()) {
510 rv = global->Dispatch(runnable.forget());
511 } else {
512 rv = NS_DispatchToMainThread(runnable);
514 if (NS_FAILED(rv)) {
515 NS_WARNING(
516 "Dispatch to main thread Failed in "
517 "DataTransferItem::GetAsString!");
521 already_AddRefed<nsIVariant> DataTransferItem::DataNoSecurityCheck() {
522 if (!mData) {
523 FillInExternalData();
525 nsCOMPtr<nsIVariant> data = mData;
526 return data.forget();
529 already_AddRefed<nsIVariant> DataTransferItem::Data(nsIPrincipal* aPrincipal,
530 ErrorResult& aRv) {
531 MOZ_ASSERT(aPrincipal);
533 // If the inbound principal is system, we can skip the below checks, as
534 // they will trivially succeed.
535 if (aPrincipal->IsSystemPrincipal()) {
536 return DataNoSecurityCheck();
539 // We should not allow raw data to be accessed from a Protected DataTransfer.
540 // We don't prevent this access if the accessing document is Chrome.
541 if (mDataTransfer->IsProtected()) {
542 return nullptr;
545 nsCOMPtr<nsIVariant> variant = DataNoSecurityCheck();
547 MOZ_ASSERT(!ChromeOnly(),
548 "Non-chrome code shouldn't see a ChromeOnly DataTransferItem");
549 if (ChromeOnly()) {
550 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
551 return nullptr;
554 bool checkItemPrincipal = mDataTransfer->IsCrossDomainSubFrameDrop() ||
555 (mDataTransfer->GetEventMessage() != eDrop &&
556 mDataTransfer->GetEventMessage() != ePaste &&
557 mDataTransfer->GetEventMessage() != eEditorInput);
559 // Check if the caller is allowed to access the drag data. Callers with
560 // chrome privileges can always read the data. During the
561 // drop event, allow retrieving the data except in the case where the
562 // source of the drag is in a child frame of the caller. In that case,
563 // we only allow access to data of the same principal. During other events,
564 // only allow access to the data with the same principal.
566 // We don't want to fail with an exception in this siutation, rather we want
567 // to just pretend as though the stored data is "nullptr". This is consistent
568 // with Chrome's behavior and is less surprising for web applications which
569 // don't expect execptions to be raised when performing certain operations.
570 if (Principal() && checkItemPrincipal && !aPrincipal->Subsumes(Principal())) {
571 return nullptr;
574 if (!variant) {
575 return nullptr;
578 nsCOMPtr<nsISupports> data;
579 nsresult rv = variant->GetAsISupports(getter_AddRefs(data));
580 if (NS_SUCCEEDED(rv) && data) {
581 nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
582 if (pt) {
583 nsIGlobalObject* go = pt->GetOwnerGlobal();
584 if (NS_WARN_IF(!go)) {
585 return nullptr;
588 nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
589 MOZ_ASSERT(sp, "This cannot fail on the main thread.");
591 nsIPrincipal* dataPrincipal = sp->GetPrincipal();
592 if (NS_WARN_IF(!dataPrincipal || !aPrincipal->Equals(dataPrincipal))) {
593 return nullptr;
598 return variant.forget();
601 } // namespace mozilla::dom