1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsClipboard.h"
11 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
18 #include "mozilla/Logging.h"
19 #include "mozilla/StaticPrefs_clipboard.h"
20 #include "nsArrayUtils.h"
22 #include "nsComponentManagerUtils.h"
23 #include "nsDataObj.h"
25 #include "nsNativeCharsetUtils.h"
26 #include "nsIInputStream.h"
27 #include "nsITransferable.h"
29 #include "nsReadableUtils.h"
30 #include "nsUnicharUtils.h"
31 #include "nsPrimitiveHelpers.h"
32 #include "nsIWidget.h"
33 #include "nsWidgetsCID.h"
35 #include "nsNetUtil.h"
36 #include "nsIFileProtocolHandler.h"
38 #include "nsIObserverService.h"
39 #include "nsMimeTypes.h"
40 #include "imgITools.h"
42 using mozilla::LogLevel
;
44 static mozilla::LazyLogModule
gWin32ClipboardLog("nsClipboard");
47 UINT
nsClipboard::GetHtmlClipboardFormat() {
48 static UINT format
= ::RegisterClipboardFormatW(L
"HTML Format");
53 UINT
nsClipboard::GetCustomClipboardFormat() {
55 ::RegisterClipboardFormatW(L
"application/x-moz-custom-clipdata");
59 //-------------------------------------------------------------------------
61 // nsClipboard constructor
63 //-------------------------------------------------------------------------
64 nsClipboard::nsClipboard() : nsBaseClipboard() {
67 // Register for a shutdown notification so that we can flush data
68 // to the OS clipboard.
69 nsCOMPtr
<nsIObserverService
> observerService
=
70 do_GetService("@mozilla.org/observer-service;1");
71 if (observerService
) {
72 observerService
->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID
,
77 //-------------------------------------------------------------------------
78 // nsClipboard destructor
79 //-------------------------------------------------------------------------
80 nsClipboard::~nsClipboard() {}
82 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard
, nsBaseClipboard
, nsIObserver
)
85 nsClipboard::Observe(nsISupports
* aSubject
, const char* aTopic
,
86 const char16_t
* aData
) {
87 // This will be called on shutdown.
88 ::OleFlushClipboard();
94 //-------------------------------------------------------------------------
95 UINT
nsClipboard::GetFormat(const char* aMimeStr
, bool aMapHTMLMime
) {
98 if (strcmp(aMimeStr
, kTextMime
) == 0) {
100 } else if (strcmp(aMimeStr
, kUnicodeMime
) == 0) {
101 format
= CF_UNICODETEXT
;
102 } else if (strcmp(aMimeStr
, kRTFMime
) == 0) {
103 format
= ::RegisterClipboardFormat(L
"Rich Text Format");
104 } else if (strcmp(aMimeStr
, kJPEGImageMime
) == 0 ||
105 strcmp(aMimeStr
, kJPGImageMime
) == 0 ||
106 strcmp(aMimeStr
, kPNGImageMime
) == 0) {
108 } else if (strcmp(aMimeStr
, kFileMime
) == 0 ||
109 strcmp(aMimeStr
, kFilePromiseMime
) == 0) {
111 } else if ((strcmp(aMimeStr
, kNativeHTMLMime
) == 0) ||
112 (aMapHTMLMime
&& strcmp(aMimeStr
, kHTMLMime
) == 0)) {
113 format
= GetHtmlClipboardFormat();
114 } else if (strcmp(aMimeStr
, kCustomTypesMime
) == 0) {
115 format
= GetCustomClipboardFormat();
117 format
= ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr
).get());
123 //-------------------------------------------------------------------------
125 nsresult
nsClipboard::CreateNativeDataObject(nsITransferable
* aTransferable
,
126 IDataObject
** aDataObj
,
128 if (nullptr == aTransferable
) {
129 return NS_ERROR_FAILURE
;
132 // Create our native DataObject that implements
133 // the OLE IDataObject interface
134 nsDataObj
* dataObj
= new nsDataObj(uri
);
137 return NS_ERROR_OUT_OF_MEMORY
;
142 // Now set it up with all the right data flavors & enums
143 nsresult res
= SetupNativeDataObject(aTransferable
, dataObj
);
152 static nsresult
StoreValueInDataObject(nsDataObj
* aObj
,
153 LPCWSTR aClipboardFormat
, DWORD value
) {
154 HGLOBAL hGlobalMemory
= ::GlobalAlloc(GMEM_MOVEABLE
, sizeof(DWORD
));
155 if (!hGlobalMemory
) {
156 return NS_ERROR_OUT_OF_MEMORY
;
158 DWORD
* pdw
= (DWORD
*)::GlobalLock(hGlobalMemory
);
160 ::GlobalUnlock(hGlobalMemory
);
163 stg
.tymed
= TYMED_HGLOBAL
;
164 stg
.pUnkForRelease
= nullptr;
165 stg
.hGlobal
= hGlobalMemory
;
168 SET_FORMATETC(fe
, ::RegisterClipboardFormat(aClipboardFormat
), 0,
169 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
170 aObj
->SetData(&fe
, &stg
, TRUE
);
175 //-------------------------------------------------------------------------
176 nsresult
nsClipboard::SetupNativeDataObject(nsITransferable
* aTransferable
,
177 IDataObject
* aDataObj
) {
178 if (nullptr == aTransferable
|| nullptr == aDataObj
) {
179 return NS_ERROR_FAILURE
;
182 nsDataObj
* dObj
= static_cast<nsDataObj
*>(aDataObj
);
184 // Now give the Transferable to the DataObject
185 // for getting the data out of it
186 dObj
->SetTransferable(aTransferable
);
188 // Get the transferable list of data flavors
189 nsTArray
<nsCString
> flavors
;
190 aTransferable
->FlavorsTransferableCanExport(flavors
);
192 // Walk through flavors that contain data and register them
193 // into the DataObj as supported flavors
194 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
195 nsCString
& flavorStr
= flavors
[i
];
197 // When putting data onto the clipboard, we want to maintain kHTMLMime
198 // ("text/html") and not map it to CF_HTML here since this will be done
200 UINT format
= GetFormat(flavorStr
.get(), false);
202 // Now tell the native IDataObject about both our mime type and
203 // the native data format
205 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
206 dObj
->AddDataFlavor(flavorStr
.get(), &fe
);
208 // Do various things internal to the implementation, like map one
209 // flavor to another or add additional flavors based on what's required
210 // for the win32 impl.
211 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
212 // if we find text/unicode, also advertise text/plain (which we will
213 // convert on our own in nsDataObj::GetText().
215 SET_FORMATETC(textFE
, CF_TEXT
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
216 dObj
->AddDataFlavor(kTextMime
, &textFE
);
217 } else if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
218 // if we find text/html, also advertise win32's html flavor (which we will
219 // convert on our own in nsDataObj::GetText().
221 SET_FORMATETC(htmlFE
, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT
, -1,
223 dObj
->AddDataFlavor(kHTMLMime
, &htmlFE
);
224 } else if (flavorStr
.EqualsLiteral(kURLMime
)) {
225 // if we're a url, in addition to also being text, we need to register
226 // the "file" flavors so that the win32 shell knows to create an internet
227 // shortcut when it sees one of these beasts.
228 FORMATETC shortcutFE
;
229 SET_FORMATETC(shortcutFE
,
230 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA
), 0,
231 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
232 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
233 SET_FORMATETC(shortcutFE
,
234 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW
), 0,
235 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
236 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
237 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_FILECONTENTS
),
238 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
239 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
240 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_INETURLA
), 0,
241 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
242 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
243 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_INETURLW
), 0,
244 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
245 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
246 } else if (flavorStr
.EqualsLiteral(kPNGImageMime
) ||
247 flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
248 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
249 flavorStr
.EqualsLiteral(kGIFImageMime
) ||
250 flavorStr
.EqualsLiteral(kNativeImageMime
)) {
251 // if we're an image, register the native bitmap flavor
254 SET_FORMATETC(imageFE
, CF_DIBV5
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
255 dObj
->AddDataFlavor(flavorStr
.get(), &imageFE
);
257 SET_FORMATETC(imageFE
, CF_DIB
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
258 dObj
->AddDataFlavor(flavorStr
.get(), &imageFE
);
259 } else if (flavorStr
.EqualsLiteral(kFilePromiseMime
)) {
260 // if we're a file promise flavor, also register the
261 // CFSTR_PREFERREDDROPEFFECT format. The data object
262 // returns a value of DROPEFFECTS_MOVE to the drop target
263 // when it asks for the value of this format. This causes
264 // the file to be moved from the temporary location instead
265 // of being copied. The right thing to do here is to call
266 // SetData() on the data object and set the value of this format
267 // to DROPEFFECTS_MOVE on this particular data object. But,
268 // since all the other clipboard formats follow the model of setting
269 // data on the data object only when the drop object calls GetData(),
270 // I am leaving this format's value hard coded in the data object.
271 // We can change this if other consumers of this format get added to this
272 // codebase and they need different values.
273 FORMATETC shortcutFE
;
274 SET_FORMATETC(shortcutFE
,
275 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT
), 0,
276 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
277 dObj
->AddDataFlavor(kFilePromiseMime
, &shortcutFE
);
281 if (!StaticPrefs::clipboard_copyPrivateDataToClipboardCloudOrHistory()) {
282 // Let Clipboard know that data is sensitive and must not be copied to
283 // the Cloud Clipboard, Clipboard History and similar.
284 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats
285 if (aTransferable
->GetIsPrivateData()) {
287 StoreValueInDataObject(dObj
, TEXT("CanUploadToCloudClipboard"), 0);
288 NS_ENSURE_SUCCESS(rv
, rv
);
290 StoreValueInDataObject(dObj
, TEXT("CanIncludeInClipboardHistory"), 0);
291 NS_ENSURE_SUCCESS(rv
, rv
);
292 rv
= StoreValueInDataObject(
293 dObj
, TEXT("ExcludeClipboardContentFromMonitorProcessing"), 0);
294 NS_ENSURE_SUCCESS(rv
, rv
);
301 // See methods listed at
302 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
303 static void IDataObjectMethodResultToString(const HRESULT aHres
,
304 nsACString
& aResult
) {
307 aResult
= "E_INVALIDARG";
310 aResult
= "E_UNEXPECTED";
313 aResult
= "E_OUTOFMEMORY";
316 aResult
= "DV_E_LINDEX";
319 aResult
= "DV_E_FORMATETC";
322 aResult
= "DV_E_TYMED";
325 aResult
= "DV_E_DVASPECT";
327 case OLE_E_NOTRUNNING
:
328 aResult
= "OLE_E_NOTRUNNING";
330 case STG_E_MEDIUMFULL
:
331 aResult
= "STG_E_MEDIUMFULL";
333 case DV_E_CLIPFORMAT
:
334 aResult
= "DV_E_CLIPFORMAT";
340 // Explicit template instantiaton, because otherwise the call is
342 constexpr int kRadix
= 16;
343 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
349 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
350 static void OleGetClipboardResultToString(const HRESULT aHres
,
351 nsACString
& aResult
) {
356 case CLIPBRD_E_CANT_OPEN
:
357 aResult
= "CLIPBRD_E_CANT_OPEN";
359 case CLIPBRD_E_CANT_CLOSE
:
360 aResult
= "CLIPBRD_E_CANT_CLOSE";
363 // Explicit template instantiaton, because otherwise the call is
365 constexpr int kRadix
= 16;
366 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
372 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
373 static void LogOleGetClipboardResult(const HRESULT aHres
) {
374 if (MOZ_LOG_TEST(gWin32ClipboardLog
, LogLevel::Debug
)) {
375 nsAutoCString hresString
;
376 OleGetClipboardResultToString(aHres
, hresString
);
377 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
,
378 ("OleGetClipboard result: %s", hresString
.get()));
383 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
384 static void OleSetClipboardResultToString(HRESULT aHres
, nsACString
& aResult
) {
389 case CLIPBRD_E_CANT_OPEN
:
390 aResult
= "CLIPBRD_E_CANT_OPEN";
392 case CLIPBRD_E_CANT_EMPTY
:
393 aResult
= "CLIPBRD_E_CANT_EMPTY";
395 case CLIPBRD_E_CANT_CLOSE
:
396 aResult
= "CLIPBRD_E_CANT_CLOSE";
398 case CLIPBRD_E_CANT_SET
:
399 aResult
= "CLIPBRD_E_CANT_SET";
402 // Explicit template instantiaton, because otherwise the call is
404 constexpr int kRadix
= 16;
405 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
411 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
412 static void LogOleSetClipboardResult(const HRESULT aHres
) {
413 if (MOZ_LOG_TEST(gWin32ClipboardLog
, LogLevel::Debug
)) {
414 nsAutoCString hresString
;
415 OleSetClipboardResultToString(aHres
, hresString
);
416 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
,
417 ("OleSetClipboard result: %s", hresString
.get()));
421 template <typename Function
, typename LogFunction
, typename
... Args
>
422 static HRESULT
RepeatedlyTry(Function aFunction
, LogFunction aLogFunction
,
424 // These are magic values based on local testing. They are chosen not higher
425 // to avoid jank (<https://developer.mozilla.org/en-US/docs/Glossary/Jank>).
426 // When changing them, be careful.
427 static constexpr int kNumberOfTries
= 3;
428 static constexpr int kDelayInMs
= 3;
431 for (int i
= 0; i
< kNumberOfTries
; ++i
) {
432 hres
= aFunction(aArgs
...);
439 std::this_thread::sleep_for(std::chrono::milliseconds(kDelayInMs
));
445 // Other apps can block access to the clipboard. This repeatedly
446 // calls `::OleSetClipboard` for a fixed number of times and should be called
447 // instead of `::OleSetClipboard`.
448 static void RepeatedlyTryOleSetClipboard(IDataObject
* aDataObj
) {
449 RepeatedlyTry(::OleSetClipboard
, LogOleSetClipboardResult
, aDataObj
);
452 //-------------------------------------------------------------------------
453 NS_IMETHODIMP
nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard
) {
454 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
, ("%s", __FUNCTION__
));
456 if (aWhichClipboard
!= kGlobalClipboard
) {
457 return NS_ERROR_FAILURE
;
460 // make sure we have a good transferable
461 if (nullptr == mTransferable
) {
462 return NS_ERROR_FAILURE
;
465 IDataObject
* dataObj
;
466 if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable
, &dataObj
,
467 nullptr))) { // this add refs dataObj
468 RepeatedlyTryOleSetClipboard(dataObj
);
471 // Clear the native clipboard
472 RepeatedlyTryOleSetClipboard(nullptr);
478 //-------------------------------------------------------------------------
479 nsresult
nsClipboard::GetGlobalData(HGLOBAL aHGBL
, void** aData
,
481 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Verbose
, ("%s", __FUNCTION__
));
483 // Allocate a new memory buffer and copy the data from global memory.
484 // Recall that win98 allocates to nearest DWORD boundary. As a safety
485 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
486 // and null them out to ensure that all of our NS_strlen calls will succeed.
487 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
488 // a full NUL char16_t when |*aLen| is odd.
489 nsresult result
= NS_ERROR_FAILURE
;
490 if (aHGBL
!= nullptr) {
491 LPSTR lpStr
= (LPSTR
)GlobalLock(aHGBL
);
492 mozilla::CheckedInt
<uint32_t> allocSize
=
493 mozilla::CheckedInt
<uint32_t>(GlobalSize(aHGBL
)) + 3;
494 if (!allocSize
.isValid()) {
495 return NS_ERROR_INVALID_ARG
;
497 char* data
= static_cast<char*>(malloc(allocSize
.value()));
499 uint32_t size
= allocSize
.value() - 3;
500 memcpy(data
, lpStr
, size
);
501 // null terminate for safety
502 data
[size
] = data
[size
+ 1] = data
[size
+ 2] = '\0';
511 // We really shouldn't ever get here
518 FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
, nullptr,
520 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
521 (LPWSTR
)&lpMsgBuf
, 0, nullptr);
523 // Display the string.
524 MessageBoxW(nullptr, (LPCWSTR
)lpMsgBuf
, L
"GetLastError",
525 MB_OK
| MB_ICONINFORMATION
);
534 //-------------------------------------------------------------------------
535 nsresult
nsClipboard::GetNativeDataOffClipboard(nsIWidget
* aWidget
,
536 UINT
/*aIndex*/, UINT aFormat
,
537 void** aData
, uint32_t* aLen
) {
538 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
,
539 ("%s: overload taking nsIWidget*.", __FUNCTION__
));
542 nsresult result
= NS_ERROR_FAILURE
;
544 HWND nativeWin
= nullptr;
545 if (::OpenClipboard(nativeWin
)) {
546 hglb
= ::GetClipboardData(aFormat
);
547 result
= GetGlobalData(hglb
, aData
, aLen
);
553 // See methods listed at
554 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
555 static void LogIDataObjectMethodResult(const HRESULT aHres
,
556 const nsCString
& aMethodName
) {
557 if (MOZ_LOG_TEST(gWin32ClipboardLog
, LogLevel::Debug
)) {
558 nsAutoCString hresString
;
559 IDataObjectMethodResultToString(aHres
, hresString
);
561 gWin32ClipboardLog
, LogLevel::Debug
,
562 ("IDataObject::%s result: %s", aMethodName
.get(), hresString
.get()));
566 // Other apps can block access to the clipboard. This repeatedly calls
567 // `GetData` for a fixed number of times and should be called instead of
569 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-idataobject-getdata>.
570 // While Microsoft's documentation doesn't include `CLIPBRD_E_CANT_OPEN`
571 // explicitly, it allows it implicitly and in local experiments it was indeed
573 static HRESULT
RepeatedlyTryGetData(IDataObject
& aDataObject
, LPFORMATETC pFE
,
575 return RepeatedlyTry(
576 [&aDataObject
, &pFE
, &pSTM
]() { return aDataObject
.GetData(pFE
, pSTM
); },
577 std::bind(LogIDataObjectMethodResult
, std::placeholders::_1
,
581 //-------------------------------------------------------------------------
583 HRESULT
nsClipboard::FillSTGMedium(IDataObject
* aDataObject
, UINT aFormat
,
584 LPFORMATETC pFE
, LPSTGMEDIUM pSTM
,
586 SET_FORMATETC(*pFE
, aFormat
, 0, DVASPECT_CONTENT
, -1, aTymed
);
588 // Starting by querying for the data to see if we can get it as from global
590 HRESULT hres
= S_FALSE
;
591 hres
= aDataObject
->QueryGetData(pFE
);
592 LogIDataObjectMethodResult(hres
, "QueryGetData"_ns
);
594 hres
= RepeatedlyTryGetData(*aDataObject
, pFE
, pSTM
);
599 //-------------------------------------------------------------------------
600 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
601 // an image encoder (e.g. image/png).
602 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
603 nsresult
nsClipboard::GetNativeDataOffClipboard(IDataObject
* aDataObject
,
604 UINT aIndex
, UINT aFormat
,
605 const char* aMIMEImageFormat
,
606 void** aData
, uint32_t* aLen
) {
607 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
,
608 ("%s: overload taking IDataObject*.", __FUNCTION__
));
610 nsresult result
= NS_ERROR_FAILURE
;
618 UINT format
= aFormat
;
619 HRESULT hres
= S_FALSE
;
621 // XXX at the moment we only support global memory transfers
622 // It is here where we will add support for native images
626 hres
= FillSTGMedium(aDataObject
, format
, &fe
, &stm
, TYMED_HGLOBAL
);
628 // Currently this is only handling TYMED_HGLOBAL data
629 // For Text, Dibs, Files, and generic data (like HTML)
631 static CLIPFORMAT fileDescriptorFlavorA
=
632 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA
);
633 static CLIPFORMAT fileDescriptorFlavorW
=
634 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW
);
635 static CLIPFORMAT fileFlavor
=
636 ::RegisterClipboardFormat(CFSTR_FILECONTENTS
);
637 static CLIPFORMAT preferredDropEffect
=
638 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT
);
641 case TYMED_HGLOBAL
: {
642 switch (fe
.cfFormat
) {
644 // Get the data out of the global data handle. The size we
645 // return should not include the null because the other
646 // platforms don't use nulls, so just return the length we get
647 // back from strlen(), since we know CF_TEXT is null
648 // terminated. Recall that GetGlobalData() returns the size of
649 // the allocated buffer, not the size of the data (on 98, these
650 // are not the same) so we can't use that.
651 uint32_t allocLen
= 0;
652 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
653 *aLen
= strlen(reinterpret_cast<char*>(*aData
));
658 case CF_UNICODETEXT
: {
659 // Get the data out of the global data handle. The size we
660 // return should not include the null because the other
661 // platforms don't use nulls, so just return the length we get
662 // back from strlen(), since we know CF_UNICODETEXT is null
663 // terminated. Recall that GetGlobalData() returns the size of
664 // the allocated buffer, not the size of the data (on 98, these
665 // are not the same) so we can't use that.
666 uint32_t allocLen
= 0;
667 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
668 *aLen
= NS_strlen(reinterpret_cast<char16_t
*>(*aData
)) * 2;
674 if (aMIMEImageFormat
) {
675 uint32_t allocLen
= 0;
676 const char* clipboardData
;
677 if (NS_SUCCEEDED(GetGlobalData(
678 stm
.hGlobal
, (void**)&clipboardData
, &allocLen
))) {
679 nsCOMPtr
<imgIContainer
> container
;
680 nsCOMPtr
<imgITools
> imgTools
=
681 do_CreateInstance("@mozilla.org/image/tools;1");
682 result
= imgTools
->DecodeImageFromBuffer(
683 clipboardData
, allocLen
,
684 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD
),
685 getter_AddRefs(container
));
686 if (NS_FAILED(result
)) {
690 nsAutoCString mimeType
;
691 if (strcmp(aMIMEImageFormat
, kJPGImageMime
) == 0) {
692 mimeType
.Assign(IMAGE_JPEG
);
694 mimeType
.Assign(aMIMEImageFormat
);
697 nsCOMPtr
<nsIInputStream
> inputStream
;
698 result
= imgTools
->EncodeImage(container
, mimeType
, u
""_ns
,
699 getter_AddRefs(inputStream
));
700 if (NS_FAILED(result
)) {
705 result
= NS_ERROR_FAILURE
;
709 *aData
= inputStream
.forget().take();
710 *aLen
= sizeof(nsIInputStream
*);
716 // in the case of a file drop, multiple files are stashed within a
717 // single data object. In order to match mozilla's D&D apis, we
718 // just pull out the file at the requested index, pretending as
719 // if there really are multiple drag items.
720 HDROP dropFiles
= (HDROP
)GlobalLock(stm
.hGlobal
);
722 UINT numFiles
= ::DragQueryFileW(dropFiles
, 0xFFFFFFFF, nullptr, 0);
723 NS_ASSERTION(numFiles
> 0,
724 "File drop flavor, but no files...hmmmm");
725 NS_ASSERTION(aIndex
< numFiles
,
726 "Asked for a file index out of range of list");
729 ::DragQueryFileW(dropFiles
, aIndex
, nullptr, 0);
730 wchar_t* buffer
= reinterpret_cast<wchar_t*>(
731 moz_xmalloc((fileNameLen
+ 1) * sizeof(wchar_t)));
732 ::DragQueryFileW(dropFiles
, aIndex
, buffer
, fileNameLen
+ 1);
734 *aLen
= fileNameLen
* sizeof(char16_t
);
737 GlobalUnlock(stm
.hGlobal
);
742 if (fe
.cfFormat
== fileDescriptorFlavorA
||
743 fe
.cfFormat
== fileDescriptorFlavorW
||
744 fe
.cfFormat
== fileFlavor
) {
746 "Mozilla doesn't yet understand how to read this type of "
749 // Get the data out of the global data handle. The size we
750 // return should not include the null because the other
751 // platforms don't use nulls, so just return the length we get
752 // back from strlen(), since we know CF_UNICODETEXT is null
753 // terminated. Recall that GetGlobalData() returns the size of
754 // the allocated buffer, not the size of the data (on 98, these
755 // are not the same) so we can't use that.
757 // NOTE: we are assuming that anything that falls into this
758 // default case is unicode. As we start to get more
759 // kinds of binary data, this may become an incorrect
760 // assumption. Stay tuned.
761 uint32_t allocLen
= 0;
762 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
763 if (fe
.cfFormat
== GetHtmlClipboardFormat()) {
764 // CF_HTML is actually UTF8, not unicode, so disregard the
765 // assumption above. We have to check the header for the
766 // actual length, and we'll do that in FindPlatformHTML().
767 // For now, return the allocLen. This case is mostly to
768 // ensure we don't try to call strlen on the buffer.
770 } else if (fe
.cfFormat
== GetCustomClipboardFormat()) {
773 } else if (fe
.cfFormat
== preferredDropEffect
) {
774 // As per the MSDN doc entitled: "Shell Clipboard Formats"
775 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
777 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
779 allocLen
== sizeof(DWORD
),
780 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
783 *aLen
= NS_strlen(reinterpret_cast<char16_t
*>(*aData
)) *
795 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Info
,
796 ("*********************** TYMED_GDI\n"));
804 ReleaseStgMedium(&stm
);
810 //-------------------------------------------------------------------------
811 nsresult
nsClipboard::GetDataFromDataObject(IDataObject
* aDataObject
,
812 UINT anIndex
, nsIWidget
* aWindow
,
813 nsITransferable
* aTransferable
) {
814 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
, ("%s", __FUNCTION__
));
816 // make sure we have a good transferable
817 if (!aTransferable
) {
818 return NS_ERROR_INVALID_ARG
;
821 nsresult res
= NS_ERROR_FAILURE
;
823 // get flavor list that includes all flavors that can be written (including
824 // ones obtained through conversion)
825 nsTArray
<nsCString
> flavors
;
826 res
= aTransferable
->FlavorsTransferableCanImport(flavors
);
827 if (NS_FAILED(res
)) {
828 return NS_ERROR_FAILURE
;
831 // Walk through flavors and see which flavor is on the clipboard them on the
833 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
834 nsCString
& flavorStr
= flavors
[i
];
835 UINT format
= GetFormat(flavorStr
.get());
837 // Try to get the data using the desired flavor. This might fail, but all is
839 void* data
= nullptr;
840 uint32_t dataLen
= 0;
841 bool dataFound
= false;
842 if (nullptr != aDataObject
) {
843 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject
, anIndex
, format
,
844 flavorStr
.get(), &data
,
848 } else if (nullptr != aWindow
) {
849 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow
, anIndex
, format
,
855 // This is our second chance to try to find some data, having not found it
856 // when directly asking for the flavor. Let's try digging around in other
857 // flavors to help satisfy our craving for data.
859 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
861 FindUnicodeFromPlainText(aDataObject
, anIndex
, &data
, &dataLen
);
862 } else if (flavorStr
.EqualsLiteral(kURLMime
)) {
863 // drags from other windows apps expose the native
864 // CFSTR_INETURL{A,W} flavor
865 dataFound
= FindURLFromNativeURL(aDataObject
, anIndex
, &data
, &dataLen
);
868 FindURLFromLocalFile(aDataObject
, anIndex
, &data
, &dataLen
);
871 } // if we try one last ditch effort to find our data
873 // Hopefully by this point we've found it and can go about our business
875 nsCOMPtr
<nsISupports
> genericDataWrapper
;
876 if (flavorStr
.EqualsLiteral(kFileMime
)) {
877 // we have a file path in |data|. Create an nsLocalFile object.
878 nsDependentString
filepath(reinterpret_cast<char16_t
*>(data
));
879 nsCOMPtr
<nsIFile
> file
;
881 NS_NewLocalFile(filepath
, false, getter_AddRefs(file
)))) {
882 genericDataWrapper
= do_QueryInterface(file
);
885 } else if (flavorStr
.EqualsLiteral(kNativeHTMLMime
)) {
887 // the editor folks want CF_HTML exactly as it's on the clipboard, no
888 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
889 // a wrapper and hand it back to them.
890 if (FindPlatformHTML(aDataObject
, anIndex
, &data
, &dummy
, &dataLen
)) {
891 nsPrimitiveHelpers::CreatePrimitiveForData(
892 flavorStr
, data
, dataLen
, getter_AddRefs(genericDataWrapper
));
895 continue; // something wrong with this flavor, keep looking for other
899 } else if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
900 uint32_t startOfData
= 0;
901 // The JS folks want CF_HTML exactly as it is on the clipboard, but
902 // minus the CF_HTML header index information.
903 // It also needs to be converted to UTF16 and have linebreaks changed.
904 if (FindPlatformHTML(aDataObject
, anIndex
, &data
, &startOfData
,
906 dataLen
-= startOfData
;
907 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
908 static_cast<char*>(data
) + startOfData
, &dataLen
,
909 getter_AddRefs(genericDataWrapper
));
912 continue; // something wrong with this flavor, keep looking for other
916 } else if (flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
917 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
918 flavorStr
.EqualsLiteral(kPNGImageMime
)) {
919 nsIInputStream
* imageStream
= reinterpret_cast<nsIInputStream
*>(data
);
920 genericDataWrapper
= do_QueryInterface(imageStream
);
921 NS_IF_RELEASE(imageStream
);
923 // Treat custom types as a string of bytes.
924 if (!flavorStr
.EqualsLiteral(kCustomTypesMime
)) {
925 // we probably have some form of text. The DOM only wants LF, so
926 // convert from Win32 line endings to DOM line endings.
927 int32_t signedLen
= static_cast<int32_t>(dataLen
);
928 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr
, &data
,
932 if (flavorStr
.EqualsLiteral(kRTFMime
)) {
933 // RTF on Windows is known to sometimes deliver an extra null byte.
934 if (dataLen
> 0 && static_cast<char*>(data
)[dataLen
- 1] == '\0') {
940 nsPrimitiveHelpers::CreatePrimitiveForData(
941 flavorStr
, data
, dataLen
, getter_AddRefs(genericDataWrapper
));
945 NS_ASSERTION(genericDataWrapper
,
946 "About to put null data into the transferable");
947 aTransferable
->SetTransferData(flavorStr
.get(), genericDataWrapper
);
950 // we found one, get out of the loop
961 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
964 bool nsClipboard ::FindPlatformHTML(IDataObject
* inDataObject
, UINT inIndex
,
965 void** outData
, uint32_t* outStartOfData
,
966 uint32_t* outDataLen
) {
967 // Reference: MSDN doc entitled "HTML Clipboard Format"
968 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
969 // CF_HTML is UTF8, not unicode. We also can't rely on it being
970 // null-terminated so we have to check the CF_HTML header for the correct
971 // length. The length we return is the bytecount from the beginning of the
972 // selected data to the end of the selected data, without the null
973 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
975 if (!outData
|| !*outData
) {
979 char version
[8] = {0};
980 int32_t startOfData
= 0;
981 int32_t endOfData
= 0;
983 sscanf((char*)*outData
, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version
,
984 &startOfData
, &endOfData
);
986 if (numFound
!= 3 || startOfData
< -1 || endOfData
< -1) {
990 // Fixup the start and end markers if they have no context (set to -1)
991 if (startOfData
== -1) {
994 if (endOfData
== -1) {
995 endOfData
= *outDataLen
;
998 // Make sure we were passed sane values within our buffer size.
999 // (Note that we've handled all cases of negative endOfData above, so we can
1000 // safely cast it to be unsigned here.)
1001 if (!endOfData
|| startOfData
>= endOfData
||
1002 static_cast<uint32_t>(endOfData
) > *outDataLen
) {
1006 // We want to return the buffer not offset by startOfData because it will be
1007 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
1008 // in CF_HTML format.
1010 // We return the byte offset from the start of the data buffer to where the
1011 // HTML data starts. The caller might want to extract the HTML only.
1012 *outStartOfData
= startOfData
;
1013 *outDataLen
= endOfData
;
1018 // FindUnicodeFromPlainText
1020 // we are looking for text/unicode and we failed to find it on the clipboard
1021 // first, try again with text/plain. If that is present, convert it to unicode.
1023 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject
* inDataObject
,
1024 UINT inIndex
, void** outData
,
1025 uint32_t* outDataLen
) {
1026 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
, ("%s", __FUNCTION__
));
1028 // we are looking for text/unicode and we failed to find it on the clipboard
1029 // first, try again with text/plain. If that is present, convert it to
1032 GetNativeDataOffClipboard(inDataObject
, inIndex
, GetFormat(kTextMime
),
1033 nullptr, outData
, outDataLen
);
1034 if (NS_FAILED(rv
) || !*outData
) {
1038 const char* castedText
= static_cast<char*>(*outData
);
1040 rv
= NS_CopyNativeToUnicode(nsDependentCSubstring(castedText
, *outDataLen
),
1042 if (NS_FAILED(rv
)) {
1046 // out with the old, in with the new
1048 *outData
= ToNewUnicode(tmp
);
1049 *outDataLen
= tmp
.Length() * sizeof(char16_t
);
1053 } // FindUnicodeFromPlainText
1056 // FindURLFromLocalFile
1058 // we are looking for a URL and couldn't find it, try again with looking for
1059 // a local file. If we have one, it may either be a normal file or an internet
1060 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
1061 // in the local file case).
1063 bool nsClipboard ::FindURLFromLocalFile(IDataObject
* inDataObject
, UINT inIndex
,
1064 void** outData
, uint32_t* outDataLen
) {
1065 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
, ("%s", __FUNCTION__
));
1067 bool dataFound
= false;
1069 nsresult loadResult
=
1070 GetNativeDataOffClipboard(inDataObject
, inIndex
, GetFormat(kFileMime
),
1071 nullptr, outData
, outDataLen
);
1072 if (NS_SUCCEEDED(loadResult
) && *outData
) {
1073 // we have a file path in |data|. Is it an internet shortcut or a normal
1075 const nsDependentString
filepath(static_cast<char16_t
*>(*outData
));
1076 nsCOMPtr
<nsIFile
> file
;
1077 nsresult rv
= NS_NewLocalFile(filepath
, true, getter_AddRefs(file
));
1078 if (NS_FAILED(rv
)) {
1083 if (IsInternetShortcut(filepath
)) {
1086 ResolveShortcut(file
, url
);
1087 if (!url
.IsEmpty()) {
1088 // convert it to unicode and pass it out
1089 NS_ConvertUTF8toUTF16
urlString(url
);
1090 // the internal mozilla URL format, text/x-moz-url, contains
1091 // URL\ntitle. We can guess the title from the file's name.
1093 file
->GetLeafName(title
);
1094 // We rely on IsInternetShortcut check that file has a .url extension.
1095 title
.SetLength(title
.Length() - 4);
1096 if (title
.IsEmpty()) {
1099 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ title
);
1101 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1106 // we have a normal file, use some Necko objects to get our file path
1107 nsAutoCString urlSpec
;
1108 NS_GetURLSpecFromFile(file
, urlSpec
);
1110 // convert it to unicode and pass it out
1112 *outData
= UTF8ToNewUnicode(urlSpec
);
1114 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1116 } // else regular file
1120 } // FindURLFromLocalFile
1123 // FindURLFromNativeURL
1125 // we are looking for a URL and couldn't find it using our internal
1126 // URL flavor, so look for it using the native URL flavor,
1127 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
1129 bool nsClipboard ::FindURLFromNativeURL(IDataObject
* inDataObject
, UINT inIndex
,
1130 void** outData
, uint32_t* outDataLen
) {
1131 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
, ("%s", __FUNCTION__
));
1133 bool dataFound
= false;
1135 void* tempOutData
= nullptr;
1136 uint32_t tempDataLen
= 0;
1138 nsresult loadResult
= GetNativeDataOffClipboard(
1139 inDataObject
, inIndex
, ::RegisterClipboardFormat(CFSTR_INETURLW
), nullptr,
1140 &tempOutData
, &tempDataLen
);
1141 if (NS_SUCCEEDED(loadResult
) && tempOutData
) {
1142 nsDependentString
urlString(static_cast<char16_t
*>(tempOutData
));
1143 // the internal mozilla URL format, text/x-moz-url, contains
1144 // URL\ntitle. Since we don't actually have a title here,
1145 // just repeat the URL to fake it.
1146 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ urlString
);
1148 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1152 loadResult
= GetNativeDataOffClipboard(
1153 inDataObject
, inIndex
, ::RegisterClipboardFormat(CFSTR_INETURLA
),
1154 nullptr, &tempOutData
, &tempDataLen
);
1155 if (NS_SUCCEEDED(loadResult
) && tempOutData
) {
1156 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
1157 // CF_TEXT which is by definition ANSI encoded.
1158 nsCString urlUnescapedA
;
1160 NS_UnescapeURL(static_cast<char*>(tempOutData
), tempDataLen
,
1161 esc_OnlyNonASCII
| esc_SkipControl
, urlUnescapedA
);
1165 NS_CopyNativeToUnicode(urlUnescapedA
, urlString
);
1167 NS_CopyNativeToUnicode(
1168 nsDependentCString(static_cast<char*>(tempOutData
), tempDataLen
),
1172 // the internal mozilla URL format, text/x-moz-url, contains
1173 // URL\ntitle. Since we don't actually have a title here,
1174 // just repeat the URL to fake it.
1175 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ urlString
);
1177 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1184 } // FindURLFromNativeURL
1186 // Other apps can block access to the clipboard. This repeatedly
1187 // calls `::OleGetClipboard` for a fixed number of times and should be called
1188 // instead of `::OleGetClipboard`.
1189 static HRESULT
RepeatedlyTryOleGetClipboard(IDataObject
** aDataObj
) {
1190 return RepeatedlyTry(::OleGetClipboard
, LogOleGetClipboardResult
, aDataObj
);
1196 void nsClipboard ::ResolveShortcut(nsIFile
* aFile
, nsACString
& outURL
) {
1197 nsCOMPtr
<nsIFileProtocolHandler
> fph
;
1198 nsresult rv
= NS_GetFileProtocolHandler(getter_AddRefs(fph
));
1199 if (NS_FAILED(rv
)) {
1203 nsCOMPtr
<nsIURI
> uri
;
1204 rv
= fph
->ReadURLFile(aFile
, getter_AddRefs(uri
));
1205 if (NS_FAILED(rv
)) {
1209 uri
->GetSpec(outURL
);
1210 } // ResolveShortcut
1213 // IsInternetShortcut
1215 // A file is an Internet Shortcut if it ends with .URL
1217 bool nsClipboard ::IsInternetShortcut(const nsAString
& inFileName
) {
1218 return StringEndsWith(inFileName
, u
".url"_ns
,
1219 nsCaseInsensitiveStringComparator
);
1220 } // IsInternetShortcut
1222 //-------------------------------------------------------------------------
1224 nsClipboard::GetNativeClipboardData(nsITransferable
* aTransferable
,
1225 int32_t aWhichClipboard
) {
1226 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Debug
,
1227 ("%s aWhichClipboard=%i", __FUNCTION__
, aWhichClipboard
));
1229 // make sure we have a good transferable
1230 if (!aTransferable
|| aWhichClipboard
!= kGlobalClipboard
) {
1231 return NS_ERROR_FAILURE
;
1236 // This makes sure we can use the OLE functionality for the clipboard
1237 IDataObject
* dataObj
;
1238 if (S_OK
== RepeatedlyTryOleGetClipboard(&dataObj
)) {
1239 // Use OLE IDataObject for clipboard operations
1240 MOZ_LOG(gWin32ClipboardLog
, LogLevel::Verbose
,
1241 ("%s: use OLE IDataObject.", __FUNCTION__
));
1242 res
= GetDataFromDataObject(dataObj
, 0, nullptr, aTransferable
);
1245 // do it the old manual way
1246 res
= GetDataFromDataObject(nullptr, 0, mWindow
, aTransferable
);
1252 nsClipboard::EmptyClipboard(int32_t aWhichClipboard
) {
1253 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1254 // clipboard to scan it. If we i) empty and then ii) set data, then the
1255 // 'set data' can sometimes fail with access denied becacuse another program
1256 // has the clipboard open. So to avoid this race condition for OpenClipboard
1257 // we do not empty the clipboard when we're setting it.
1258 if (aWhichClipboard
== kGlobalClipboard
&& !mEmptyingForSetData
) {
1259 RepeatedlyTryOleSetClipboard(nullptr);
1261 return nsBaseClipboard::EmptyClipboard(aWhichClipboard
);
1264 //-------------------------------------------------------------------------
1265 NS_IMETHODIMP
nsClipboard::HasDataMatchingFlavors(
1266 const nsTArray
<nsCString
>& aFlavorList
, int32_t aWhichClipboard
,
1269 if (aWhichClipboard
!= kGlobalClipboard
) {
1273 for (auto& flavor
: aFlavorList
) {
1275 if (flavor
.EqualsLiteral(kTextMime
)) {
1277 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
1282 UINT format
= GetFormat(flavor
.get());
1283 if (IsClipboardFormatAvailable(format
)) {
1287 // We haven't found the exact flavor the client asked for, but maybe we
1288 // can still find it from something else that's on the clipboard...
1289 if (flavor
.EqualsLiteral(kUnicodeMime
)) {
1290 // client asked for unicode and it wasn't present, check if we have
1291 // CF_TEXT. We'll handle the actual data substitution in the data
1293 if (IsClipboardFormatAvailable(GetFormat(kTextMime
))) {