Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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/BasicEvents.h"
10 #include "DataTransfer.h"
12 #include "nsIDOMDocument.h"
13 #include "nsIVariant.h"
14 #include "nsISupportsPrimitives.h"
15 #include "nsIScriptSecurityManager.h"
16 #include "mozilla/dom/DOMStringList.h"
17 #include "nsError.h"
18 #include "nsIDragService.h"
19 #include "nsIClipboard.h"
20 #include "nsContentUtils.h"
21 #include "nsIContent.h"
22 #include "nsCRT.h"
23 #include "nsIScriptObjectPrincipal.h"
24 #include "nsIScriptContext.h"
25 #include "nsIDocument.h"
26 #include "nsIScriptGlobalObject.h"
27 #include "mozilla/dom/DataTransferBinding.h"
28 #include "mozilla/dom/Element.h"
29 #include "mozilla/dom/BindingUtils.h"
31 namespace mozilla {
32 namespace dom {
34 NS_IMPL_CYCLE_COLLECTION_CLASS(DataTransfer)
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DataTransfer)
37 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
38 if (tmp->mFiles) {
39 tmp->mFiles->Disconnect();
40 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFiles)
41 }
42 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragTarget)
43 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDragImage)
44 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
45 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DataTransfer)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFiles)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragTarget)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDragImage)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(DataTransfer)
55 NS_IMPL_CYCLE_COLLECTING_ADDREF(DataTransfer)
56 NS_IMPL_CYCLE_COLLECTING_RELEASE(DataTransfer)
58 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DataTransfer)
59 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
60 NS_INTERFACE_MAP_ENTRY(mozilla::dom::DataTransfer)
61 NS_INTERFACE_MAP_ENTRY(nsIDOMDataTransfer)
62 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDataTransfer)
63 NS_INTERFACE_MAP_END
65 // the size of the array
66 const char DataTransfer::sEffects[8][9] = {
67 "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"
68 };
70 DataTransfer::DataTransfer(nsISupports* aParent, uint32_t aEventType,
71 bool aIsExternal, int32_t aClipboardType)
72 : mParent(aParent),
73 mEventType(aEventType),
74 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
75 mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED),
76 mCursorState(false),
77 mReadOnly(true),
78 mIsExternal(aIsExternal),
79 mUserCancelled(false),
80 mIsCrossDomainSubFrameDrop(false),
81 mClipboardType(aClipboardType),
82 mDragImageX(0),
83 mDragImageY(0)
84 {
85 MOZ_ASSERT(mParent);
86 SetIsDOMBinding();
87 // For these events, we want to be able to add data to the data transfer, so
88 // clear the readonly state. Otherwise, the data is already present. For
89 // external usage, cache the data from the native clipboard or drag.
90 if (aEventType == NS_CUT ||
91 aEventType == NS_COPY ||
92 aEventType == NS_DRAGDROP_START ||
93 aEventType == NS_DRAGDROP_GESTURE) {
94 mReadOnly = false;
95 } else if (mIsExternal) {
96 if (aEventType == NS_PASTE) {
97 CacheExternalClipboardFormats();
98 } else if (aEventType >= NS_DRAGDROP_EVENT_START && aEventType <= NS_DRAGDROP_LEAVE_SYNTH) {
99 CacheExternalDragFormats();
100 }
101 }
102 }
104 DataTransfer::DataTransfer(nsISupports* aParent,
105 uint32_t aEventType,
106 const uint32_t aEffectAllowed,
107 bool aCursorState,
108 bool aIsExternal,
109 bool aUserCancelled,
110 bool aIsCrossDomainSubFrameDrop,
111 int32_t aClipboardType,
112 nsTArray<nsTArray<TransferItem> >& aItems,
113 Element* aDragImage,
114 uint32_t aDragImageX,
115 uint32_t aDragImageY)
116 : mParent(aParent),
117 mEventType(aEventType),
118 mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE),
119 mEffectAllowed(aEffectAllowed),
120 mCursorState(aCursorState),
121 mReadOnly(true),
122 mIsExternal(aIsExternal),
123 mUserCancelled(aUserCancelled),
124 mIsCrossDomainSubFrameDrop(aIsCrossDomainSubFrameDrop),
125 mClipboardType(aClipboardType),
126 mItems(aItems),
127 mDragImage(aDragImage),
128 mDragImageX(aDragImageX),
129 mDragImageY(aDragImageY)
130 {
131 MOZ_ASSERT(mParent);
132 SetIsDOMBinding();
133 // The items are copied from aItems into mItems. There is no need to copy
134 // the actual data in the items as the data transfer will be read only. The
135 // draggesture and dragstart events are the only times when items are
136 // modifiable, but those events should have been using the first constructor
137 // above.
138 NS_ASSERTION(aEventType != NS_DRAGDROP_GESTURE &&
139 aEventType != NS_DRAGDROP_START,
140 "invalid event type for DataTransfer constructor");
141 }
143 DataTransfer::~DataTransfer()
144 {
145 if (mFiles) {
146 mFiles->Disconnect();
147 }
148 }
150 // static
151 already_AddRefed<DataTransfer>
152 DataTransfer::Constructor(const GlobalObject& aGlobal,
153 const nsAString& aEventType, bool aIsExternal,
154 ErrorResult& aRv)
155 {
156 nsAutoCString onEventType("on");
157 AppendUTF16toUTF8(aEventType, onEventType);
158 nsCOMPtr<nsIAtom> eventTypeAtom = do_GetAtom(onEventType);
159 if (!eventTypeAtom) {
160 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
161 return nullptr;
162 }
164 uint32_t eventType = nsContentUtils::GetEventId(eventTypeAtom);
165 nsRefPtr<DataTransfer> transfer = new DataTransfer(aGlobal.GetAsSupports(),
166 eventType, aIsExternal,
167 -1);
168 return transfer.forget();
169 }
171 JSObject*
172 DataTransfer::WrapObject(JSContext* aCx)
173 {
174 return DataTransferBinding::Wrap(aCx, this);
175 }
177 NS_IMETHODIMP
178 DataTransfer::GetDropEffect(nsAString& aDropEffect)
179 {
180 nsString dropEffect;
181 GetDropEffect(dropEffect);
182 aDropEffect = dropEffect;
183 return NS_OK;
184 }
186 NS_IMETHODIMP
187 DataTransfer::SetDropEffect(const nsAString& aDropEffect)
188 {
189 // the drop effect can only be 'none', 'copy', 'move' or 'link'.
190 for (uint32_t e = 0; e <= nsIDragService::DRAGDROP_ACTION_LINK; e++) {
191 if (aDropEffect.EqualsASCII(sEffects[e])) {
192 // don't allow copyMove
193 if (e != (nsIDragService::DRAGDROP_ACTION_COPY |
194 nsIDragService::DRAGDROP_ACTION_MOVE))
195 mDropEffect = e;
196 break;
197 }
198 }
200 return NS_OK;
201 }
203 NS_IMETHODIMP
204 DataTransfer::GetEffectAllowed(nsAString& aEffectAllowed)
205 {
206 nsString effectAllowed;
207 GetEffectAllowed(effectAllowed);
208 aEffectAllowed = effectAllowed;
209 return NS_OK;
210 }
212 NS_IMETHODIMP
213 DataTransfer::SetEffectAllowed(const nsAString& aEffectAllowed)
214 {
215 if (aEffectAllowed.EqualsLiteral("uninitialized")) {
216 mEffectAllowed = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
217 return NS_OK;
218 }
220 static_assert(nsIDragService::DRAGDROP_ACTION_NONE == 0,
221 "DRAGDROP_ACTION_NONE constant is wrong");
222 static_assert(nsIDragService::DRAGDROP_ACTION_COPY == 1,
223 "DRAGDROP_ACTION_COPY constant is wrong");
224 static_assert(nsIDragService::DRAGDROP_ACTION_MOVE == 2,
225 "DRAGDROP_ACTION_MOVE constant is wrong");
226 static_assert(nsIDragService::DRAGDROP_ACTION_LINK == 4,
227 "DRAGDROP_ACTION_LINK constant is wrong");
229 for (uint32_t e = 0; e < ArrayLength(sEffects); e++) {
230 if (aEffectAllowed.EqualsASCII(sEffects[e])) {
231 mEffectAllowed = e;
232 break;
233 }
234 }
236 return NS_OK;
237 }
239 NS_IMETHODIMP
240 DataTransfer::GetDropEffectInt(uint32_t* aDropEffect)
241 {
242 *aDropEffect = mDropEffect;
243 return NS_OK;
244 }
246 NS_IMETHODIMP
247 DataTransfer::SetDropEffectInt(uint32_t aDropEffect)
248 {
249 mDropEffect = aDropEffect;
250 return NS_OK;
251 }
253 NS_IMETHODIMP
254 DataTransfer::GetEffectAllowedInt(uint32_t* aEffectAllowed)
255 {
256 *aEffectAllowed = mEffectAllowed;
257 return NS_OK;
258 }
260 NS_IMETHODIMP
261 DataTransfer::SetEffectAllowedInt(uint32_t aEffectAllowed)
262 {
263 mEffectAllowed = aEffectAllowed;
264 return NS_OK;
265 }
267 NS_IMETHODIMP
268 DataTransfer::GetMozUserCancelled(bool* aUserCancelled)
269 {
270 *aUserCancelled = MozUserCancelled();
271 return NS_OK;
272 }
274 nsDOMFileList*
275 DataTransfer::GetFiles(ErrorResult& aRv)
276 {
277 if (mEventType != NS_DRAGDROP_DROP && mEventType != NS_DRAGDROP_DRAGDROP &&
278 mEventType != NS_PASTE) {
279 return nullptr;
280 }
282 if (!mFiles) {
283 mFiles = new nsDOMFileList(static_cast<nsIDOMDataTransfer*>(this));
285 uint32_t count = mItems.Length();
287 for (uint32_t i = 0; i < count; i++) {
288 nsCOMPtr<nsIVariant> variant;
289 aRv = MozGetDataAt(NS_ConvertUTF8toUTF16(kFileMime), i, getter_AddRefs(variant));
290 if (aRv.Failed()) {
291 return nullptr;
292 }
294 if (!variant)
295 continue;
297 nsCOMPtr<nsISupports> supports;
298 nsresult rv = variant->GetAsISupports(getter_AddRefs(supports));
300 if (NS_FAILED(rv))
301 continue;
303 nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
305 if (!file)
306 continue;
308 nsRefPtr<nsDOMFileFile> domFile = new nsDOMFileFile(file);
310 if (!mFiles->Append(domFile)) {
311 aRv.Throw(NS_ERROR_FAILURE);
312 return nullptr;
313 }
314 }
315 }
317 return mFiles;
318 }
320 NS_IMETHODIMP
321 DataTransfer::GetFiles(nsIDOMFileList** aFileList)
322 {
323 ErrorResult rv;
324 NS_IF_ADDREF(*aFileList = GetFiles(rv));
325 return rv.ErrorCode();
326 }
328 already_AddRefed<DOMStringList>
329 DataTransfer::Types()
330 {
331 nsRefPtr<DOMStringList> types = new DOMStringList();
332 if (mItems.Length()) {
333 bool addFile = false;
334 const nsTArray<TransferItem>& item = mItems[0];
335 for (uint32_t i = 0; i < item.Length(); i++) {
336 const nsString& format = item[i].mFormat;
337 types->Add(format);
338 if (!addFile) {
339 addFile = format.EqualsASCII(kFileMime) ||
340 format.EqualsASCII("application/x-moz-file-promise");
341 }
342 }
344 if (addFile) {
345 types->Add(NS_LITERAL_STRING("Files"));
346 }
347 }
349 return types.forget();
350 }
352 NS_IMETHODIMP
353 DataTransfer::GetTypes(nsISupports** aTypes)
354 {
355 nsRefPtr<DOMStringList> types = Types();
356 types.forget(aTypes);
358 return NS_OK;
359 }
361 void
362 DataTransfer::GetData(const nsAString& aFormat, nsAString& aData,
363 ErrorResult& aRv)
364 {
365 // return an empty string if data for the format was not found
366 aData.Truncate();
368 nsCOMPtr<nsIVariant> data;
369 nsresult rv = MozGetDataAt(aFormat, 0, getter_AddRefs(data));
370 if (NS_FAILED(rv)) {
371 if (rv != NS_ERROR_DOM_INDEX_SIZE_ERR) {
372 aRv.Throw(rv);
373 }
374 return;
375 }
377 if (data) {
378 nsAutoString stringdata;
379 data->GetAsAString(stringdata);
381 // for the URL type, parse out the first URI from the list. The URIs are
382 // separated by newlines
383 nsAutoString lowercaseFormat;
384 aRv = nsContentUtils::ASCIIToLower(aFormat, lowercaseFormat);
385 if (aRv.Failed()) {
386 return;
387 }
389 if (lowercaseFormat.EqualsLiteral("url")) {
390 int32_t lastidx = 0, idx;
391 int32_t length = stringdata.Length();
392 while (lastidx < length) {
393 idx = stringdata.FindChar('\n', lastidx);
394 // lines beginning with # are comments
395 if (stringdata[lastidx] == '#') {
396 if (idx == -1)
397 break;
398 }
399 else {
400 if (idx == -1)
401 aData.Assign(Substring(stringdata, lastidx));
402 else
403 aData.Assign(Substring(stringdata, lastidx, idx - lastidx));
404 aData = nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(aData, true);
405 return;
406 }
407 lastidx = idx + 1;
408 }
409 }
410 else {
411 aData = stringdata;
412 }
413 }
414 }
416 NS_IMETHODIMP
417 DataTransfer::GetData(const nsAString& aFormat, nsAString& aData)
418 {
419 ErrorResult rv;
420 GetData(aFormat, aData, rv);
421 return rv.ErrorCode();
422 }
424 void
425 DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData,
426 ErrorResult& aRv)
427 {
428 nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
429 if (!variant) {
430 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
431 return;
432 }
434 variant->SetAsAString(aData);
436 aRv = MozSetDataAt(aFormat, variant, 0);
437 }
439 NS_IMETHODIMP
440 DataTransfer::SetData(const nsAString& aFormat, const nsAString& aData)
441 {
442 ErrorResult rv;
443 SetData(aFormat, aData, rv);
444 return rv.ErrorCode();
445 }
447 void
448 DataTransfer::ClearData(const Optional<nsAString>& aFormat, ErrorResult& aRv)
449 {
450 if (mReadOnly) {
451 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
452 return;
453 }
455 if (mItems.Length() == 0) {
456 return;
457 }
459 if (aFormat.WasPassed()) {
460 MozClearDataAtHelper(aFormat.Value(), 0, aRv);
461 } else {
462 MozClearDataAtHelper(EmptyString(), 0, aRv);
463 }
464 }
466 NS_IMETHODIMP
467 DataTransfer::ClearData(const nsAString& aFormat)
468 {
469 Optional<nsAString> format;
470 format = &aFormat;
471 ErrorResult rv;
472 ClearData(format, rv);
473 return rv.ErrorCode();
474 }
476 NS_IMETHODIMP
477 DataTransfer::GetMozItemCount(uint32_t* aCount)
478 {
479 *aCount = MozItemCount();
480 return NS_OK;
481 }
483 NS_IMETHODIMP
484 DataTransfer::GetMozCursor(nsAString& aCursorState)
485 {
486 nsString cursor;
487 GetMozCursor(cursor);
488 aCursorState = cursor;
489 return NS_OK;
490 }
492 NS_IMETHODIMP
493 DataTransfer::SetMozCursor(const nsAString& aCursorState)
494 {
495 // Lock the cursor to an arrow during the drag.
496 mCursorState = aCursorState.EqualsLiteral("default");
498 return NS_OK;
499 }
501 already_AddRefed<nsINode>
502 DataTransfer::GetMozSourceNode()
503 {
504 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
505 if (!dragSession) {
506 return nullptr;
507 }
509 nsCOMPtr<nsIDOMNode> sourceNode;
510 dragSession->GetSourceNode(getter_AddRefs(sourceNode));
511 nsCOMPtr<nsINode> node = do_QueryInterface(sourceNode);
512 if (node && !nsContentUtils::CanCallerAccess(node)) {
513 return nullptr;
514 }
516 return node.forget();
517 }
519 NS_IMETHODIMP
520 DataTransfer::GetMozSourceNode(nsIDOMNode** aSourceNode)
521 {
522 nsCOMPtr<nsINode> sourceNode = GetMozSourceNode();
523 if (!sourceNode) {
524 *aSourceNode = nullptr;
525 return NS_OK;
526 }
528 return CallQueryInterface(sourceNode, aSourceNode);
529 }
531 already_AddRefed<DOMStringList>
532 DataTransfer::MozTypesAt(uint32_t aIndex, ErrorResult& aRv)
533 {
534 // Only the first item is valid for clipboard events
535 if (aIndex > 0 &&
536 (mEventType == NS_CUT || mEventType == NS_COPY || mEventType == NS_PASTE)) {
537 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
538 return nullptr;
539 }
541 nsRefPtr<DOMStringList> types = new DOMStringList();
542 if (aIndex < mItems.Length()) {
543 // note that you can retrieve the types regardless of their principal
544 nsTArray<TransferItem>& item = mItems[aIndex];
545 for (uint32_t i = 0; i < item.Length(); i++)
546 types->Add(item[i].mFormat);
547 }
549 return types.forget();
550 }
552 NS_IMETHODIMP
553 DataTransfer::MozTypesAt(uint32_t aIndex, nsISupports** aTypes)
554 {
555 ErrorResult rv;
556 nsRefPtr<DOMStringList> types = MozTypesAt(aIndex, rv);
557 types.forget(aTypes);
558 return rv.ErrorCode();
559 }
561 NS_IMETHODIMP
562 DataTransfer::MozGetDataAt(const nsAString& aFormat, uint32_t aIndex,
563 nsIVariant** aData)
564 {
565 *aData = nullptr;
567 if (aFormat.IsEmpty())
568 return NS_OK;
570 if (aIndex >= mItems.Length()) {
571 return NS_ERROR_DOM_INDEX_SIZE_ERR;
572 }
574 // Only the first item is valid for clipboard events
575 if (aIndex > 0 &&
576 (mEventType == NS_CUT || mEventType == NS_COPY || mEventType == NS_PASTE)) {
577 return NS_ERROR_DOM_INDEX_SIZE_ERR;
578 }
581 nsAutoString format;
582 GetRealFormat(aFormat, format);
584 nsTArray<TransferItem>& item = mItems[aIndex];
586 // Check if the caller is allowed to access the drag data. Callers with
587 // chrome privileges can always read the data. During the
588 // drop event, allow retrieving the data except in the case where the
589 // source of the drag is in a child frame of the caller. In that case,
590 // we only allow access to data of the same principal. During other events,
591 // only allow access to the data with the same principal.
592 nsIPrincipal* principal = nullptr;
593 if (mIsCrossDomainSubFrameDrop ||
594 (mEventType != NS_DRAGDROP_DROP && mEventType != NS_DRAGDROP_DRAGDROP &&
595 mEventType != NS_PASTE &&
596 !nsContentUtils::IsCallerChrome())) {
597 nsresult rv = NS_OK;
598 principal = GetCurrentPrincipal(&rv);
599 NS_ENSURE_SUCCESS(rv, rv);
600 }
602 uint32_t count = item.Length();
603 for (uint32_t i = 0; i < count; i++) {
604 TransferItem& formatitem = item[i];
605 if (formatitem.mFormat.Equals(format)) {
606 bool subsumes;
607 if (formatitem.mPrincipal && principal &&
608 (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) || !subsumes))
609 return NS_ERROR_DOM_SECURITY_ERR;
611 if (!formatitem.mData) {
612 FillInExternalData(formatitem, aIndex);
613 } else {
614 nsCOMPtr<nsISupports> data;
615 formatitem.mData->GetAsISupports(getter_AddRefs(data));
616 // Make sure the code that is calling us is same-origin with the data.
617 nsCOMPtr<EventTarget> pt = do_QueryInterface(data);
618 if (pt) {
619 nsresult rv = NS_OK;
620 nsIScriptContext* c = pt->GetContextForEventHandlers(&rv);
621 NS_ENSURE_TRUE(c && NS_SUCCEEDED(rv), NS_ERROR_DOM_SECURITY_ERR);
622 nsIGlobalObject* go = c->GetGlobalObject();
623 NS_ENSURE_TRUE(go, NS_ERROR_DOM_SECURITY_ERR);
624 nsCOMPtr<nsIScriptObjectPrincipal> sp = do_QueryInterface(go);
625 MOZ_ASSERT(sp, "This cannot fail on the main thread.");
626 nsIPrincipal* dataPrincipal = sp->GetPrincipal();
627 NS_ENSURE_TRUE(dataPrincipal, NS_ERROR_DOM_SECURITY_ERR);
628 NS_ENSURE_TRUE(principal || (principal = GetCurrentPrincipal(&rv)),
629 NS_ERROR_DOM_SECURITY_ERR);
630 NS_ENSURE_SUCCESS(rv, rv);
631 bool equals = false;
632 NS_ENSURE_TRUE(NS_SUCCEEDED(principal->Equals(dataPrincipal, &equals)) && equals,
633 NS_ERROR_DOM_SECURITY_ERR);
634 }
635 }
636 *aData = formatitem.mData;
637 NS_IF_ADDREF(*aData);
638 return NS_OK;
639 }
640 }
642 return NS_OK;
643 }
645 void
646 DataTransfer::MozGetDataAt(JSContext* aCx, const nsAString& aFormat,
647 uint32_t aIndex,
648 JS::MutableHandle<JS::Value> aRetval,
649 mozilla::ErrorResult& aRv)
650 {
651 nsCOMPtr<nsIVariant> data;
652 aRv = MozGetDataAt(aFormat, aIndex, getter_AddRefs(data));
653 if (aRv.Failed()) {
654 return;
655 }
657 if (!data) {
658 return;
659 }
661 JS::Rooted<JS::Value> result(aCx);
662 if (!VariantToJsval(aCx, data, aRetval)) {
663 aRv = NS_ERROR_FAILURE;
664 return;
665 }
666 }
668 NS_IMETHODIMP
669 DataTransfer::MozSetDataAt(const nsAString& aFormat, nsIVariant* aData,
670 uint32_t aIndex)
671 {
672 if (aFormat.IsEmpty()) {
673 return NS_OK;
674 }
676 if (mReadOnly) {
677 return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
678 }
680 // Specifying an index less than the current length will replace an existing
681 // item. Specifying an index equal to the current length will add a new item.
682 if (aIndex > mItems.Length()) {
683 return NS_ERROR_DOM_INDEX_SIZE_ERR;
684 }
686 // Only the first item is valid for clipboard events
687 if (aIndex > 0 &&
688 (mEventType == NS_CUT || mEventType == NS_COPY || mEventType == NS_PASTE)) {
689 return NS_ERROR_DOM_INDEX_SIZE_ERR;
690 }
692 // don't allow non-chrome to add file data
693 // XXX perhaps this should also limit any non-string type as well
694 if ((aFormat.EqualsLiteral("application/x-moz-file-promise") ||
695 aFormat.EqualsLiteral("application/x-moz-file")) &&
696 !nsContentUtils::IsCallerChrome()) {
697 return NS_ERROR_DOM_SECURITY_ERR;
698 }
700 nsresult rv = NS_OK;
701 nsIPrincipal* principal = GetCurrentPrincipal(&rv);
702 NS_ENSURE_SUCCESS(rv, rv);
704 return SetDataWithPrincipal(aFormat, aData, aIndex, principal);
705 }
707 void
708 DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
709 JS::Handle<JS::Value> aData,
710 uint32_t aIndex, ErrorResult& aRv)
711 {
712 nsCOMPtr<nsIVariant> data;
713 aRv = nsContentUtils::XPConnect()->JSValToVariant(aCx, aData,
714 getter_AddRefs(data));
715 if (!aRv.Failed()) {
716 aRv = MozSetDataAt(aFormat, data, aIndex);
717 }
718 }
720 void
721 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex,
722 ErrorResult& aRv)
723 {
724 if (mReadOnly) {
725 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
726 return;
727 }
729 if (aIndex >= mItems.Length()) {
730 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
731 return;
732 }
734 // Only the first item is valid for clipboard events
735 if (aIndex > 0 &&
736 (mEventType == NS_CUT || mEventType == NS_COPY || mEventType == NS_PASTE)) {
737 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
738 return;
739 }
741 MozClearDataAtHelper(aFormat, aIndex, aRv);
742 }
744 void
745 DataTransfer::MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex,
746 ErrorResult& aRv)
747 {
748 MOZ_ASSERT(!mReadOnly);
749 MOZ_ASSERT(aIndex < mItems.Length());
750 MOZ_ASSERT(aIndex == 0 ||
751 (mEventType != NS_CUT && mEventType != NS_COPY &&
752 mEventType != NS_PASTE));
754 nsAutoString format;
755 GetRealFormat(aFormat, format);
757 nsresult rv = NS_OK;
758 nsIPrincipal* principal = GetCurrentPrincipal(&rv);
759 if (NS_FAILED(rv)) {
760 aRv = rv;
761 return;
762 }
764 // if the format is empty, clear all formats
765 bool clearall = format.IsEmpty();
767 nsTArray<TransferItem>& item = mItems[aIndex];
768 // count backwards so that the count and index don't have to be adjusted
769 // after removing an element
770 for (int32_t i = item.Length() - 1; i >= 0; i--) {
771 TransferItem& formatitem = item[i];
772 if (clearall || formatitem.mFormat.Equals(format)) {
773 // don't allow removing data that has a stronger principal
774 bool subsumes;
775 if (formatitem.mPrincipal && principal &&
776 (NS_FAILED(principal->Subsumes(formatitem.mPrincipal, &subsumes)) || !subsumes)) {
777 aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
778 return;
779 }
781 item.RemoveElementAt(i);
783 // if a format was specified, break out. Otherwise, loop around until
784 // all formats have been removed
785 if (!clearall)
786 break;
787 }
788 }
790 // if the last format for an item is removed, remove the entire item
791 if (!item.Length())
792 mItems.RemoveElementAt(aIndex);
793 }
795 NS_IMETHODIMP
796 DataTransfer::MozClearDataAt(const nsAString& aFormat, uint32_t aIndex)
797 {
798 ErrorResult rv;
799 MozClearDataAt(aFormat, aIndex, rv);
800 return rv.ErrorCode();
801 }
803 void
804 DataTransfer::SetDragImage(Element& aImage, int32_t aX, int32_t aY,
805 ErrorResult& aRv)
806 {
807 if (mReadOnly) {
808 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
809 return;
810 }
812 mDragImage = &aImage;
813 mDragImageX = aX;
814 mDragImageY = aY;
815 }
817 NS_IMETHODIMP
818 DataTransfer::SetDragImage(nsIDOMElement* aImage, int32_t aX, int32_t aY)
819 {
820 ErrorResult rv;
821 nsCOMPtr<Element> image = do_QueryInterface(aImage);
822 if (image) {
823 SetDragImage(*image, aX, aY, rv);
824 }
825 return rv.ErrorCode();
826 }
828 void
829 DataTransfer::AddElement(Element& aElement, ErrorResult& aRv)
830 {
831 if (mReadOnly) {
832 aRv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
833 return;
834 }
836 mDragTarget = &aElement;
837 }
839 NS_IMETHODIMP
840 DataTransfer::AddElement(nsIDOMElement* aElement)
841 {
842 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
844 nsCOMPtr<Element> element = do_QueryInterface(aElement);
845 NS_ENSURE_TRUE(element, NS_ERROR_INVALID_ARG);
847 ErrorResult rv;
848 AddElement(*element, rv);
849 return rv.ErrorCode();
850 }
852 nsresult
853 DataTransfer::Clone(nsISupports* aParent, uint32_t aEventType,
854 bool aUserCancelled, bool aIsCrossDomainSubFrameDrop,
855 DataTransfer** aNewDataTransfer)
856 {
857 DataTransfer* newDataTransfer =
858 new DataTransfer(aParent, aEventType, mEffectAllowed, mCursorState,
859 mIsExternal, aUserCancelled, aIsCrossDomainSubFrameDrop,
860 mClipboardType, mItems, mDragImage, mDragImageX,
861 mDragImageY);
863 *aNewDataTransfer = newDataTransfer;
864 NS_ADDREF(*aNewDataTransfer);
865 return NS_OK;
866 }
868 already_AddRefed<nsISupportsArray>
869 DataTransfer::GetTransferables(nsIDOMNode* aDragTarget)
870 {
871 MOZ_ASSERT(aDragTarget);
873 nsCOMPtr<nsISupportsArray> transArray =
874 do_CreateInstance("@mozilla.org/supports-array;1");
875 if (!transArray) {
876 return nullptr;
877 }
880 nsCOMPtr<nsINode> dragNode = do_QueryInterface(aDragTarget);
881 if (!dragNode) {
882 return nullptr;
883 }
885 nsIDocument* doc = dragNode->GetCurrentDoc();
886 if (!doc) {
887 return nullptr;
888 }
890 nsILoadContext* loadContext = doc->GetLoadContext();
892 uint32_t count = mItems.Length();
893 for (uint32_t i = 0; i < count; i++) {
894 nsCOMPtr<nsITransferable> transferable = GetTransferable(i, loadContext);
895 if (transferable) {
896 transArray->AppendElement(transferable);
897 }
898 }
900 return transArray.forget();
901 }
903 already_AddRefed<nsITransferable>
904 DataTransfer::GetTransferable(uint32_t aIndex, nsILoadContext* aLoadContext)
905 {
906 if (aIndex >= mItems.Length()) {
907 return nullptr;
908 }
910 nsTArray<TransferItem>& item = mItems[aIndex];
911 uint32_t count = item.Length();
912 if (!count) {
913 return nullptr;
914 }
916 nsCOMPtr<nsITransferable> transferable =
917 do_CreateInstance("@mozilla.org/widget/transferable;1");
918 if (!transferable) {
919 return nullptr;
920 }
921 transferable->Init(aLoadContext);
923 bool added = false;
924 for (uint32_t f = 0; f < count; f++) {
925 const TransferItem& formatitem = item[f];
926 if (!formatitem.mData) { // skip empty items
927 continue;
928 }
930 uint32_t length;
931 nsCOMPtr<nsISupports> convertedData;
932 if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length)) {
933 continue;
934 }
936 // the underlying drag code uses text/unicode, so use that instead of text/plain
937 const char* format;
938 NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
939 if (utf8format.EqualsLiteral("text/plain")) {
940 format = kUnicodeMime;
941 } else {
942 format = utf8format.get();
943 }
945 // if a converter is set for a format, set the converter for the
946 // transferable and don't add the item
947 nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
948 if (converter) {
949 transferable->AddDataFlavor(format);
950 transferable->SetConverter(converter);
951 continue;
952 }
954 nsresult rv = transferable->SetTransferData(format, convertedData, length);
955 if (NS_FAILED(rv)) {
956 return nullptr;
957 }
959 added = true;
960 }
962 // only return the transferable if data was successfully added to it
963 if (added) {
964 return transferable.forget();
965 }
967 return nullptr;
968 }
970 bool
971 DataTransfer::ConvertFromVariant(nsIVariant* aVariant,
972 nsISupports** aSupports,
973 uint32_t* aLength)
974 {
975 *aSupports = nullptr;
976 *aLength = 0;
978 uint16_t type;
979 aVariant->GetDataType(&type);
980 if (type == nsIDataType::VTYPE_INTERFACE ||
981 type == nsIDataType::VTYPE_INTERFACE_IS) {
982 nsCOMPtr<nsISupports> data;
983 if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data))))
984 return false;
986 nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
987 if (fdp) {
988 // for flavour data providers, use kFlavorHasDataProvider (which has the
989 // value 0) as the length.
990 NS_ADDREF(*aSupports = fdp);
991 *aLength = nsITransferable::kFlavorHasDataProvider;
992 }
993 else {
994 // wrap the item in an nsISupportsInterfacePointer
995 nsCOMPtr<nsISupportsInterfacePointer> ptrSupports =
996 do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
997 if (!ptrSupports)
998 return false;
1000 ptrSupports->SetData(data);
1001 NS_ADDREF(*aSupports = ptrSupports);
1003 *aLength = sizeof(nsISupportsInterfacePointer *);
1004 }
1006 return true;
1007 }
1009 char16_t* chrs;
1010 uint32_t len = 0;
1011 nsresult rv = aVariant->GetAsWStringWithSize(&len, &chrs);
1012 if (NS_FAILED(rv))
1013 return false;
1015 nsAutoString str;
1016 str.Adopt(chrs, len);
1018 nsCOMPtr<nsISupportsString>
1019 strSupports(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID));
1020 if (!strSupports)
1021 return false;
1023 strSupports->SetData(str);
1025 *aSupports = strSupports;
1026 NS_ADDREF(*aSupports);
1028 // each character is two bytes
1029 *aLength = str.Length() << 1;
1031 return true;
1032 }
1034 void
1035 DataTransfer::ClearAll()
1036 {
1037 mItems.Clear();
1038 }
1040 nsresult
1041 DataTransfer::SetDataWithPrincipal(const nsAString& aFormat,
1042 nsIVariant* aData,
1043 uint32_t aIndex,
1044 nsIPrincipal* aPrincipal)
1045 {
1046 nsAutoString format;
1047 GetRealFormat(aFormat, format);
1049 // check if the item for the format already exists. In that case,
1050 // just replace it.
1051 TransferItem* formatitem;
1052 if (aIndex < mItems.Length()) {
1053 nsTArray<TransferItem>& item = mItems[aIndex];
1054 uint32_t count = item.Length();
1055 for (uint32_t i = 0; i < count; i++) {
1056 TransferItem& itemformat = item[i];
1057 if (itemformat.mFormat.Equals(format)) {
1058 // don't allow replacing data that has a stronger principal
1059 bool subsumes;
1060 if (itemformat.mPrincipal && aPrincipal &&
1061 (NS_FAILED(aPrincipal->Subsumes(itemformat.mPrincipal, &subsumes)) || !subsumes))
1062 return NS_ERROR_DOM_SECURITY_ERR;
1064 itemformat.mPrincipal = aPrincipal;
1065 itemformat.mData = aData;
1066 return NS_OK;
1067 }
1068 }
1070 // add a new format
1071 formatitem = item.AppendElement();
1072 }
1073 else {
1074 NS_ASSERTION(aIndex == mItems.Length(), "Index out of range");
1076 // add a new index
1077 nsTArray<TransferItem>* item = mItems.AppendElement();
1078 NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY);
1080 formatitem = item->AppendElement();
1081 }
1083 NS_ENSURE_TRUE(formatitem, NS_ERROR_OUT_OF_MEMORY);
1085 formatitem->mFormat = format;
1086 formatitem->mPrincipal = aPrincipal;
1087 formatitem->mData = aData;
1089 return NS_OK;
1090 }
1092 nsIPrincipal*
1093 DataTransfer::GetCurrentPrincipal(nsresult* rv)
1094 {
1095 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1097 nsCOMPtr<nsIPrincipal> currentPrincipal;
1098 *rv = ssm->GetSubjectPrincipal(getter_AddRefs(currentPrincipal));
1099 NS_ENSURE_SUCCESS(*rv, nullptr);
1101 if (!currentPrincipal)
1102 ssm->GetSystemPrincipal(getter_AddRefs(currentPrincipal));
1104 return currentPrincipal.get();
1105 }
1107 void
1108 DataTransfer::GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat)
1109 {
1110 // treat text/unicode as equivalent to text/plain
1111 nsAutoString lowercaseFormat;
1112 nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
1113 if (lowercaseFormat.EqualsLiteral("text") || lowercaseFormat.EqualsLiteral("text/unicode"))
1114 aOutFormat.AssignLiteral("text/plain");
1115 else if (lowercaseFormat.EqualsLiteral("url"))
1116 aOutFormat.AssignLiteral("text/uri-list");
1117 else
1118 aOutFormat.Assign(lowercaseFormat);
1119 }
1121 void
1122 DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex, nsIPrincipal* aPrincipal)
1123 {
1124 if (strcmp(aFormat, kUnicodeMime) == 0) {
1125 SetDataWithPrincipal(NS_LITERAL_STRING("text/plain"), nullptr, aIndex, aPrincipal);
1126 } else {
1127 if (strcmp(aFormat, kURLDataMime) == 0) {
1128 SetDataWithPrincipal(NS_LITERAL_STRING("text/uri-list"), nullptr, aIndex, aPrincipal);
1129 }
1130 SetDataWithPrincipal(NS_ConvertUTF8toUTF16(aFormat), nullptr, aIndex, aPrincipal);
1131 }
1132 }
1134 void
1135 DataTransfer::CacheExternalDragFormats()
1136 {
1137 // Called during the constructor to cache the formats available from an
1138 // external drag. The data associated with each format will be set to null.
1139 // This data will instead only be retrieved in FillInExternalDragData when
1140 // asked for, as it may be time consuming for the source application to
1141 // generate it.
1143 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1144 if (!dragSession)
1145 return;
1147 // make sure that the system principal is used for external drags
1148 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1149 nsCOMPtr<nsIPrincipal> sysPrincipal;
1150 ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
1152 // there isn't a way to get a list of the formats that might be available on
1153 // all platforms, so just check for the types that can actually be imported
1154 // XXXndeakin there are some other formats but those are platform specific.
1155 const char* formats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, kUnicodeMime };
1157 uint32_t count;
1158 dragSession->GetNumDropItems(&count);
1159 for (uint32_t c = 0; c < count; c++) {
1160 for (uint32_t f = 0; f < ArrayLength(formats); f++) {
1161 // IsDataFlavorSupported doesn't take an index as an argument and just
1162 // checks if any of the items support a particular flavor, even though
1163 // the GetData method does take an index. Here, we just assume that
1164 // every item being dragged has the same set of flavors.
1165 bool supported;
1166 dragSession->IsDataFlavorSupported(formats[f], &supported);
1167 // if the format is supported, add an item to the array with null as
1168 // the data. When retrieved, GetRealData will read the data.
1169 if (supported) {
1170 CacheExternalData(formats[f], c, sysPrincipal);
1171 }
1172 }
1173 }
1174 }
1176 void
1177 DataTransfer::CacheExternalClipboardFormats()
1178 {
1179 NS_ASSERTION(mEventType == NS_PASTE, "caching clipboard data for invalid event");
1181 // Called during the constructor for paste events to cache the formats
1182 // available on the clipboard. As with CacheExternalDragFormats, the
1183 // data will only be retrieved when needed.
1185 nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
1186 if (!clipboard || mClipboardType < 0) {
1187 return;
1188 }
1190 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
1191 nsCOMPtr<nsIPrincipal> sysPrincipal;
1192 ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
1194 // there isn't a way to get a list of the formats that might be available on
1195 // all platforms, so just check for the types that can actually be imported
1196 const char* formats[] = { kFileMime, kHTMLMime, kURLMime, kURLDataMime, kUnicodeMime };
1198 for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
1199 // check each format one at a time
1200 bool supported;
1201 clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, &supported);
1202 // if the format is supported, add an item to the array with null as
1203 // the data. When retrieved, GetRealData will read the data.
1204 if (supported) {
1205 CacheExternalData(formats[f], 0, sysPrincipal);
1206 }
1207 }
1208 }
1210 void
1211 DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex)
1212 {
1213 NS_PRECONDITION(mIsExternal, "Not an external data transfer");
1215 if (aItem.mData) {
1216 return;
1217 }
1219 // only drag and paste events should be calling FillInExternalData
1220 NS_ASSERTION(mEventType != NS_CUT && mEventType != NS_COPY,
1221 "clipboard event with empty data");
1223 NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
1224 const char* format = utf8format.get();
1225 if (strcmp(format, "text/plain") == 0)
1226 format = kUnicodeMime;
1227 else if (strcmp(format, "text/uri-list") == 0)
1228 format = kURLDataMime;
1230 nsCOMPtr<nsITransferable> trans =
1231 do_CreateInstance("@mozilla.org/widget/transferable;1");
1232 if (!trans)
1233 return;
1235 trans->Init(nullptr);
1236 trans->AddDataFlavor(format);
1238 if (mEventType == NS_PASTE) {
1239 MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0");
1241 nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
1242 if (!clipboard || mClipboardType < 0) {
1243 return;
1244 }
1246 clipboard->GetData(trans, mClipboardType);
1247 } else {
1248 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
1249 if (!dragSession) {
1250 return;
1251 }
1253 #ifdef DEBUG
1254 // Since this is an external drag, the source document will always be null.
1255 nsCOMPtr<nsIDOMDocument> domDoc;
1256 dragSession->GetSourceDocument(getter_AddRefs(domDoc));
1257 MOZ_ASSERT(!domDoc);
1258 #endif
1260 dragSession->GetData(trans, aIndex);
1261 }
1263 uint32_t length = 0;
1264 nsCOMPtr<nsISupports> data;
1265 trans->GetTransferData(format, getter_AddRefs(data), &length);
1266 if (!data)
1267 return;
1269 nsCOMPtr<nsIWritableVariant> variant = do_CreateInstance(NS_VARIANT_CONTRACTID);
1270 if (!variant)
1271 return;
1273 nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
1274 if (supportsstr) {
1275 nsAutoString str;
1276 supportsstr->GetData(str);
1277 variant->SetAsAString(str);
1278 }
1279 else {
1280 variant->SetAsISupports(data);
1281 }
1283 aItem.mData = variant;
1284 }
1286 } // namespace dom
1287 } // namespace mozilla