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
19 # include "mozilla/a11y/Compatibility.h"
21 #include "mozilla/Logging.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/StaticPrefs_clipboard.h"
24 #include "mozilla/StaticPrefs_widget.h"
25 #include "mozilla/WindowsVersion.h"
26 #include "SpecialSystemDirectory.h"
28 #include "nsArrayUtils.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsDataObj.h"
33 #include "nsNativeCharsetUtils.h"
34 #include "nsIInputStream.h"
35 #include "nsITransferable.h"
37 #include "nsReadableUtils.h"
38 #include "nsUnicharUtils.h"
39 #include "nsPrimitiveHelpers.h"
40 #include "nsIWidget.h"
41 #include "nsWidgetsCID.h"
43 #include "nsNetUtil.h"
44 #include "nsIFileProtocolHandler.h"
46 #include "nsIObserverService.h"
47 #include "nsMimeTypes.h"
48 #include "imgITools.h"
49 #include "imgIContainer.h"
53 UINT
nsClipboard::GetClipboardFileDescriptorFormatA() {
54 static UINT format
= ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORA
);
60 UINT
nsClipboard::GetClipboardFileDescriptorFormatW() {
61 static UINT format
= ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW
);
67 UINT
nsClipboard::GetHtmlClipboardFormat() {
68 static UINT format
= ::RegisterClipboardFormatW(L
"HTML Format");
73 UINT
nsClipboard::GetCustomClipboardFormat() {
75 ::RegisterClipboardFormatW(L
"application/x-moz-custom-clipdata");
79 //-------------------------------------------------------------------------
81 // nsClipboard constructor
83 //-------------------------------------------------------------------------
84 nsClipboard::nsClipboard()
85 : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
86 false /* supportsSelectionClipboard */,
87 false /* supportsFindClipboard */,
88 false /* supportsSelectionCache */)) {
91 // Register for a shutdown notification so that we can flush data
92 // to the OS clipboard.
93 nsCOMPtr
<nsIObserverService
> observerService
=
94 do_GetService("@mozilla.org/observer-service;1");
95 if (observerService
) {
96 observerService
->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID
,
101 //-------------------------------------------------------------------------
102 // nsClipboard destructor
103 //-------------------------------------------------------------------------
104 nsClipboard::~nsClipboard() {}
106 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard
, nsBaseClipboard
, nsIObserver
)
109 nsClipboard::Observe(nsISupports
* aSubject
, const char* aTopic
,
110 const char16_t
* aData
) {
111 // This will be called on shutdown.
112 ::OleFlushClipboard();
118 //-------------------------------------------------------------------------
119 UINT
nsClipboard::GetFormat(const char* aMimeStr
, bool aMapHTMLMime
) {
122 if (strcmp(aMimeStr
, kTextMime
) == 0) {
123 format
= CF_UNICODETEXT
;
124 } else if (strcmp(aMimeStr
, kRTFMime
) == 0) {
125 format
= ::RegisterClipboardFormat(L
"Rich Text Format");
126 } else if (strcmp(aMimeStr
, kJPEGImageMime
) == 0 ||
127 strcmp(aMimeStr
, kJPGImageMime
) == 0 ||
128 strcmp(aMimeStr
, kPNGImageMime
) == 0) {
130 } else if (strcmp(aMimeStr
, kFileMime
) == 0 ||
131 strcmp(aMimeStr
, kFilePromiseMime
) == 0) {
133 } else if ((strcmp(aMimeStr
, kNativeHTMLMime
) == 0) ||
134 (aMapHTMLMime
&& strcmp(aMimeStr
, kHTMLMime
) == 0)) {
135 format
= GetHtmlClipboardFormat();
136 } else if (strcmp(aMimeStr
, kCustomTypesMime
) == 0) {
137 format
= GetCustomClipboardFormat();
139 format
= ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr
).get());
145 //-------------------------------------------------------------------------
147 nsresult
nsClipboard::CreateNativeDataObject(
148 nsITransferable
* aTransferable
, IDataObject
** aDataObj
, nsIURI
* aUri
,
149 MightNeedToFlush
* aMightNeedToFlush
) {
150 MOZ_ASSERT(aTransferable
);
151 if (!aTransferable
) {
152 return NS_ERROR_FAILURE
;
155 // Create our native DataObject that implements the OLE IDataObject interface
156 RefPtr
<nsDataObj
> dataObj
= new nsDataObj(aUri
);
158 // Now set it up with all the right data flavors & enums
160 SetupNativeDataObject(aTransferable
, dataObj
, aMightNeedToFlush
);
161 if (NS_SUCCEEDED(res
)) {
162 dataObj
.forget(aDataObj
);
167 static nsresult
StoreValueInDataObject(nsDataObj
* aObj
,
168 LPCWSTR aClipboardFormat
, DWORD value
) {
169 HGLOBAL hGlobalMemory
= ::GlobalAlloc(GMEM_MOVEABLE
, sizeof(DWORD
));
170 if (!hGlobalMemory
) {
171 return NS_ERROR_OUT_OF_MEMORY
;
173 DWORD
* pdw
= (DWORD
*)::GlobalLock(hGlobalMemory
);
175 ::GlobalUnlock(hGlobalMemory
);
178 stg
.tymed
= TYMED_HGLOBAL
;
179 stg
.pUnkForRelease
= nullptr;
180 stg
.hGlobal
= hGlobalMemory
;
183 SET_FORMATETC(fe
, ::RegisterClipboardFormat(aClipboardFormat
), 0,
184 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
185 aObj
->SetData(&fe
, &stg
, TRUE
);
190 //-------------------------------------------------------------------------
191 nsresult
nsClipboard::SetupNativeDataObject(
192 nsITransferable
* aTransferable
, IDataObject
* aDataObj
,
193 MightNeedToFlush
* aMightNeedToFlush
) {
194 MOZ_ASSERT(aTransferable
);
195 MOZ_ASSERT(aDataObj
);
196 if (!aTransferable
|| !aDataObj
) {
197 return NS_ERROR_FAILURE
;
200 auto* dObj
= static_cast<nsDataObj
*>(aDataObj
);
201 if (aMightNeedToFlush
) {
202 *aMightNeedToFlush
= MightNeedToFlush::No
;
205 // Now give the Transferable to the DataObject
206 // for getting the data out of it
207 dObj
->SetTransferable(aTransferable
);
209 // Get the transferable list of data flavors
210 nsTArray
<nsCString
> flavors
;
211 aTransferable
->FlavorsTransferableCanExport(flavors
);
213 // Walk through flavors that contain data and register them
214 // into the DataObj as supported flavors
215 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
216 nsCString
& flavorStr
= flavors
[i
];
218 // When putting data onto the clipboard, we want to maintain kHTMLMime
219 // ("text/html") and not map it to CF_HTML here since this will be done
221 UINT format
= GetFormat(flavorStr
.get(), false);
223 // Now tell the native IDataObject about both our mime type and
224 // the native data format
226 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
227 dObj
->AddDataFlavor(flavorStr
.get(), &fe
);
229 // Do various things internal to the implementation, like map one
230 // flavor to another or add additional flavors based on what's required
231 // for the win32 impl.
232 if (flavorStr
.EqualsLiteral(kTextMime
)) {
233 // if we find text/plain, also add CF_TEXT, but we can add it for
234 // text/plain as well.
236 SET_FORMATETC(textFE
, CF_TEXT
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
237 dObj
->AddDataFlavor(kTextMime
, &textFE
);
238 if (aMightNeedToFlush
) {
239 *aMightNeedToFlush
= MightNeedToFlush::Yes
;
241 } else if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
242 // if we find text/html, also advertise win32's html flavor (which we will
243 // convert on our own in nsDataObj::GetText().
245 SET_FORMATETC(htmlFE
, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT
, -1,
247 dObj
->AddDataFlavor(kHTMLMime
, &htmlFE
);
248 } else if (flavorStr
.EqualsLiteral(kURLMime
)) {
249 // if we're a url, in addition to also being text, we need to register
250 // the "file" flavors so that the win32 shell knows to create an internet
251 // shortcut when it sees one of these beasts.
252 FORMATETC shortcutFE
;
253 SET_FORMATETC(shortcutFE
,
254 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA
), 0,
255 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
256 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
257 SET_FORMATETC(shortcutFE
,
258 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW
), 0,
259 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
260 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
261 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_FILECONTENTS
),
262 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
263 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
264 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_INETURLA
), 0,
265 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
266 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
267 SET_FORMATETC(shortcutFE
, ::RegisterClipboardFormat(CFSTR_INETURLW
), 0,
268 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
269 dObj
->AddDataFlavor(kURLMime
, &shortcutFE
);
270 } else if (flavorStr
.EqualsLiteral(kPNGImageMime
) ||
271 flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
272 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
273 flavorStr
.EqualsLiteral(kGIFImageMime
) ||
274 flavorStr
.EqualsLiteral(kNativeImageMime
)) {
275 // if we're an image, register the relevant bitmap flavors
278 // Add PNG, depending on prefs
279 if (mozilla::StaticPrefs::clipboard_copy_image_as_png()) {
280 static const CLIPFORMAT CF_PNG
= ::RegisterClipboardFormat(TEXT("PNG"));
281 SET_FORMATETC(imageFE
, CF_PNG
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
282 dObj
->AddDataFlavor(flavorStr
.get(), &imageFE
);
286 SET_FORMATETC(imageFE
, CF_DIBV5
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
287 dObj
->AddDataFlavor(flavorStr
.get(), &imageFE
);
290 SET_FORMATETC(imageFE
, CF_DIB
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
291 dObj
->AddDataFlavor(flavorStr
.get(), &imageFE
);
292 } else if (flavorStr
.EqualsLiteral(kFilePromiseMime
)) {
293 // if we're a file promise flavor, also register the
294 // CFSTR_PREFERREDDROPEFFECT format. The data object
295 // returns a value of DROPEFFECTS_MOVE to the drop target
296 // when it asks for the value of this format. This causes
297 // the file to be moved from the temporary location instead
298 // of being copied. The right thing to do here is to call
299 // SetData() on the data object and set the value of this format
300 // to DROPEFFECTS_MOVE on this particular data object. But,
301 // since all the other clipboard formats follow the model of setting
302 // data on the data object only when the drop object calls GetData(),
303 // I am leaving this format's value hard coded in the data object.
304 // We can change this if other consumers of this format get added to this
305 // codebase and they need different values.
306 FORMATETC shortcutFE
;
307 SET_FORMATETC(shortcutFE
,
308 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT
), 0,
309 DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
)
310 dObj
->AddDataFlavor(kFilePromiseMime
, &shortcutFE
);
314 if (!mozilla::StaticPrefs::
315 clipboard_copyPrivateDataToClipboardCloudOrHistory()) {
316 // Let Clipboard know that data is sensitive and must not be copied to
317 // the Cloud Clipboard, Clipboard History and similar.
318 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats
319 if (aTransferable
->GetIsPrivateData()) {
321 StoreValueInDataObject(dObj
, TEXT("CanUploadToCloudClipboard"), 0);
322 NS_ENSURE_SUCCESS(rv
, rv
);
324 StoreValueInDataObject(dObj
, TEXT("CanIncludeInClipboardHistory"), 0);
325 NS_ENSURE_SUCCESS(rv
, rv
);
326 rv
= StoreValueInDataObject(
327 dObj
, TEXT("ExcludeClipboardContentFromMonitorProcessing"), 0);
328 NS_ENSURE_SUCCESS(rv
, rv
);
335 // See methods listed at
336 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
337 static void IDataObjectMethodResultToString(const HRESULT aHres
,
338 nsACString
& aResult
) {
341 aResult
= "E_INVALIDARG";
344 aResult
= "E_UNEXPECTED";
347 aResult
= "E_OUTOFMEMORY";
350 aResult
= "DV_E_LINDEX";
353 aResult
= "DV_E_FORMATETC";
356 aResult
= "DV_E_TYMED";
359 aResult
= "DV_E_DVASPECT";
361 case OLE_E_NOTRUNNING
:
362 aResult
= "OLE_E_NOTRUNNING";
364 case STG_E_MEDIUMFULL
:
365 aResult
= "STG_E_MEDIUMFULL";
367 case DV_E_CLIPFORMAT
:
368 aResult
= "DV_E_CLIPFORMAT";
374 // Explicit template instantiaton, because otherwise the call is
376 constexpr int kRadix
= 16;
377 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
383 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
384 static void OleGetClipboardResultToString(const HRESULT aHres
,
385 nsACString
& aResult
) {
390 case CLIPBRD_E_CANT_OPEN
:
391 aResult
= "CLIPBRD_E_CANT_OPEN";
393 case CLIPBRD_E_CANT_CLOSE
:
394 aResult
= "CLIPBRD_E_CANT_CLOSE";
397 // Explicit template instantiaton, because otherwise the call is
399 constexpr int kRadix
= 16;
400 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
405 static void MaybeLogClipboardCurrentOwner(
406 const HRESULT aHres
, const mozilla::StaticString
& aMethodName
) {
407 if (!MOZ_CLIPBOARD_LOG_ENABLED()) {
411 if (aHres
!= CLIPBRD_E_CANT_OPEN
) {
414 auto hwnd
= ::GetOpenClipboardWindow();
417 "IDataObject::%s | Clipboard already opened by unknown process",
422 DWORD threadId
= ::GetWindowThreadProcessId(hwnd
, &procId
);
423 NS_ENSURE_TRUE_VOID(threadId
);
424 nsAutoString procName
;
425 NS_ENSURE_SUCCESS_VOID(
426 mozilla::widget::WinUtils::GetProcessImageName(procId
, procName
));
428 "IDataObject::%s | Clipboard already opened by HWND: %p | "
429 "Process ID: %lu | Thread ID: %lu | App name: %s",
430 aMethodName
.get(), hwnd
, procId
, threadId
,
431 NS_ConvertUTF16toUTF8(procName
).get());
435 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
436 static void LogOleGetClipboardResult(const HRESULT aHres
) {
437 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
438 nsAutoCString hresString
;
439 OleGetClipboardResultToString(aHres
, hresString
);
440 MOZ_CLIPBOARD_LOG("OleGetClipboard result: %s", hresString
.get());
441 MaybeLogClipboardCurrentOwner(aHres
, "OleGetClipboard");
446 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
447 static void OleSetClipboardResultToString(HRESULT aHres
, nsACString
& aResult
) {
452 case CLIPBRD_E_CANT_OPEN
:
453 aResult
= "CLIPBRD_E_CANT_OPEN";
455 case CLIPBRD_E_CANT_EMPTY
:
456 aResult
= "CLIPBRD_E_CANT_EMPTY";
458 case CLIPBRD_E_CANT_CLOSE
:
459 aResult
= "CLIPBRD_E_CANT_CLOSE";
461 case CLIPBRD_E_CANT_SET
:
462 aResult
= "CLIPBRD_E_CANT_SET";
465 // Explicit template instantiaton, because otherwise the call is
467 constexpr int kRadix
= 16;
468 aResult
= IntToCString
<int32_t>(aHres
, kRadix
);
474 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
475 static void LogOleSetClipboardResult(const HRESULT aHres
) {
476 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
477 nsAutoCString hresString
;
478 OleSetClipboardResultToString(aHres
, hresString
);
479 MOZ_CLIPBOARD_LOG("OleSetClipboard result: %s", hresString
.get());
480 MaybeLogClipboardCurrentOwner(aHres
, "OleSetClipboard");
484 template <typename Function
, typename LogFunction
, typename
... Args
>
485 static HRESULT
RepeatedlyTry(Function aFunction
, LogFunction aLogFunction
,
487 // These are magic values based on local testing. They are chosen not higher
488 // to avoid jank (<https://developer.mozilla.org/en-US/docs/Glossary/Jank>).
489 // When changing them, be careful.
490 static constexpr int kNumberOfTries
= 3;
491 static constexpr int kDelayInMs
= 3;
494 for (int i
= 0; i
< kNumberOfTries
; ++i
) {
495 hres
= aFunction(aArgs
...);
502 // TODO: This was formerly std::sleep_for, which wasn't actually sleeping
503 // in tests (bug 1927664).
504 ::SleepEx(kDelayInMs
, TRUE
);
510 // Other apps can block access to the clipboard. This repeatedly
511 // calls `::OleSetClipboard` for a fixed number of times and should be called
512 // instead of `::OleSetClipboard`.
513 static void RepeatedlyTryOleSetClipboard(IDataObject
* aDataObj
) {
514 RepeatedlyTry(::OleSetClipboard
, LogOleSetClipboardResult
, aDataObj
);
517 //-------------------------------------------------------------------------
518 NS_IMETHODIMP
nsClipboard::SetNativeClipboardData(
519 nsITransferable
* aTransferable
, ClipboardType aWhichClipboard
) {
520 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
522 if (aWhichClipboard
!= kGlobalClipboard
) {
523 return NS_ERROR_FAILURE
;
526 // make sure we have a good transferable
527 if (!aTransferable
) {
528 return NS_ERROR_FAILURE
;
532 mozilla::a11y::Compatibility::SuppressA11yForClipboardCopy();
535 RefPtr
<IDataObject
> dataObj
;
536 auto mightNeedToFlush
= MightNeedToFlush::No
;
537 if (NS_SUCCEEDED(CreateNativeDataObject(aTransferable
,
538 getter_AddRefs(dataObj
), nullptr,
539 &mightNeedToFlush
))) {
540 RepeatedlyTryOleSetClipboard(dataObj
);
542 const bool doFlush
= [&] {
543 switch (mozilla::StaticPrefs::widget_windows_sync_clipboard_flush()) {
549 // Bug 1774285: Windows Suggested Actions (introduced in Windows 11
550 // 22H2) walks the entire a11y tree using UIA if something is placed
551 // on the clipboard using delayed rendering. (The OLE clipboard always
552 // uses delayed rendering.) This a11y tree walk causes an unacceptable
553 // hang, particularly when the a11y cache is disabled. We choose the
554 // lesser of the two performance/memory evils here and force immediate
555 // rendering as part of our workaround.
556 return mightNeedToFlush
== MightNeedToFlush::Yes
&&
557 mozilla::IsWin1122H2OrLater();
561 RepeatedlyTry(::OleFlushClipboard
, [](HRESULT
) {});
564 // Clear the native clipboard
565 RepeatedlyTryOleSetClipboard(nullptr);
571 //-------------------------------------------------------------------------
572 nsresult
nsClipboard::GetGlobalData(HGLOBAL aHGBL
, void** aData
,
574 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
576 // Allocate a new memory buffer and copy the data from global memory.
577 // Recall that win98 allocates to nearest DWORD boundary. As a safety
578 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
579 // and null them out to ensure that all of our NS_strlen calls will succeed.
580 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
581 // a full NUL char16_t when |*aLen| is odd.
582 nsresult result
= NS_ERROR_FAILURE
;
583 if (aHGBL
!= nullptr) {
584 LPSTR lpStr
= (LPSTR
)GlobalLock(aHGBL
);
585 mozilla::CheckedInt
<uint32_t> allocSize
=
586 mozilla::CheckedInt
<uint32_t>(GlobalSize(aHGBL
)) + 3;
587 if (!allocSize
.isValid()) {
588 return NS_ERROR_INVALID_ARG
;
590 char* data
= static_cast<char*>(malloc(allocSize
.value()));
592 uint32_t size
= allocSize
.value() - 3;
593 memcpy(data
, lpStr
, size
);
594 // null terminate for safety
595 data
[size
] = data
[size
+ 1] = data
[size
+ 2] = '\0';
604 // We really shouldn't ever get here
611 FORMAT_MESSAGE_ALLOCATE_BUFFER
| FORMAT_MESSAGE_FROM_SYSTEM
, nullptr,
613 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), // Default language
614 (LPWSTR
)&lpMsgBuf
, 0, nullptr);
616 // Display the string.
617 MessageBoxW(nullptr, (LPCWSTR
)lpMsgBuf
, L
"GetLastError",
618 MB_OK
| MB_ICONINFORMATION
);
627 //-------------------------------------------------------------------------
628 nsresult
nsClipboard::GetNativeDataOffClipboard(nsIWidget
* aWidget
,
629 UINT
/*aIndex*/, UINT aFormat
,
630 void** aData
, uint32_t* aLen
) {
631 MOZ_CLIPBOARD_LOG("%s: overload taking nsIWidget*.", __FUNCTION__
);
634 nsresult result
= NS_ERROR_FAILURE
;
636 HWND nativeWin
= nullptr;
637 if (::OpenClipboard(nativeWin
)) {
638 hglb
= ::GetClipboardData(aFormat
);
639 result
= GetGlobalData(hglb
, aData
, aLen
);
645 // See methods listed at
646 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
647 static void LogIDataObjectMethodResult(const HRESULT aHres
,
648 mozilla::StaticString aMethodName
) {
649 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
650 nsAutoCString hresString
;
651 IDataObjectMethodResultToString(aHres
, hresString
);
652 MOZ_CLIPBOARD_LOG("IDataObject::%s result : %s", aMethodName
.get(),
654 MaybeLogClipboardCurrentOwner(aHres
, aMethodName
);
658 // Other apps can block access to the clipboard. This repeatedly calls
659 // `GetData` for a fixed number of times and should be called instead of
661 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-idataobject-getdata>.
662 // While Microsoft's documentation doesn't include `CLIPBRD_E_CANT_OPEN`
663 // explicitly, it allows it implicitly and in local experiments it was indeed
665 static HRESULT
RepeatedlyTryGetData(IDataObject
& aDataObject
, LPFORMATETC pFE
,
667 return RepeatedlyTry(
668 [&aDataObject
, &pFE
, &pSTM
]() { return aDataObject
.GetData(pFE
, pSTM
); },
669 [](HRESULT hres
) { LogIDataObjectMethodResult(hres
, "GetData"); });
672 //-------------------------------------------------------------------------
674 HRESULT
nsClipboard::FillSTGMedium(IDataObject
* aDataObject
, UINT aFormat
,
675 LPFORMATETC pFE
, LPSTGMEDIUM pSTM
,
677 SET_FORMATETC(*pFE
, aFormat
, 0, DVASPECT_CONTENT
, -1, aTymed
);
679 // Starting by querying for the data to see if we can get it as from global
681 HRESULT hres
= S_FALSE
;
682 hres
= aDataObject
->QueryGetData(pFE
);
683 LogIDataObjectMethodResult(hres
, "QueryGetData");
685 hres
= RepeatedlyTryGetData(*aDataObject
, pFE
, pSTM
);
690 //-------------------------------------------------------------------------
691 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
692 // an image encoder (e.g. image/png).
693 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
694 nsresult
nsClipboard::GetNativeDataOffClipboard(IDataObject
* aDataObject
,
695 UINT aIndex
, UINT aFormat
,
696 const char* aMIMEImageFormat
,
697 void** aData
, uint32_t* aLen
) {
698 MOZ_CLIPBOARD_LOG("%s: overload taking IDataObject*.", __FUNCTION__
);
700 nsresult result
= NS_ERROR_FAILURE
;
708 UINT format
= aFormat
;
709 HRESULT hres
= S_FALSE
;
711 // XXX at the moment we only support global memory transfers
712 // It is here where we will add support for native images
716 hres
= FillSTGMedium(aDataObject
, format
, &fe
, &stm
, TYMED_HGLOBAL
);
718 // If the format is CF_HDROP and we haven't found any files we can try looking
719 // for virtual files with FILEDESCRIPTOR.
720 if (FAILED(hres
) && format
== CF_HDROP
) {
721 hres
= FillSTGMedium(aDataObject
,
722 nsClipboard::GetClipboardFileDescriptorFormatW(), &fe
,
723 &stm
, TYMED_HGLOBAL
);
725 hres
= FillSTGMedium(aDataObject
,
726 nsClipboard::GetClipboardFileDescriptorFormatA(),
727 &fe
, &stm
, TYMED_HGLOBAL
);
731 // Currently this is only handling TYMED_HGLOBAL data
732 // For Text, Dibs, Files, and generic data (like HTML)
734 static CLIPFORMAT fileDescriptorFlavorA
=
735 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA
);
736 static CLIPFORMAT fileDescriptorFlavorW
=
737 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW
);
738 static CLIPFORMAT fileFlavor
=
739 ::RegisterClipboardFormat(CFSTR_FILECONTENTS
);
740 static CLIPFORMAT preferredDropEffect
=
741 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT
);
744 case TYMED_HGLOBAL
: {
745 switch (fe
.cfFormat
) {
747 // Get the data out of the global data handle. The size we
748 // return should not include the null because the other
749 // platforms don't use nulls, so just return the length we get
750 // back from strlen(), since we know CF_TEXT is null
751 // terminated. Recall that GetGlobalData() returns the size of
752 // the allocated buffer, not the size of the data (on 98, these
753 // are not the same) so we can't use that.
754 uint32_t allocLen
= 0;
755 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
756 *aLen
= strlen(reinterpret_cast<char*>(*aData
));
761 case CF_UNICODETEXT
: {
762 // Get the data out of the global data handle. The size we
763 // return should not include the null because the other
764 // platforms don't use nulls, so just return the length we get
765 // back from strlen(), since we know CF_UNICODETEXT is null
766 // terminated. Recall that GetGlobalData() returns the size of
767 // the allocated buffer, not the size of the data (on 98, these
768 // are not the same) so we can't use that.
769 uint32_t allocLen
= 0;
770 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
771 *aLen
= NS_strlen(reinterpret_cast<char16_t
*>(*aData
)) * 2;
777 if (aMIMEImageFormat
) {
778 uint32_t allocLen
= 0;
779 const char* clipboardData
;
780 if (NS_SUCCEEDED(GetGlobalData(
781 stm
.hGlobal
, (void**)&clipboardData
, &allocLen
))) {
782 nsCOMPtr
<imgIContainer
> container
;
783 nsCOMPtr
<imgITools
> imgTools
=
784 do_CreateInstance("@mozilla.org/image/tools;1");
785 result
= imgTools
->DecodeImageFromBuffer(
786 clipboardData
, allocLen
,
787 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD
),
788 getter_AddRefs(container
));
789 if (NS_FAILED(result
)) {
793 nsAutoCString mimeType
;
794 if (strcmp(aMIMEImageFormat
, kJPGImageMime
) == 0) {
795 mimeType
.Assign(IMAGE_JPEG
);
797 mimeType
.Assign(aMIMEImageFormat
);
800 nsCOMPtr
<nsIInputStream
> inputStream
;
801 result
= imgTools
->EncodeImage(container
, mimeType
, u
""_ns
,
802 getter_AddRefs(inputStream
));
803 if (NS_FAILED(result
)) {
808 result
= NS_ERROR_FAILURE
;
812 *aData
= inputStream
.forget().take();
813 *aLen
= sizeof(nsIInputStream
*);
819 // in the case of a file drop, multiple files are stashed within a
820 // single data object. In order to match mozilla's D&D apis, we
821 // just pull out the file at the requested index, pretending as
822 // if there really are multiple drag items.
823 HDROP dropFiles
= (HDROP
)GlobalLock(stm
.hGlobal
);
825 UINT numFiles
= ::DragQueryFileW(dropFiles
, 0xFFFFFFFF, nullptr, 0);
826 NS_ASSERTION(numFiles
> 0,
827 "File drop flavor, but no files...hmmmm");
828 NS_ASSERTION(aIndex
< numFiles
,
829 "Asked for a file index out of range of list");
832 ::DragQueryFileW(dropFiles
, aIndex
, nullptr, 0);
833 wchar_t* buffer
= reinterpret_cast<wchar_t*>(
834 moz_xmalloc((fileNameLen
+ 1) * sizeof(wchar_t)));
835 ::DragQueryFileW(dropFiles
, aIndex
, buffer
, fileNameLen
+ 1);
837 *aLen
= fileNameLen
* sizeof(char16_t
);
840 GlobalUnlock(stm
.hGlobal
);
845 if (fe
.cfFormat
== fileDescriptorFlavorA
||
846 fe
.cfFormat
== fileDescriptorFlavorW
) {
847 nsAutoString tempPath
;
849 LPFILEGROUPDESCRIPTOR fgdesc
=
850 static_cast<LPFILEGROUPDESCRIPTOR
>(GlobalLock(stm
.hGlobal
));
852 result
= GetTempFilePath(
853 nsDependentString((fgdesc
->fgd
)[aIndex
].cFileName
),
855 GlobalUnlock(stm
.hGlobal
);
857 if (NS_FAILED(result
)) {
860 result
= SaveStorageOrStream(aDataObject
, aIndex
, tempPath
);
861 if (NS_FAILED(result
)) {
864 wchar_t* buffer
= reinterpret_cast<wchar_t*>(
865 moz_xmalloc((tempPath
.Length() + 1) * sizeof(wchar_t)));
866 wcscpy(buffer
, tempPath
.get());
868 *aLen
= tempPath
.Length() * sizeof(wchar_t);
870 } else if (fe
.cfFormat
== fileFlavor
) {
872 "Mozilla doesn't yet understand how to read this type of "
875 // Get the data out of the global data handle. The size we
876 // return should not include the null because the other
877 // platforms don't use nulls, so just return the length we get
878 // back from strlen(), since we know CF_UNICODETEXT is null
879 // terminated. Recall that GetGlobalData() returns the size of
880 // the allocated buffer, not the size of the data (on 98, these
881 // are not the same) so we can't use that.
883 // NOTE: we are assuming that anything that falls into this
884 // default case is unicode. As we start to get more
885 // kinds of binary data, this may become an incorrect
886 // assumption. Stay tuned.
887 uint32_t allocLen
= 0;
888 if (NS_SUCCEEDED(GetGlobalData(stm
.hGlobal
, aData
, &allocLen
))) {
889 if (fe
.cfFormat
== GetHtmlClipboardFormat()) {
890 // CF_HTML is actually UTF8, not unicode, so disregard the
891 // assumption above. We have to check the header for the
892 // actual length, and we'll do that in FindPlatformHTML().
893 // For now, return the allocLen. This case is mostly to
894 // ensure we don't try to call strlen on the buffer.
896 } else if (fe
.cfFormat
== GetCustomClipboardFormat()) {
899 } else if (fe
.cfFormat
== preferredDropEffect
) {
900 // As per the MSDN doc entitled: "Shell Clipboard Formats"
901 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
903 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
905 allocLen
== sizeof(DWORD
),
906 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
909 *aLen
= NS_strlen(reinterpret_cast<char16_t
*>(*aData
)) *
921 MOZ_CLIPBOARD_LOG("*********************** TYMED_GDI");
929 ReleaseStgMedium(&stm
);
935 //-------------------------------------------------------------------------
936 nsresult
nsClipboard::GetDataFromDataObject(IDataObject
* aDataObject
,
937 UINT anIndex
, nsIWidget
* aWindow
,
938 nsITransferable
* aTransferable
) {
939 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
941 // make sure we have a good transferable
942 if (!aTransferable
) {
943 return NS_ERROR_INVALID_ARG
;
946 nsresult res
= NS_ERROR_FAILURE
;
948 // get flavor list that includes all flavors that can be written (including
949 // ones obtained through conversion)
950 nsTArray
<nsCString
> flavors
;
951 res
= aTransferable
->FlavorsTransferableCanImport(flavors
);
952 if (NS_FAILED(res
)) {
953 return NS_ERROR_FAILURE
;
956 // Walk through flavors and see which flavor is on the clipboard them on the
958 for (uint32_t i
= 0; i
< flavors
.Length(); i
++) {
959 nsCString
& flavorStr
= flavors
[i
];
960 UINT format
= GetFormat(flavorStr
.get());
962 // Try to get the data using the desired flavor. This might fail, but all is
964 void* data
= nullptr;
965 uint32_t dataLen
= 0;
966 bool dataFound
= false;
967 if (nullptr != aDataObject
) {
968 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject
, anIndex
, format
,
969 flavorStr
.get(), &data
,
973 } else if (nullptr != aWindow
) {
974 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow
, anIndex
, format
,
980 // This is our second chance to try to find some data, having not found it
981 // when directly asking for the flavor. Let's try digging around in other
982 // flavors to help satisfy our craving for data.
984 if (flavorStr
.EqualsLiteral(kTextMime
)) {
986 FindUnicodeFromPlainText(aDataObject
, anIndex
, &data
, &dataLen
);
987 } else if (flavorStr
.EqualsLiteral(kURLMime
)) {
988 // drags from other windows apps expose the native
989 // CFSTR_INETURL{A,W} flavor
990 dataFound
= FindURLFromNativeURL(aDataObject
, anIndex
, &data
, &dataLen
);
993 FindURLFromLocalFile(aDataObject
, anIndex
, &data
, &dataLen
);
996 } // if we try one last ditch effort to find our data
998 // Hopefully by this point we've found it and can go about our business
1000 nsCOMPtr
<nsISupports
> genericDataWrapper
;
1001 if (flavorStr
.EqualsLiteral(kFileMime
)) {
1002 // we have a file path in |data|. Create an nsLocalFile object.
1003 nsDependentString
filepath(reinterpret_cast<char16_t
*>(data
));
1004 nsCOMPtr
<nsIFile
> file
;
1005 if (NS_SUCCEEDED(NS_NewLocalFile(filepath
, getter_AddRefs(file
)))) {
1006 genericDataWrapper
= do_QueryInterface(file
);
1009 } else if (flavorStr
.EqualsLiteral(kNativeHTMLMime
)) {
1011 // the editor folks want CF_HTML exactly as it's on the clipboard, no
1012 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
1013 // a wrapper and hand it back to them.
1014 if (FindPlatformHTML(aDataObject
, anIndex
, &data
, &dummy
, &dataLen
)) {
1015 nsPrimitiveHelpers::CreatePrimitiveForData(
1016 flavorStr
, data
, dataLen
, getter_AddRefs(genericDataWrapper
));
1019 continue; // something wrong with this flavor, keep looking for other
1023 } else if (flavorStr
.EqualsLiteral(kHTMLMime
)) {
1024 uint32_t startOfData
= 0;
1025 // The JS folks want CF_HTML exactly as it is on the clipboard, but
1026 // minus the CF_HTML header index information.
1027 // It also needs to be converted to UTF16 and have linebreaks changed.
1028 if (FindPlatformHTML(aDataObject
, anIndex
, &data
, &startOfData
,
1030 dataLen
-= startOfData
;
1031 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
1032 static_cast<char*>(data
) + startOfData
, &dataLen
,
1033 getter_AddRefs(genericDataWrapper
));
1036 continue; // something wrong with this flavor, keep looking for other
1040 } else if (flavorStr
.EqualsLiteral(kJPEGImageMime
) ||
1041 flavorStr
.EqualsLiteral(kJPGImageMime
) ||
1042 flavorStr
.EqualsLiteral(kPNGImageMime
)) {
1043 nsIInputStream
* imageStream
= reinterpret_cast<nsIInputStream
*>(data
);
1044 genericDataWrapper
= do_QueryInterface(imageStream
);
1045 NS_IF_RELEASE(imageStream
);
1047 // Treat custom types as a string of bytes.
1048 if (!flavorStr
.EqualsLiteral(kCustomTypesMime
)) {
1049 bool isRTF
= flavorStr
.EqualsLiteral(kRTFMime
);
1050 // we probably have some form of text. The DOM only wants LF, so
1051 // convert from Win32 line endings to DOM line endings.
1052 int32_t signedLen
= static_cast<int32_t>(dataLen
);
1053 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(isRTF
, &data
,
1055 dataLen
= signedLen
;
1058 // RTF on Windows is known to sometimes deliver an extra null byte.
1059 if (dataLen
> 0 && static_cast<char*>(data
)[dataLen
- 1] == '\0') {
1065 nsPrimitiveHelpers::CreatePrimitiveForData(
1066 flavorStr
, data
, dataLen
, getter_AddRefs(genericDataWrapper
));
1070 NS_ASSERTION(genericDataWrapper
,
1071 "About to put null data into the transferable");
1072 aTransferable
->SetTransferData(flavorStr
.get(), genericDataWrapper
);
1075 // we found one, get out of the loop
1086 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
1089 bool nsClipboard ::FindPlatformHTML(IDataObject
* inDataObject
, UINT inIndex
,
1090 void** outData
, uint32_t* outStartOfData
,
1091 uint32_t* outDataLen
) {
1092 // Reference: MSDN doc entitled "HTML Clipboard Format"
1093 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
1094 // CF_HTML is UTF8, not unicode. We also can't rely on it being
1095 // null-terminated so we have to check the CF_HTML header for the correct
1096 // length. The length we return is the bytecount from the beginning of the
1097 // selected data to the end of the selected data, without the null
1098 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
1100 if (!outData
|| !*outData
) {
1104 char version
[8] = {0};
1105 int32_t startOfData
= 0;
1106 int32_t endOfData
= 0;
1108 sscanf((char*)*outData
, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version
,
1109 &startOfData
, &endOfData
);
1111 if (numFound
!= 3 || startOfData
< -1 || endOfData
< -1) {
1115 // Fixup the start and end markers if they have no context (set to -1)
1116 if (startOfData
== -1) {
1119 if (endOfData
== -1) {
1120 endOfData
= *outDataLen
;
1123 // Make sure we were passed sane values within our buffer size.
1124 // (Note that we've handled all cases of negative endOfData above, so we can
1125 // safely cast it to be unsigned here.)
1126 if (!endOfData
|| startOfData
>= endOfData
||
1127 static_cast<uint32_t>(endOfData
) > *outDataLen
) {
1131 // We want to return the buffer not offset by startOfData because it will be
1132 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
1133 // in CF_HTML format.
1135 // We return the byte offset from the start of the data buffer to where the
1136 // HTML data starts. The caller might want to extract the HTML only.
1137 *outStartOfData
= startOfData
;
1138 *outDataLen
= endOfData
;
1143 // FindUnicodeFromPlainText
1145 // Looks for CF_TEXT on the clipboard and converts it into an UTF-16 string
1146 // if present. Returns this string in outData, and its length in outDataLen.
1147 // XXXndeakin Windows converts between CF_UNICODE and CF_TEXT automatically
1148 // so it doesn't seem like this is actually needed.
1150 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject
* inDataObject
,
1151 UINT inIndex
, void** outData
,
1152 uint32_t* outDataLen
) {
1153 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
1155 // We are looking for text/plain and we failed to find it on the clipboard
1156 // first, so try again with CF_TEXT. If that is present, convert it to
1158 nsresult rv
= GetNativeDataOffClipboard(inDataObject
, inIndex
, CF_TEXT
,
1159 nullptr, outData
, outDataLen
);
1160 if (NS_FAILED(rv
) || !*outData
) {
1164 const char* castedText
= static_cast<char*>(*outData
);
1166 rv
= NS_CopyNativeToUnicode(nsDependentCSubstring(castedText
, *outDataLen
),
1168 if (NS_FAILED(rv
)) {
1172 // out with the old, in with the new
1174 *outData
= ToNewUnicode(tmp
);
1175 *outDataLen
= tmp
.Length() * sizeof(char16_t
);
1179 } // FindUnicodeFromPlainText
1182 // FindURLFromLocalFile
1184 // we are looking for a URL and couldn't find it, try again with looking for
1185 // a local file. If we have one, it may either be a normal file or an internet
1186 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
1187 // in the local file case).
1189 bool nsClipboard ::FindURLFromLocalFile(IDataObject
* inDataObject
, UINT inIndex
,
1190 void** outData
, uint32_t* outDataLen
) {
1191 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
1193 bool dataFound
= false;
1195 nsresult loadResult
=
1196 GetNativeDataOffClipboard(inDataObject
, inIndex
, GetFormat(kFileMime
),
1197 nullptr, outData
, outDataLen
);
1198 if (NS_SUCCEEDED(loadResult
) && *outData
) {
1199 // we have a file path in |data|. Is it an internet shortcut or a normal
1201 const nsDependentString
filepath(static_cast<char16_t
*>(*outData
));
1202 nsCOMPtr
<nsIFile
> file
;
1203 nsresult rv
= NS_NewLocalFile(filepath
, getter_AddRefs(file
));
1204 if (NS_FAILED(rv
)) {
1209 if (IsInternetShortcut(filepath
)) {
1212 ResolveShortcut(file
, url
);
1213 if (!url
.IsEmpty()) {
1214 // convert it to unicode and pass it out
1215 NS_ConvertUTF8toUTF16
urlString(url
);
1216 // the internal mozilla URL format, text/x-moz-url, contains
1217 // URL\ntitle. We can guess the title from the file's name.
1219 file
->GetLeafName(title
);
1220 // We rely on IsInternetShortcut check that file has a .url extension.
1221 title
.SetLength(title
.Length() - 4);
1222 if (title
.IsEmpty()) {
1225 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ title
);
1227 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1232 // we have a normal file, use some Necko objects to get our file path
1233 nsAutoCString urlSpec
;
1234 NS_GetURLSpecFromFile(file
, urlSpec
);
1236 // convert it to unicode and pass it out
1238 *outData
= UTF8ToNewUnicode(urlSpec
);
1240 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1242 } // else regular file
1246 } // FindURLFromLocalFile
1249 // FindURLFromNativeURL
1251 // we are looking for a URL and couldn't find it using our internal
1252 // URL flavor, so look for it using the native URL flavor,
1253 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
1255 bool nsClipboard ::FindURLFromNativeURL(IDataObject
* inDataObject
, UINT inIndex
,
1256 void** outData
, uint32_t* outDataLen
) {
1257 MOZ_CLIPBOARD_LOG("%s", __FUNCTION__
);
1259 bool dataFound
= false;
1261 void* tempOutData
= nullptr;
1262 uint32_t tempDataLen
= 0;
1264 nsresult loadResult
= GetNativeDataOffClipboard(
1265 inDataObject
, inIndex
, ::RegisterClipboardFormat(CFSTR_INETURLW
), nullptr,
1266 &tempOutData
, &tempDataLen
);
1267 if (NS_SUCCEEDED(loadResult
) && tempOutData
) {
1268 nsDependentString
urlString(static_cast<char16_t
*>(tempOutData
));
1269 // the internal mozilla URL format, text/x-moz-url, contains
1270 // URL\ntitle. Since we don't actually have a title here,
1271 // just repeat the URL to fake it.
1272 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ urlString
);
1274 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1278 loadResult
= GetNativeDataOffClipboard(
1279 inDataObject
, inIndex
, ::RegisterClipboardFormat(CFSTR_INETURLA
),
1280 nullptr, &tempOutData
, &tempDataLen
);
1281 if (NS_SUCCEEDED(loadResult
) && tempOutData
) {
1282 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
1283 // CF_TEXT which is by definition ANSI encoded.
1284 nsCString urlUnescapedA
;
1286 NS_UnescapeURL(static_cast<char*>(tempOutData
), tempDataLen
,
1287 esc_OnlyNonASCII
| esc_SkipControl
, urlUnescapedA
);
1291 NS_CopyNativeToUnicode(urlUnescapedA
, urlString
);
1293 NS_CopyNativeToUnicode(
1294 nsDependentCString(static_cast<char*>(tempOutData
), tempDataLen
),
1298 // the internal mozilla URL format, text/x-moz-url, contains
1299 // URL\ntitle. Since we don't actually have a title here,
1300 // just repeat the URL to fake it.
1301 *outData
= ToNewUnicode(urlString
+ u
"\n"_ns
+ urlString
);
1303 NS_strlen(static_cast<char16_t
*>(*outData
)) * sizeof(char16_t
);
1310 } // FindURLFromNativeURL
1312 // Other apps can block access to the clipboard. This repeatedly
1313 // calls `::OleGetClipboard` for a fixed number of times and should be called
1314 // instead of `::OleGetClipboard`.
1315 static HRESULT
RepeatedlyTryOleGetClipboard(IDataObject
** aDataObj
) {
1316 return RepeatedlyTry(::OleGetClipboard
, LogOleGetClipboardResult
, aDataObj
);
1322 void nsClipboard ::ResolveShortcut(nsIFile
* aFile
, nsACString
& outURL
) {
1323 nsCOMPtr
<nsIFileProtocolHandler
> fph
;
1324 nsresult rv
= NS_GetFileProtocolHandler(getter_AddRefs(fph
));
1325 if (NS_FAILED(rv
)) {
1329 nsCOMPtr
<nsIURI
> uri
;
1330 rv
= fph
->ReadURLFile(aFile
, getter_AddRefs(uri
));
1331 if (NS_FAILED(rv
)) {
1335 uri
->GetSpec(outURL
);
1336 } // ResolveShortcut
1339 // IsInternetShortcut
1341 // A file is an Internet Shortcut if it ends with .URL
1343 bool nsClipboard ::IsInternetShortcut(const nsAString
& inFileName
) {
1344 return StringEndsWith(inFileName
, u
".url"_ns
,
1345 nsCaseInsensitiveStringComparator
);
1346 } // IsInternetShortcut
1348 //-------------------------------------------------------------------------
1350 nsClipboard::GetNativeClipboardData(nsITransferable
* aTransferable
,
1351 ClipboardType aWhichClipboard
) {
1352 MOZ_DIAGNOSTIC_ASSERT(aTransferable
);
1353 MOZ_DIAGNOSTIC_ASSERT(
1354 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1356 MOZ_CLIPBOARD_LOG("%s aWhichClipboard=%i", __FUNCTION__
, aWhichClipboard
);
1359 // This makes sure we can use the OLE functionality for the clipboard
1360 IDataObject
* dataObj
;
1361 if (S_OK
== RepeatedlyTryOleGetClipboard(&dataObj
)) {
1362 // Use OLE IDataObject for clipboard operations
1363 MOZ_CLIPBOARD_LOG(" use OLE IDataObject:");
1364 if (MOZ_CLIPBOARD_LOG_ENABLED()) {
1365 IEnumFORMATETC
* pEnum
= nullptr;
1366 if (S_OK
== dataObj
->EnumFormatEtc(DATADIR_GET
, &pEnum
)) {
1368 while (S_OK
== pEnum
->Next(1, &fEtc
, nullptr)) {
1369 nsAutoString format
;
1370 mozilla::widget::WinUtils::GetClipboardFormatAsString(fEtc
.cfFormat
,
1372 MOZ_CLIPBOARD_LOG(" FORMAT %s",
1373 NS_ConvertUTF16toUTF8(format
).get());
1379 res
= GetDataFromDataObject(dataObj
, 0, nullptr, aTransferable
);
1382 // do it the old manual way
1383 res
= GetDataFromDataObject(nullptr, 0, mWindow
, aTransferable
);
1388 nsresult
nsClipboard::EmptyNativeClipboardData(ClipboardType aWhichClipboard
) {
1389 MOZ_DIAGNOSTIC_ASSERT(
1390 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1391 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1392 // clipboard to scan it. If we i) empty and then ii) set data, then the
1393 // 'set data' can sometimes fail with access denied becacuse another program
1394 // has the clipboard open. So to avoid this race condition for OpenClipboard
1395 // we do not empty the clipboard when we're setting it.
1396 RepeatedlyTryOleSetClipboard(nullptr);
1400 mozilla::Result
<int32_t, nsresult
>
1401 nsClipboard::GetNativeClipboardSequenceNumber(ClipboardType aWhichClipboard
) {
1402 MOZ_DIAGNOSTIC_ASSERT(kGlobalClipboard
== aWhichClipboard
);
1403 return (int32_t)::GetClipboardSequenceNumber();
1406 //-------------------------------------------------------------------------
1407 mozilla::Result
<bool, nsresult
>
1408 nsClipboard::HasNativeClipboardDataMatchingFlavors(
1409 const nsTArray
<nsCString
>& aFlavorList
, ClipboardType aWhichClipboard
) {
1410 MOZ_DIAGNOSTIC_ASSERT(
1411 nsIClipboard::IsClipboardTypeSupported(aWhichClipboard
));
1412 for (const auto& flavor
: aFlavorList
) {
1413 UINT format
= GetFormat(flavor
.get());
1414 if (IsClipboardFormatAvailable(format
)) {
1421 //-------------------------------------------------------------------------
1422 nsresult
nsClipboard::GetTempFilePath(const nsAString
& aFileName
,
1423 nsAString
& aFilePath
) {
1424 nsresult result
= NS_OK
;
1426 nsCOMPtr
<nsIFile
> tmpFile
;
1428 GetSpecialSystemDirectory(OS_TemporaryDirectory
, getter_AddRefs(tmpFile
));
1429 NS_ENSURE_SUCCESS(result
, result
);
1431 result
= tmpFile
->Append(aFileName
);
1432 NS_ENSURE_SUCCESS(result
, result
);
1434 result
= tmpFile
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, 0660);
1435 NS_ENSURE_SUCCESS(result
, result
);
1436 result
= tmpFile
->GetPath(aFilePath
);
1441 //-------------------------------------------------------------------------
1442 nsresult
nsClipboard::SaveStorageOrStream(IDataObject
* aDataObject
, UINT aIndex
,
1443 const nsAString
& aFileName
) {
1444 NS_ENSURE_ARG_POINTER(aDataObject
);
1447 SET_FORMATETC(fe
, RegisterClipboardFormat(CFSTR_FILECONTENTS
), 0,
1448 DVASPECT_CONTENT
, aIndex
, TYMED_ISTORAGE
| TYMED_ISTREAM
);
1450 STGMEDIUM stm
= {0};
1451 HRESULT hres
= aDataObject
->GetData(&fe
, &stm
);
1453 return NS_ERROR_FAILURE
;
1456 auto releaseMediumGuard
=
1457 mozilla::MakeScopeExit([&] { ReleaseStgMedium(&stm
); });
1459 // We do this check because, even though we *asked* for IStorage or IStream,
1460 // it seems that IDataObject providers can just hand us back whatever they
1461 // feel like. See Bug 1824644 for a fun example of that!
1462 if (stm
.tymed
!= TYMED_ISTORAGE
&& stm
.tymed
!= TYMED_ISTREAM
) {
1463 return NS_ERROR_FAILURE
;
1466 if (stm
.tymed
== TYMED_ISTORAGE
) {
1467 // should never happen -- but theoretically possible, given an ill-behaved
1469 if (stm
.pstg
== nullptr) {
1470 return NS_ERROR_FAILURE
;
1473 RefPtr
<IStorage
> file
;
1474 hres
= StgCreateStorageEx(
1475 aFileName
.Data(), STGM_CREATE
| STGM_READWRITE
| STGM_SHARE_EXCLUSIVE
,
1476 STGFMT_STORAGE
, 0, NULL
, NULL
, IID_IStorage
, getter_AddRefs(file
));
1478 return NS_ERROR_FAILURE
;
1481 hres
= stm
.pstg
->CopyTo(0, NULL
, NULL
, file
);
1483 return NS_ERROR_FAILURE
;
1486 file
->Commit(STGC_DEFAULT
);
1491 MOZ_ASSERT(stm
.tymed
== TYMED_ISTREAM
);
1492 // should never happen -- but possible given an ill-behaved data-source, and
1493 // has been seen in the wild (bug 1895681)
1494 if (stm
.pstm
== nullptr) {
1495 return NS_ERROR_FAILURE
;
1498 HANDLE handle
= CreateFile(aFileName
.Data(), GENERIC_WRITE
, FILE_SHARE_READ
,
1499 NULL
, CREATE_ALWAYS
, FILE_ATTRIBUTE_NORMAL
, NULL
);
1500 if (handle
== INVALID_HANDLE_VALUE
) {
1501 return NS_ERROR_FAILURE
;
1504 auto fileCloseGuard
= mozilla::MakeScopeExit([&] { CloseHandle(handle
); });
1506 const ULONG bufferSize
= 4096;
1507 char buffer
[bufferSize
] = {0};
1508 ULONG bytesRead
= 0;
1509 DWORD bytesWritten
= 0;
1511 HRESULT result
= stm
.pstm
->Read(buffer
, bufferSize
, &bytesRead
);
1512 if (FAILED(result
)) {
1513 return NS_ERROR_FAILURE
;
1515 if (bytesRead
== 0) {
1518 if (!WriteFile(handle
, buffer
, static_cast<DWORD
>(bytesRead
), &bytesWritten
,
1520 return NS_ERROR_FAILURE
;