Bug 1824856 - migrate android build-bundle tasks from firefox-android. r=bhearsum...
[gecko.git] / dom / events / DataTransferItemList.cpp
blobf5c84f75f8355849cfec76dc3091eb02e3712b81
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 "DataTransferItemList.h"
9 #include "nsContentUtils.h"
10 #include "nsIGlobalObject.h"
11 #include "nsIScriptObjectPrincipal.h"
12 #include "nsIScriptGlobalObject.h"
13 #include "nsIScriptContext.h"
14 #include "nsQueryObject.h"
15 #include "nsVariant.h"
16 #include "mozilla/BasePrincipal.h"
17 #include "mozilla/ContentEvents.h"
18 #include "mozilla/EventForwards.h"
19 #include "mozilla/storage/Variant.h"
20 #include "mozilla/dom/DataTransferItemListBinding.h"
22 namespace mozilla::dom {
24 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DataTransferItemList, mDataTransfer,
25 mItems, mIndexedItems, mFiles)
26 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransferItemList)
27 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransferItemList)
29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransferItemList)
30 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
31 NS_INTERFACE_MAP_ENTRY(nsISupports)
32 NS_INTERFACE_MAP_END
34 JSObject* DataTransferItemList::WrapObject(JSContext* aCx,
35 JS::Handle<JSObject*> aGivenProto) {
36 return DataTransferItemList_Binding::Wrap(aCx, this, aGivenProto);
39 already_AddRefed<DataTransferItemList> DataTransferItemList::Clone(
40 DataTransfer* aDataTransfer) const {
41 RefPtr<DataTransferItemList> list = new DataTransferItemList(aDataTransfer);
43 // We need to clone the mItems and mIndexedItems lists while keeping the same
44 // correspondences between the mIndexedItems and mItems lists (namely, if an
45 // item is in mIndexedItems, and mItems it must have the same new identity)
47 // First, we copy over indexedItems, and clone every entry. Then, we go over
48 // mItems. For every entry, we use its mIndex property to locate it in
49 // mIndexedItems on the original DataTransferItemList, and then copy over the
50 // reference from the same index pair on the new DataTransferItemList
52 list->mIndexedItems.SetLength(mIndexedItems.Length());
53 list->mItems.SetLength(mItems.Length());
55 // Copy over mIndexedItems, cloning every entry
56 for (uint32_t i = 0; i < mIndexedItems.Length(); i++) {
57 const nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
58 nsTArray<RefPtr<DataTransferItem>>& newItems = list->mIndexedItems[i];
59 newItems.SetLength(items.Length());
60 for (uint32_t j = 0; j < items.Length(); j++) {
61 newItems[j] = items[j]->Clone(aDataTransfer);
65 // Copy over mItems, getting the actual entries from mIndexedItems
66 for (uint32_t i = 0; i < mItems.Length(); i++) {
67 uint32_t index = mItems[i]->Index();
68 MOZ_ASSERT(index < mIndexedItems.Length());
69 uint32_t subIndex = mIndexedItems[index].IndexOf(mItems[i]);
71 // Copy over the reference
72 list->mItems[i] = list->mIndexedItems[index][subIndex];
75 return list.forget();
78 void DataTransferItemList::Remove(uint32_t aIndex,
79 nsIPrincipal& aSubjectPrincipal,
80 ErrorResult& aRv) {
81 if (mDataTransfer->IsReadOnly()) {
82 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
83 return;
86 if (aIndex >= Length()) {
87 return;
90 ClearDataHelper(mItems[aIndex], aIndex, -1, aSubjectPrincipal, aRv);
93 DataTransferItem* DataTransferItemList::IndexedGetter(uint32_t aIndex,
94 bool& aFound) const {
95 if (aIndex >= mItems.Length()) {
96 aFound = false;
97 return nullptr;
100 MOZ_ASSERT(mItems[aIndex]);
101 aFound = true;
102 return mItems[aIndex];
105 uint32_t DataTransferItemList::MozItemCount() const {
106 uint32_t length = mIndexedItems.Length();
107 // XXX: Compat hack - Index 0 always exists due to changes in internals, but
108 // if it is empty, scripts using the moz* APIs should see it as not existing.
109 if (length == 1 && mIndexedItems[0].IsEmpty()) {
110 return 0;
112 return length;
115 void DataTransferItemList::Clear(nsIPrincipal& aSubjectPrincipal,
116 ErrorResult& aRv) {
117 if (NS_WARN_IF(mDataTransfer->IsReadOnly())) {
118 return;
121 uint32_t count = Length();
122 for (uint32_t i = 0; i < count; i++) {
123 // We always remove the last item first, to avoid moving items around in
124 // memory as much
125 Remove(Length() - 1, aSubjectPrincipal, aRv);
126 ENSURE_SUCCESS_VOID(aRv);
129 MOZ_ASSERT(Length() == 0);
132 DataTransferItem* DataTransferItemList::Add(const nsAString& aData,
133 const nsAString& aType,
134 nsIPrincipal& aSubjectPrincipal,
135 ErrorResult& aRv) {
136 if (NS_WARN_IF(mDataTransfer->IsReadOnly())) {
137 return nullptr;
140 RefPtr<nsVariantCC> data(new nsVariantCC());
141 data->SetAsAString(aData);
143 nsAutoString format;
144 mDataTransfer->GetRealFormat(aType, format);
146 if (!DataTransfer::PrincipalMaySetData(format, data, &aSubjectPrincipal)) {
147 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
148 return nullptr;
151 // We add the textual data to index 0. We set aInsertOnly to true, as we don't
152 // want to update an existing entry if it is already present, as per the spec.
153 RefPtr<DataTransferItem> item =
154 SetDataWithPrincipal(format, data, 0, &aSubjectPrincipal,
155 /* aInsertOnly = */ true,
156 /* aHidden = */ false, aRv);
157 if (NS_WARN_IF(aRv.Failed())) {
158 return nullptr;
160 MOZ_ASSERT(item->Kind() != DataTransferItem::KIND_FILE);
162 return item;
165 DataTransferItem* DataTransferItemList::Add(File& aData,
166 nsIPrincipal& aSubjectPrincipal,
167 ErrorResult& aRv) {
168 if (mDataTransfer->IsReadOnly()) {
169 return nullptr;
172 nsCOMPtr<nsISupports> supports = do_QueryObject(&aData);
173 nsCOMPtr<nsIWritableVariant> data = new nsVariantCC();
174 data->SetAsISupports(supports);
176 nsAutoString type;
177 aData.GetType(type);
179 if (!DataTransfer::PrincipalMaySetData(type, data, &aSubjectPrincipal)) {
180 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
181 return nullptr;
184 // We need to add this as a new item, as multiple files can't exist in the
185 // same item in the Moz DataTransfer layout. It will be appended at the end of
186 // the internal specced layout.
187 uint32_t index = mIndexedItems.Length();
188 RefPtr<DataTransferItem> item =
189 SetDataWithPrincipal(type, data, index, &aSubjectPrincipal,
190 /* aInsertOnly = */ true,
191 /* aHidden = */ false, aRv);
192 if (NS_WARN_IF(aRv.Failed())) {
193 return nullptr;
195 MOZ_ASSERT(item->Kind() == DataTransferItem::KIND_FILE);
197 return item;
200 already_AddRefed<FileList> DataTransferItemList::Files(
201 nsIPrincipal* aPrincipal) {
202 // The DataTransfer can hold data with varying principals, coming from
203 // different windows. This means that permissions checks need to be made when
204 // accessing data from the DataTransfer. With the accessor methods, this is
205 // checked by DataTransferItem::Data(), however with files, we keep a cached
206 // live copy of the files list for spec compliance.
208 // A DataTransfer is only exposed to one webpage, chrome code and expanded
209 // principals related to WebExtensions content scripts or user scripts.
210 // The chrome code should be able to see all files on the DataTransfer, while
211 // the webpage and WebExtensions content scripts and user scripts should only
212 // be able to see the files they can see.
214 // As chrome code doesn't need as strict spec compliance as web visible code,
215 // we generate a new FileList object every time you access the Files list from
216 // chrome code, but re-use the cached one when accessing from content code.
218 // For WebExtensions content scripts (expanded principals subsuming both
219 // the attached web page principal and the extension principal) and
220 // WebExtensions user scripts (expanded principals subsuming the attached
221 // web page principal but not the extension principal) we also don't cache
222 // the FileList as for chrome code (because the webpage principal and other
223 // extension content scripts/user scripts principals would not be able to
224 // access the cached FileList when accessed by a different expanded principal
225 // first, see Bug 1707214).
227 // It is not legal to expose an identical DataTransfer object is to multiple
228 // different principals without using the `Clone` method or similar to copy it
229 // first. If that happens, this method will assert, and return nullptr in
230 // release builds. If this functionality is required in the future, a more
231 // advanced caching mechanism for the FileList objects will be required.
232 RefPtr<FileList> files;
233 if (aPrincipal->IsSystemPrincipal() ||
234 // WebExtensions content scripts and user scripts.
235 nsContentUtils::IsExpandedPrincipal(aPrincipal)) {
236 files = new FileList(mDataTransfer);
237 GenerateFiles(files, aPrincipal);
238 return files.forget();
241 if (!mFiles) {
242 mFiles = new FileList(mDataTransfer);
243 mFilesPrincipal = aPrincipal;
244 RegenerateFiles();
247 if (!aPrincipal->Subsumes(mFilesPrincipal)) {
248 MOZ_ASSERT(false,
249 "This DataTransfer should only be accessed by the system "
250 "and a single principal");
251 return nullptr;
254 files = mFiles;
255 return files.forget();
258 void DataTransferItemList::MozRemoveByTypeAt(const nsAString& aType,
259 uint32_t aIndex,
260 nsIPrincipal& aSubjectPrincipal,
261 ErrorResult& aRv) {
262 if (NS_WARN_IF(mDataTransfer->IsReadOnly() ||
263 aIndex >= mIndexedItems.Length())) {
264 return;
267 bool removeAll = aType.IsEmpty();
269 nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex];
270 uint32_t count = items.Length();
271 // We remove the last item of the list repeatedly - that way we don't
272 // have to worry about modifying the loop iterator
273 if (removeAll) {
274 for (uint32_t i = 0; i < count; ++i) {
275 uint32_t index = items.Length() - 1;
276 MOZ_ASSERT(index == count - i - 1);
278 ClearDataHelper(items[index], -1, index, aSubjectPrincipal, aRv);
279 if (NS_WARN_IF(aRv.Failed())) {
280 return;
284 // items is no longer a valid reference, as removing the last element from
285 // it via ClearDataHelper invalidated it. so we can't MOZ_ASSERT that the
286 // length is now 0.
287 return;
290 for (uint32_t i = 0; i < count; ++i) {
291 // NOTE: As this is a moz-prefixed API, it works based on internal types.
292 nsAutoString type;
293 items[i]->GetInternalType(type);
294 if (type == aType) {
295 ClearDataHelper(items[i], -1, i, aSubjectPrincipal, aRv);
296 return;
301 DataTransferItem* DataTransferItemList::MozItemByTypeAt(const nsAString& aType,
302 uint32_t aIndex) {
303 if (NS_WARN_IF(aIndex >= mIndexedItems.Length())) {
304 return nullptr;
307 uint32_t count = mIndexedItems[aIndex].Length();
308 for (uint32_t i = 0; i < count; i++) {
309 RefPtr<DataTransferItem> item = mIndexedItems[aIndex][i];
310 // NOTE: As this is a moz-prefixed API it works on internal types
311 nsString type;
312 item->GetInternalType(type);
313 if (type.Equals(aType)) {
314 return item;
318 return nullptr;
321 already_AddRefed<DataTransferItem> DataTransferItemList::SetDataWithPrincipal(
322 const nsAString& aType, nsIVariant* aData, uint32_t aIndex,
323 nsIPrincipal* aPrincipal, bool aInsertOnly, bool aHidden,
324 ErrorResult& aRv) {
325 if (aIndex < mIndexedItems.Length()) {
326 nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aIndex];
327 uint32_t count = items.Length();
328 for (uint32_t i = 0; i < count; i++) {
329 RefPtr<DataTransferItem> item = items[i];
330 nsString type;
331 item->GetInternalType(type);
332 if (type.Equals(aType)) {
333 if (NS_WARN_IF(aInsertOnly)) {
334 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
335 return nullptr;
338 // don't allow replacing data that has a stronger principal
339 bool subsumes;
340 if (NS_WARN_IF(item->Principal() && aPrincipal &&
341 (NS_FAILED(aPrincipal->Subsumes(item->Principal(),
342 &subsumes)) ||
343 !subsumes))) {
344 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
345 return nullptr;
347 item->SetPrincipal(aPrincipal);
349 DataTransferItem::eKind oldKind = item->Kind();
350 item->SetData(aData);
352 mDataTransfer->TypesListMayHaveChanged();
354 if (aIndex != 0) {
355 // If the item changes from being a file to not a file or vice-versa,
356 // its presence in the mItems array may need to change.
357 if (item->Kind() == DataTransferItem::KIND_FILE &&
358 oldKind != DataTransferItem::KIND_FILE) {
359 // not file => file
360 mItems.AppendElement(item);
361 } else if (item->Kind() != DataTransferItem::KIND_FILE &&
362 oldKind == DataTransferItem::KIND_FILE) {
363 // file => not file
364 mItems.RemoveElement(item);
368 // Regenerate the Files array if we have modified a file's status
369 if (item->Kind() == DataTransferItem::KIND_FILE ||
370 oldKind == DataTransferItem::KIND_FILE) {
371 RegenerateFiles();
374 return item.forget();
377 } else {
378 // Make sure that we aren't adding past the end of the mIndexedItems array.
379 // XXX Should this be a MOZ_ASSERT instead?
380 aIndex = mIndexedItems.Length();
383 // Add the new item
384 RefPtr<DataTransferItem> item =
385 AppendNewItem(aIndex, aType, aData, aPrincipal, aHidden);
387 if (item->Kind() == DataTransferItem::KIND_FILE) {
388 RegenerateFiles();
391 return item.forget();
394 DataTransferItem* DataTransferItemList::AppendNewItem(uint32_t aIndex,
395 const nsAString& aType,
396 nsIVariant* aData,
397 nsIPrincipal* aPrincipal,
398 bool aHidden) {
399 if (mIndexedItems.Length() <= aIndex) {
400 MOZ_ASSERT(mIndexedItems.Length() == aIndex);
401 mIndexedItems.AppendElement();
403 RefPtr<DataTransferItem> item = new DataTransferItem(mDataTransfer, aType);
404 item->SetIndex(aIndex);
405 item->SetPrincipal(aPrincipal);
406 item->SetData(aData);
407 item->SetChromeOnly(aHidden);
409 mIndexedItems[aIndex].AppendElement(item);
411 // We only want to add the item to the main mItems list if the index we are
412 // adding to is 0, or the item we are adding is a file. If we add an item
413 // which is not a file to a non-zero index, invariants could be broken.
414 // (namely the invariant that there are not 2 non-file entries in the items
415 // array with the same type).
417 // We also want to update our DataTransfer's type list any time we're adding a
418 // KIND_FILE item, or an item at index 0.
419 if (item->Kind() == DataTransferItem::KIND_FILE || aIndex == 0) {
420 if (!aHidden) {
421 mItems.AppendElement(item);
423 mDataTransfer->TypesListMayHaveChanged();
426 return item;
429 void DataTransferItemList::GetTypes(nsTArray<nsString>& aTypes,
430 CallerType aCallerType) const {
431 MOZ_ASSERT(aTypes.IsEmpty());
433 if (mIndexedItems.IsEmpty()) {
434 return;
437 bool foundFile = false;
438 for (const RefPtr<DataTransferItem>& item : mIndexedItems[0]) {
439 MOZ_ASSERT(item);
441 // XXX Why don't we check the caller type with item's permission only
442 // for "Files"?
443 if (!foundFile) {
444 foundFile = item->Kind() == DataTransferItem::KIND_FILE;
447 if (item->ChromeOnly() && aCallerType != CallerType::System) {
448 continue;
451 // NOTE: The reason why we get the internal type here is because we want
452 // kFileMime to appear in the types list for backwards compatibility
453 // reasons.
454 nsAutoString type;
455 item->GetInternalType(type);
456 if (item->Kind() != DataTransferItem::KIND_FILE ||
457 type.EqualsASCII(kFileMime)) {
458 aTypes.AppendElement(type);
462 // Additional files will be added at a non-zero index.
463 if (!foundFile) {
464 for (uint32_t i = 1; i < mIndexedItems.Length(); i++) {
465 for (const RefPtr<DataTransferItem>& item : mIndexedItems[i]) {
466 MOZ_ASSERT(item);
468 foundFile = item->Kind() == DataTransferItem::KIND_FILE;
469 if (foundFile) {
470 break;
476 if (foundFile) {
477 aTypes.AppendElement(u"Files"_ns);
481 bool DataTransferItemList::HasType(const nsAString& aType) const {
482 MOZ_ASSERT(!aType.EqualsASCII("Files"), "Use HasFile instead");
483 if (mIndexedItems.IsEmpty()) {
484 return false;
487 for (const RefPtr<DataTransferItem>& item : mIndexedItems[0]) {
488 if (item->IsInternalType(aType)) {
489 return true;
492 return false;
495 bool DataTransferItemList::HasFile() const {
496 if (mIndexedItems.IsEmpty()) {
497 return false;
500 for (const RefPtr<DataTransferItem>& item : mIndexedItems[0]) {
501 if (item->Kind() == DataTransferItem::KIND_FILE) {
502 return true;
505 return false;
508 const nsTArray<RefPtr<DataTransferItem>>* DataTransferItemList::MozItemsAt(
509 uint32_t aIndex) // -- INDEXED
511 if (aIndex >= mIndexedItems.Length()) {
512 return nullptr;
515 return &mIndexedItems[aIndex];
518 void DataTransferItemList::PopIndexZero() {
519 MOZ_ASSERT(mIndexedItems.Length() > 1);
520 MOZ_ASSERT(mIndexedItems[0].IsEmpty());
522 mIndexedItems.RemoveElementAt(0);
524 // Update the index of every element which has now been shifted
525 for (uint32_t i = 0; i < mIndexedItems.Length(); i++) {
526 nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
527 for (uint32_t j = 0; j < items.Length(); j++) {
528 items[j]->SetIndex(i);
533 void DataTransferItemList::ClearAllItems() {
534 // We always need to have index 0, so don't delete that one
535 mItems.Clear();
536 mIndexedItems.Clear();
537 mIndexedItems.SetLength(1);
538 mDataTransfer->TypesListMayHaveChanged();
540 // Re-generate files (into an empty list)
541 RegenerateFiles();
544 void DataTransferItemList::ClearDataHelper(DataTransferItem* aItem,
545 uint32_t aIndexHint,
546 uint32_t aMozOffsetHint,
547 nsIPrincipal& aSubjectPrincipal,
548 ErrorResult& aRv) {
549 MOZ_ASSERT(aItem);
550 if (NS_WARN_IF(mDataTransfer->IsReadOnly())) {
551 return;
554 if (aItem->Principal() && !aSubjectPrincipal.Subsumes(aItem->Principal())) {
555 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
556 return;
559 // Check if the aIndexHint is actually the index, and then remove the item
560 // from aItems
561 bool found;
562 if (IndexedGetter(aIndexHint, found) == aItem) {
563 mItems.RemoveElementAt(aIndexHint);
564 } else {
565 mItems.RemoveElement(aItem);
568 // Check if the aMozIndexHint and aMozOffsetHint are actually the index and
569 // offset, and then remove them from mIndexedItems
570 MOZ_ASSERT(aItem->Index() < mIndexedItems.Length());
571 nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[aItem->Index()];
572 if (aMozOffsetHint < items.Length() && aItem == items[aMozOffsetHint]) {
573 items.RemoveElementAt(aMozOffsetHint);
574 } else {
575 items.RemoveElement(aItem);
578 mDataTransfer->TypesListMayHaveChanged();
580 // Check if we should remove the index. We never remove index 0.
581 if (items.Length() == 0 && aItem->Index() != 0) {
582 mIndexedItems.RemoveElementAt(aItem->Index());
584 // Update the index of every element which has now been shifted
585 for (uint32_t i = aItem->Index(); i < mIndexedItems.Length(); i++) {
586 nsTArray<RefPtr<DataTransferItem>>& items = mIndexedItems[i];
587 for (uint32_t j = 0; j < items.Length(); j++) {
588 items[j]->SetIndex(i);
593 // Give the removed item the invalid index
594 aItem->SetIndex(-1);
596 if (aItem->Kind() == DataTransferItem::KIND_FILE) {
597 RegenerateFiles();
601 void DataTransferItemList::RegenerateFiles() {
602 // We don't want to regenerate the files list unless we already have a files
603 // list. That way we can avoid the unnecessary work if the user never touches
604 // the files list.
605 if (mFiles) {
606 // We clear the list rather than performing smaller updates, because it
607 // simplifies the logic greatly on this code path, which should be very
608 // infrequently used.
609 mFiles->Clear();
611 DataTransferItemList::GenerateFiles(mFiles, mFilesPrincipal);
615 void DataTransferItemList::GenerateFiles(FileList* aFiles,
616 nsIPrincipal* aFilesPrincipal) {
617 MOZ_ASSERT(aFiles);
618 MOZ_ASSERT(aFilesPrincipal);
620 // For non-system principals, the Files list should be empty if the
621 // DataTransfer is protected.
622 if (!aFilesPrincipal->IsSystemPrincipal() && mDataTransfer->IsProtected()) {
623 return;
626 uint32_t count = Length();
627 for (uint32_t i = 0; i < count; i++) {
628 bool found;
629 RefPtr<DataTransferItem> item = IndexedGetter(i, found);
630 MOZ_ASSERT(found);
632 if (item->Kind() == DataTransferItem::KIND_FILE) {
633 RefPtr<File> file = item->GetAsFile(*aFilesPrincipal, IgnoreErrors());
634 if (NS_WARN_IF(!file)) {
635 continue;
637 aFiles->Append(file);
642 } // namespace mozilla::dom