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
)
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
];
78 void DataTransferItemList::Remove(uint32_t aIndex
,
79 nsIPrincipal
& aSubjectPrincipal
,
81 if (mDataTransfer
->IsReadOnly()) {
82 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
86 if (aIndex
>= Length()) {
90 ClearDataHelper(mItems
[aIndex
], aIndex
, -1, aSubjectPrincipal
, aRv
);
93 DataTransferItem
* DataTransferItemList::IndexedGetter(uint32_t aIndex
,
95 if (aIndex
>= mItems
.Length()) {
100 MOZ_ASSERT(mItems
[aIndex
]);
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()) {
115 void DataTransferItemList::Clear(nsIPrincipal
& aSubjectPrincipal
,
117 if (NS_WARN_IF(mDataTransfer
->IsReadOnly())) {
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
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
,
136 if (NS_WARN_IF(mDataTransfer
->IsReadOnly())) {
140 RefPtr
<nsVariantCC
> data(new nsVariantCC());
141 data
->SetAsAString(aData
);
144 mDataTransfer
->GetRealFormat(aType
, format
);
146 if (!DataTransfer::PrincipalMaySetData(format
, data
, &aSubjectPrincipal
)) {
147 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
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())) {
160 MOZ_ASSERT(item
->Kind() != DataTransferItem::KIND_FILE
);
165 DataTransferItem
* DataTransferItemList::Add(File
& aData
,
166 nsIPrincipal
& aSubjectPrincipal
,
168 if (mDataTransfer
->IsReadOnly()) {
172 nsCOMPtr
<nsISupports
> supports
= do_QueryObject(&aData
);
173 nsCOMPtr
<nsIWritableVariant
> data
= new nsVariantCC();
174 data
->SetAsISupports(supports
);
179 if (!DataTransfer::PrincipalMaySetData(type
, data
, &aSubjectPrincipal
)) {
180 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
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())) {
195 MOZ_ASSERT(item
->Kind() == DataTransferItem::KIND_FILE
);
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();
242 mFiles
= new FileList(mDataTransfer
);
243 mFilesPrincipal
= aPrincipal
;
247 if (!aPrincipal
->Subsumes(mFilesPrincipal
)) {
249 "This DataTransfer should only be accessed by the system "
250 "and a single principal");
255 return files
.forget();
258 void DataTransferItemList::MozRemoveByTypeAt(const nsAString
& aType
,
260 nsIPrincipal
& aSubjectPrincipal
,
262 if (NS_WARN_IF(mDataTransfer
->IsReadOnly() ||
263 aIndex
>= mIndexedItems
.Length())) {
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
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())) {
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
290 for (uint32_t i
= 0; i
< count
; ++i
) {
291 // NOTE: As this is a moz-prefixed API, it works based on internal types.
293 items
[i
]->GetInternalType(type
);
295 ClearDataHelper(items
[i
], -1, i
, aSubjectPrincipal
, aRv
);
301 DataTransferItem
* DataTransferItemList::MozItemByTypeAt(const nsAString
& aType
,
303 if (NS_WARN_IF(aIndex
>= mIndexedItems
.Length())) {
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
312 item
->GetInternalType(type
);
313 if (type
.Equals(aType
)) {
321 already_AddRefed
<DataTransferItem
> DataTransferItemList::SetDataWithPrincipal(
322 const nsAString
& aType
, nsIVariant
* aData
, uint32_t aIndex
,
323 nsIPrincipal
* aPrincipal
, bool aInsertOnly
, bool aHidden
,
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
];
331 item
->GetInternalType(type
);
332 if (type
.Equals(aType
)) {
333 if (NS_WARN_IF(aInsertOnly
)) {
334 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
338 // don't allow replacing data that has a stronger principal
340 if (NS_WARN_IF(item
->Principal() && aPrincipal
&&
341 (NS_FAILED(aPrincipal
->Subsumes(item
->Principal(),
344 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
347 item
->SetPrincipal(aPrincipal
);
349 DataTransferItem::eKind oldKind
= item
->Kind();
350 item
->SetData(aData
);
352 mDataTransfer
->TypesListMayHaveChanged();
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
) {
360 mItems
.AppendElement(item
);
361 } else if (item
->Kind() != DataTransferItem::KIND_FILE
&&
362 oldKind
== DataTransferItem::KIND_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
) {
374 return item
.forget();
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();
384 RefPtr
<DataTransferItem
> item
=
385 AppendNewItem(aIndex
, aType
, aData
, aPrincipal
, aHidden
);
387 if (item
->Kind() == DataTransferItem::KIND_FILE
) {
391 return item
.forget();
394 DataTransferItem
* DataTransferItemList::AppendNewItem(uint32_t aIndex
,
395 const nsAString
& aType
,
397 nsIPrincipal
* aPrincipal
,
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) {
421 mItems
.AppendElement(item
);
423 mDataTransfer
->TypesListMayHaveChanged();
429 void DataTransferItemList::GetTypes(nsTArray
<nsString
>& aTypes
,
430 CallerType aCallerType
) const {
431 MOZ_ASSERT(aTypes
.IsEmpty());
433 if (mIndexedItems
.IsEmpty()) {
437 bool foundFile
= false;
438 for (const RefPtr
<DataTransferItem
>& item
: mIndexedItems
[0]) {
441 // XXX Why don't we check the caller type with item's permission only
444 foundFile
= item
->Kind() == DataTransferItem::KIND_FILE
;
447 if (item
->ChromeOnly() && aCallerType
!= CallerType::System
) {
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
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.
464 for (uint32_t i
= 1; i
< mIndexedItems
.Length(); i
++) {
465 for (const RefPtr
<DataTransferItem
>& item
: mIndexedItems
[i
]) {
468 foundFile
= item
->Kind() == DataTransferItem::KIND_FILE
;
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()) {
487 for (const RefPtr
<DataTransferItem
>& item
: mIndexedItems
[0]) {
488 if (item
->IsInternalType(aType
)) {
495 bool DataTransferItemList::HasFile() const {
496 if (mIndexedItems
.IsEmpty()) {
500 for (const RefPtr
<DataTransferItem
>& item
: mIndexedItems
[0]) {
501 if (item
->Kind() == DataTransferItem::KIND_FILE
) {
508 const nsTArray
<RefPtr
<DataTransferItem
>>* DataTransferItemList::MozItemsAt(
509 uint32_t aIndex
) // -- INDEXED
511 if (aIndex
>= mIndexedItems
.Length()) {
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
536 mIndexedItems
.Clear();
537 mIndexedItems
.SetLength(1);
538 mDataTransfer
->TypesListMayHaveChanged();
540 // Re-generate files (into an empty list)
544 void DataTransferItemList::ClearDataHelper(DataTransferItem
* aItem
,
546 uint32_t aMozOffsetHint
,
547 nsIPrincipal
& aSubjectPrincipal
,
550 if (NS_WARN_IF(mDataTransfer
->IsReadOnly())) {
554 if (aItem
->Principal() && !aSubjectPrincipal
.Subsumes(aItem
->Principal())) {
555 aRv
.Throw(NS_ERROR_DOM_SECURITY_ERR
);
559 // Check if the aIndexHint is actually the index, and then remove the item
562 if (IndexedGetter(aIndexHint
, found
) == aItem
) {
563 mItems
.RemoveElementAt(aIndexHint
);
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
);
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
596 if (aItem
->Kind() == DataTransferItem::KIND_FILE
) {
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
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.
611 DataTransferItemList::GenerateFiles(mFiles
, mFilesPrincipal
);
615 void DataTransferItemList::GenerateFiles(FileList
* aFiles
,
616 nsIPrincipal
* aFilesPrincipal
) {
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()) {
626 uint32_t count
= Length();
627 for (uint32_t i
= 0; i
< count
; i
++) {
629 RefPtr
<DataTransferItem
> item
= IndexedGetter(i
, found
);
632 if (item
->Kind() == DataTransferItem::KIND_FILE
) {
633 RefPtr
<File
> file
= item
->GetAsFile(*aFilesPrincipal
, IgnoreErrors());
634 if (NS_WARN_IF(!file
)) {
637 aFiles
->Append(file
);
642 } // namespace mozilla::dom