Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / windows / nsClipboard.cpp
blob7c771f1bb4d8454b0b1e14a704da4773ba510fa2
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"
8 #include <shlobj.h>
9 #include <intshcut.h>
11 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
12 #include <shellapi.h>
14 #include <functional>
15 #include <thread>
16 #include <chrono>
18 #ifdef ACCESSIBILITY
19 # include "mozilla/a11y/Compatibility.h"
20 #endif
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"
29 #include "nsCOMPtr.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsDataObj.h"
32 #include "nsString.h"
33 #include "nsNativeCharsetUtils.h"
34 #include "nsIInputStream.h"
35 #include "nsITransferable.h"
36 #include "nsXPCOM.h"
37 #include "nsReadableUtils.h"
38 #include "nsUnicharUtils.h"
39 #include "nsPrimitiveHelpers.h"
40 #include "nsIWidget.h"
41 #include "nsWidgetsCID.h"
42 #include "nsCRT.h"
43 #include "nsNetUtil.h"
44 #include "nsIFileProtocolHandler.h"
45 #include "nsEscape.h"
46 #include "nsIObserverService.h"
47 #include "nsMimeTypes.h"
48 #include "imgITools.h"
49 #include "imgIContainer.h"
50 #include "WinUtils.h"
52 /* static */
53 UINT nsClipboard::GetClipboardFileDescriptorFormatA() {
54 static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORA);
55 MOZ_ASSERT(format);
56 return format;
59 /* static */
60 UINT nsClipboard::GetClipboardFileDescriptorFormatW() {
61 static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW);
62 MOZ_ASSERT(format);
63 return format;
66 /* static */
67 UINT nsClipboard::GetHtmlClipboardFormat() {
68 static UINT format = ::RegisterClipboardFormatW(L"HTML Format");
69 return format;
72 /* static */
73 UINT nsClipboard::GetCustomClipboardFormat() {
74 static UINT format =
75 ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
76 return format;
79 //-------------------------------------------------------------------------
81 // nsClipboard constructor
83 //-------------------------------------------------------------------------
84 nsClipboard::nsClipboard()
85 : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
86 false /* supportsSelectionClipboard */,
87 false /* supportsFindClipboard */,
88 false /* supportsSelectionCache */)) {
89 mWindow = nullptr;
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,
97 false);
101 //-------------------------------------------------------------------------
102 // nsClipboard destructor
103 //-------------------------------------------------------------------------
104 nsClipboard::~nsClipboard() {}
106 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
108 NS_IMETHODIMP
109 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
110 const char16_t* aData) {
111 // This will be called on shutdown.
112 ::OleFlushClipboard();
113 ::CloseClipboard();
115 return NS_OK;
118 //-------------------------------------------------------------------------
119 UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
120 UINT format;
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) {
129 format = CF_DIBV5;
130 } else if (strcmp(aMimeStr, kFileMime) == 0 ||
131 strcmp(aMimeStr, kFilePromiseMime) == 0) {
132 format = CF_HDROP;
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();
138 } else {
139 format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
142 return format;
145 //-------------------------------------------------------------------------
146 // static
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
159 nsresult res =
160 SetupNativeDataObject(aTransferable, dataObj, aMightNeedToFlush);
161 if (NS_SUCCEEDED(res)) {
162 dataObj.forget(aDataObj);
164 return res;
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);
174 *pdw = value;
175 ::GlobalUnlock(hGlobalMemory);
177 STGMEDIUM stg;
178 stg.tymed = TYMED_HGLOBAL;
179 stg.pUnkForRelease = nullptr;
180 stg.hGlobal = hGlobalMemory;
182 FORMATETC fe;
183 SET_FORMATETC(fe, ::RegisterClipboardFormat(aClipboardFormat), 0,
184 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
185 aObj->SetData(&fe, &stg, TRUE);
187 return NS_OK;
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
220 // below.
221 UINT format = GetFormat(flavorStr.get(), false);
223 // Now tell the native IDataObject about both our mime type and
224 // the native data format
225 FORMATETC fe;
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.
235 FORMATETC textFE;
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().
244 FORMATETC htmlFE;
245 SET_FORMATETC(htmlFE, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
246 TYMED_HGLOBAL);
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
276 FORMATETC imageFE;
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);
285 // Add DIBv5
286 SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
287 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
289 // Add DIBv3
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()) {
320 nsresult rv =
321 StoreValueInDataObject(dObj, TEXT("CanUploadToCloudClipboard"), 0);
322 NS_ENSURE_SUCCESS(rv, rv);
323 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);
332 return NS_OK;
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) {
339 switch (aHres) {
340 case E_INVALIDARG:
341 aResult = "E_INVALIDARG";
342 break;
343 case E_UNEXPECTED:
344 aResult = "E_UNEXPECTED";
345 break;
346 case E_OUTOFMEMORY:
347 aResult = "E_OUTOFMEMORY";
348 break;
349 case DV_E_LINDEX:
350 aResult = "DV_E_LINDEX";
351 break;
352 case DV_E_FORMATETC:
353 aResult = "DV_E_FORMATETC";
354 break;
355 case DV_E_TYMED:
356 aResult = "DV_E_TYMED";
357 break;
358 case DV_E_DVASPECT:
359 aResult = "DV_E_DVASPECT";
360 break;
361 case OLE_E_NOTRUNNING:
362 aResult = "OLE_E_NOTRUNNING";
363 break;
364 case STG_E_MEDIUMFULL:
365 aResult = "STG_E_MEDIUMFULL";
366 break;
367 case DV_E_CLIPFORMAT:
368 aResult = "DV_E_CLIPFORMAT";
369 break;
370 case S_OK:
371 aResult = "S_OK";
372 break;
373 default:
374 // Explicit template instantiaton, because otherwise the call is
375 // ambiguous.
376 constexpr int kRadix = 16;
377 aResult = IntToCString<int32_t>(aHres, kRadix);
378 break;
382 // See
383 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
384 static void OleGetClipboardResultToString(const HRESULT aHres,
385 nsACString& aResult) {
386 switch (aHres) {
387 case S_OK:
388 aResult = "S_OK";
389 break;
390 case CLIPBRD_E_CANT_OPEN:
391 aResult = "CLIPBRD_E_CANT_OPEN";
392 break;
393 case CLIPBRD_E_CANT_CLOSE:
394 aResult = "CLIPBRD_E_CANT_CLOSE";
395 break;
396 default:
397 // Explicit template instantiaton, because otherwise the call is
398 // ambiguous.
399 constexpr int kRadix = 16;
400 aResult = IntToCString<int32_t>(aHres, kRadix);
401 break;
405 static void MaybeLogClipboardCurrentOwner(
406 const HRESULT aHres, const mozilla::StaticString& aMethodName) {
407 if (!MOZ_CLIPBOARD_LOG_ENABLED()) {
408 return;
411 if (aHres != CLIPBRD_E_CANT_OPEN) {
412 return;
414 auto hwnd = ::GetOpenClipboardWindow();
415 if (!hwnd) {
416 MOZ_CLIPBOARD_LOG(
417 "IDataObject::%s | Clipboard already opened by unknown process",
418 aMethodName.get());
419 return;
421 DWORD procId;
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));
427 MOZ_CLIPBOARD_LOG(
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());
434 // See
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");
445 // See
446 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
447 static void OleSetClipboardResultToString(HRESULT aHres, nsACString& aResult) {
448 switch (aHres) {
449 case S_OK:
450 aResult = "S_OK";
451 break;
452 case CLIPBRD_E_CANT_OPEN:
453 aResult = "CLIPBRD_E_CANT_OPEN";
454 break;
455 case CLIPBRD_E_CANT_EMPTY:
456 aResult = "CLIPBRD_E_CANT_EMPTY";
457 break;
458 case CLIPBRD_E_CANT_CLOSE:
459 aResult = "CLIPBRD_E_CANT_CLOSE";
460 break;
461 case CLIPBRD_E_CANT_SET:
462 aResult = "CLIPBRD_E_CANT_SET";
463 break;
464 default:
465 // Explicit template instantiaton, because otherwise the call is
466 // ambiguous.
467 constexpr int kRadix = 16;
468 aResult = IntToCString<int32_t>(aHres, kRadix);
469 break;
473 // See
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,
486 Args... aArgs) {
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;
493 HRESULT hres;
494 for (int i = 0; i < kNumberOfTries; ++i) {
495 hres = aFunction(aArgs...);
496 aLogFunction(hres);
498 if (hres == S_OK) {
499 break;
502 // TODO: This was formerly std::sleep_for, which wasn't actually sleeping
503 // in tests (bug 1927664).
504 ::SleepEx(kDelayInMs, TRUE);
507 return hres;
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;
531 #ifdef ACCESSIBILITY
532 mozilla::a11y::Compatibility::SuppressA11yForClipboardCopy();
533 #endif
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()) {
544 case 0:
545 return false;
546 case 1:
547 return true;
548 default:
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();
559 }();
560 if (doFlush) {
561 RepeatedlyTry(::OleFlushClipboard, [](HRESULT) {});
563 } else {
564 // Clear the native clipboard
565 RepeatedlyTryOleSetClipboard(nullptr);
568 return NS_OK;
571 //-------------------------------------------------------------------------
572 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
573 uint32_t* aLen) {
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()));
591 if (data) {
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';
597 GlobalUnlock(aHGBL);
598 *aData = data;
599 *aLen = size;
601 result = NS_OK;
603 } else {
604 // We really shouldn't ever get here
605 // but just in case
606 *aData = nullptr;
607 *aLen = 0;
608 LPVOID lpMsgBuf;
610 FormatMessageW(
611 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
612 GetLastError(),
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);
620 // Free the buffer.
621 LocalFree(lpMsgBuf);
624 return result;
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__);
633 HGLOBAL hglb;
634 nsresult result = NS_ERROR_FAILURE;
636 HWND nativeWin = nullptr;
637 if (::OpenClipboard(nativeWin)) {
638 hglb = ::GetClipboardData(aFormat);
639 result = GetGlobalData(hglb, aData, aLen);
640 ::CloseClipboard();
642 return result;
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(),
653 hresString.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
660 // `GetData`. See
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
664 // returned.
665 static HRESULT RepeatedlyTryGetData(IDataObject& aDataObject, LPFORMATETC pFE,
666 LPSTGMEDIUM pSTM) {
667 return RepeatedlyTry(
668 [&aDataObject, &pFE, &pSTM]() { return aDataObject.GetData(pFE, pSTM); },
669 [](HRESULT hres) { LogIDataObjectMethodResult(hres, "GetData"); });
672 //-------------------------------------------------------------------------
673 // static
674 HRESULT nsClipboard::FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
675 LPFORMATETC pFE, LPSTGMEDIUM pSTM,
676 DWORD aTymed) {
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
680 // memory
681 HRESULT hres = S_FALSE;
682 hres = aDataObject->QueryGetData(pFE);
683 LogIDataObjectMethodResult(hres, "QueryGetData");
684 if (S_OK == hres) {
685 hres = RepeatedlyTryGetData(*aDataObject, pFE, pSTM);
687 return hres;
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;
701 *aData = nullptr;
702 *aLen = 0;
704 if (!aDataObject) {
705 return result;
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
713 // and IStream
714 FORMATETC fe;
715 STGMEDIUM stm;
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);
724 if (FAILED(hres)) {
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)
733 if (S_OK == hres) {
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);
743 switch (stm.tymed) {
744 case TYMED_HGLOBAL: {
745 switch (fe.cfFormat) {
746 case CF_TEXT: {
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));
757 result = NS_OK;
759 } break;
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;
772 result = NS_OK;
774 } break;
776 case CF_DIBV5:
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)) {
790 break;
793 nsAutoCString mimeType;
794 if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
795 mimeType.Assign(IMAGE_JPEG);
796 } else {
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)) {
804 break;
807 if (!inputStream) {
808 result = NS_ERROR_FAILURE;
809 break;
812 *aData = inputStream.forget().take();
813 *aLen = sizeof(nsIInputStream*);
816 break;
818 case CF_HDROP: {
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");
830 if (numFiles > 0) {
831 UINT fileNameLen =
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);
836 *aData = buffer;
837 *aLen = fileNameLen * sizeof(char16_t);
838 result = NS_OK;
840 GlobalUnlock(stm.hGlobal);
842 } break;
844 default: {
845 if (fe.cfFormat == fileDescriptorFlavorA ||
846 fe.cfFormat == fileDescriptorFlavorW) {
847 nsAutoString tempPath;
849 LPFILEGROUPDESCRIPTOR fgdesc =
850 static_cast<LPFILEGROUPDESCRIPTOR>(GlobalLock(stm.hGlobal));
851 if (fgdesc) {
852 result = GetTempFilePath(
853 nsDependentString((fgdesc->fgd)[aIndex].cFileName),
854 tempPath);
855 GlobalUnlock(stm.hGlobal);
857 if (NS_FAILED(result)) {
858 break;
860 result = SaveStorageOrStream(aDataObject, aIndex, tempPath);
861 if (NS_FAILED(result)) {
862 break;
864 wchar_t* buffer = reinterpret_cast<wchar_t*>(
865 moz_xmalloc((tempPath.Length() + 1) * sizeof(wchar_t)));
866 wcscpy(buffer, tempPath.get());
867 *aData = buffer;
868 *aLen = tempPath.Length() * sizeof(wchar_t);
869 result = NS_OK;
870 } else if (fe.cfFormat == fileFlavor) {
871 NS_WARNING(
872 "Mozilla doesn't yet understand how to read this type of "
873 "file flavor");
874 } else {
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.
895 *aLen = allocLen;
896 } else if (fe.cfFormat == GetCustomClipboardFormat()) {
897 // Binary data
898 *aLen = allocLen;
899 } else if (fe.cfFormat == preferredDropEffect) {
900 // As per the MSDN doc entitled: "Shell Clipboard Formats"
901 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
902 // Reference:
903 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
904 NS_ASSERTION(
905 allocLen == sizeof(DWORD),
906 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
907 *aLen = allocLen;
908 } else {
909 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
910 sizeof(char16_t);
912 result = NS_OK;
915 } break;
916 } // switch
917 } break;
919 case TYMED_GDI: {
920 #ifdef DEBUG
921 MOZ_CLIPBOARD_LOG("*********************** TYMED_GDI");
922 #endif
923 } break;
925 default:
926 break;
927 } // switch
929 ReleaseStgMedium(&stm);
932 return result;
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
957 // native clipboard,
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
963 // not lost.
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,
970 &dataLen))) {
971 dataFound = true;
973 } else if (nullptr != aWindow) {
974 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
975 &data, &dataLen))) {
976 dataFound = true;
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.
983 if (!dataFound) {
984 if (flavorStr.EqualsLiteral(kTextMime)) {
985 dataFound =
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);
991 if (!dataFound) {
992 dataFound =
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
999 if (dataFound) {
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);
1008 free(data);
1009 } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
1010 uint32_t dummy;
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));
1017 } else {
1018 free(data);
1019 continue; // something wrong with this flavor, keep looking for other
1020 // data
1022 free(data);
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,
1029 &dataLen)) {
1030 dataLen -= startOfData;
1031 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
1032 static_cast<char*>(data) + startOfData, &dataLen,
1033 getter_AddRefs(genericDataWrapper));
1034 } else {
1035 free(data);
1036 continue; // something wrong with this flavor, keep looking for other
1037 // data
1039 free(data);
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);
1046 } else {
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,
1054 &signedLen);
1055 dataLen = signedLen;
1057 if (isRTF) {
1058 // RTF on Windows is known to sometimes deliver an extra null byte.
1059 if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
1060 dataLen--;
1065 nsPrimitiveHelpers::CreatePrimitiveForData(
1066 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
1067 free(data);
1070 NS_ASSERTION(genericDataWrapper,
1071 "About to put null data into the transferable");
1072 aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
1073 res = NS_OK;
1075 // we found one, get out of the loop
1076 break;
1078 } // foreach flavor
1080 return res;
1084 // FindPlatformHTML
1086 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
1087 // as-is.
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) {
1101 return false;
1104 char version[8] = {0};
1105 int32_t startOfData = 0;
1106 int32_t endOfData = 0;
1107 int numFound =
1108 sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
1109 &startOfData, &endOfData);
1111 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
1112 return false;
1115 // Fixup the start and end markers if they have no context (set to -1)
1116 if (startOfData == -1) {
1117 startOfData = 0;
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) {
1128 return false;
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;
1139 return true;
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
1157 // unicode.
1158 nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, CF_TEXT,
1159 nullptr, outData, outDataLen);
1160 if (NS_FAILED(rv) || !*outData) {
1161 return false;
1164 const char* castedText = static_cast<char*>(*outData);
1165 nsAutoString tmp;
1166 rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
1167 tmp);
1168 if (NS_FAILED(rv)) {
1169 return false;
1172 // out with the old, in with the new
1173 free(*outData);
1174 *outData = ToNewUnicode(tmp);
1175 *outDataLen = tmp.Length() * sizeof(char16_t);
1177 return true;
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
1200 // file?
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)) {
1205 free(*outData);
1206 return dataFound;
1209 if (IsInternetShortcut(filepath)) {
1210 free(*outData);
1211 nsAutoCString url;
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.
1218 nsAutoString title;
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()) {
1223 title = urlString;
1225 *outData = ToNewUnicode(urlString + u"\n"_ns + title);
1226 *outDataLen =
1227 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1229 dataFound = true;
1231 } else {
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
1237 free(*outData);
1238 *outData = UTF8ToNewUnicode(urlSpec);
1239 *outDataLen =
1240 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1241 dataFound = true;
1242 } // else regular file
1245 return dataFound;
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);
1273 *outDataLen =
1274 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1275 free(tempOutData);
1276 dataFound = true;
1277 } else {
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;
1285 bool unescaped =
1286 NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
1287 esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
1289 nsString urlString;
1290 if (unescaped) {
1291 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
1292 } else {
1293 NS_CopyNativeToUnicode(
1294 nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
1295 urlString);
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);
1302 *outDataLen =
1303 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1304 free(tempOutData);
1305 dataFound = true;
1309 return dataFound;
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);
1320 // ResolveShortcut
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)) {
1326 return;
1329 nsCOMPtr<nsIURI> uri;
1330 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
1331 if (NS_FAILED(rv)) {
1332 return;
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 //-------------------------------------------------------------------------
1349 NS_IMETHODIMP
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);
1358 nsresult res;
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)) {
1367 FORMATETC fEtc;
1368 while (S_OK == pEnum->Next(1, &fEtc, nullptr)) {
1369 nsAutoString format;
1370 mozilla::widget::WinUtils::GetClipboardFormatAsString(fEtc.cfFormat,
1371 format);
1372 MOZ_CLIPBOARD_LOG(" FORMAT %s",
1373 NS_ConvertUTF16toUTF8(format).get());
1376 pEnum->Release();
1379 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1380 dataObj->Release();
1381 } else {
1382 // do it the old manual way
1383 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1385 return res;
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);
1397 return NS_OK;
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)) {
1415 return true;
1418 return false;
1421 //-------------------------------------------------------------------------
1422 nsresult nsClipboard::GetTempFilePath(const nsAString& aFileName,
1423 nsAString& aFilePath) {
1424 nsresult result = NS_OK;
1426 nsCOMPtr<nsIFile> tmpFile;
1427 result =
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);
1438 return result;
1441 //-------------------------------------------------------------------------
1442 nsresult nsClipboard::SaveStorageOrStream(IDataObject* aDataObject, UINT aIndex,
1443 const nsAString& aFileName) {
1444 NS_ENSURE_ARG_POINTER(aDataObject);
1446 FORMATETC fe = {0};
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);
1452 if (FAILED(hres)) {
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
1468 // data-source
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));
1477 if (FAILED(hres)) {
1478 return NS_ERROR_FAILURE;
1481 hres = stm.pstg->CopyTo(0, NULL, NULL, file);
1482 if (FAILED(hres)) {
1483 return NS_ERROR_FAILURE;
1486 file->Commit(STGC_DEFAULT);
1488 return NS_OK;
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;
1510 while (true) {
1511 HRESULT result = stm.pstm->Read(buffer, bufferSize, &bytesRead);
1512 if (FAILED(result)) {
1513 return NS_ERROR_FAILURE;
1515 if (bytesRead == 0) {
1516 break;
1518 if (!WriteFile(handle, buffer, static_cast<DWORD>(bytesRead), &bytesWritten,
1519 NULL)) {
1520 return NS_ERROR_FAILURE;
1523 return NS_OK;