Bug 1777519 [wpt PR 34660] - Avoid OSError from --kill-safari stat'ing random paths...
[gecko.git] / widget / windows / nsClipboard.cpp
blob305d368ea22baee7bdb22013ba30adfd35fb5c38
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/StaticPrefs_clipboard.h"
23 #include "nsArrayUtils.h"
24 #include "nsCOMPtr.h"
25 #include "nsComponentManagerUtils.h"
26 #include "nsDataObj.h"
27 #include "nsString.h"
28 #include "nsNativeCharsetUtils.h"
29 #include "nsIInputStream.h"
30 #include "nsITransferable.h"
31 #include "nsXPCOM.h"
32 #include "nsReadableUtils.h"
33 #include "nsUnicharUtils.h"
34 #include "nsPrimitiveHelpers.h"
35 #include "nsIWidget.h"
36 #include "nsWidgetsCID.h"
37 #include "nsCRT.h"
38 #include "nsNetUtil.h"
39 #include "nsIFileProtocolHandler.h"
40 #include "nsEscape.h"
41 #include "nsIObserverService.h"
42 #include "nsMimeTypes.h"
43 #include "imgITools.h"
45 using mozilla::LogLevel;
47 static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard");
49 /* static */
50 UINT nsClipboard::GetHtmlClipboardFormat() {
51 static UINT format = ::RegisterClipboardFormatW(L"HTML Format");
52 return format;
55 /* static */
56 UINT nsClipboard::GetCustomClipboardFormat() {
57 static UINT format =
58 ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
59 return format;
62 //-------------------------------------------------------------------------
64 // nsClipboard constructor
66 //-------------------------------------------------------------------------
67 nsClipboard::nsClipboard() : nsBaseClipboard() {
68 mWindow = nullptr;
70 // Register for a shutdown notification so that we can flush data
71 // to the OS clipboard.
72 nsCOMPtr<nsIObserverService> observerService =
73 do_GetService("@mozilla.org/observer-service;1");
74 if (observerService) {
75 observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
76 PR_FALSE);
80 //-------------------------------------------------------------------------
81 // nsClipboard destructor
82 //-------------------------------------------------------------------------
83 nsClipboard::~nsClipboard() {}
85 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
87 NS_IMETHODIMP
88 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
89 const char16_t* aData) {
90 // This will be called on shutdown.
91 ::OleFlushClipboard();
92 ::CloseClipboard();
94 return NS_OK;
97 //-------------------------------------------------------------------------
98 UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
99 UINT format;
101 if (strcmp(aMimeStr, kTextMime) == 0) {
102 format = CF_UNICODETEXT;
103 } else if (strcmp(aMimeStr, kRTFMime) == 0) {
104 format = ::RegisterClipboardFormat(L"Rich Text Format");
105 } else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
106 strcmp(aMimeStr, kJPGImageMime) == 0 ||
107 strcmp(aMimeStr, kPNGImageMime) == 0) {
108 format = CF_DIBV5;
109 } else if (strcmp(aMimeStr, kFileMime) == 0 ||
110 strcmp(aMimeStr, kFilePromiseMime) == 0) {
111 format = CF_HDROP;
112 } else if ((strcmp(aMimeStr, kNativeHTMLMime) == 0) ||
113 (aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)) {
114 format = GetHtmlClipboardFormat();
115 } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
116 format = GetCustomClipboardFormat();
117 } else {
118 format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
121 return format;
124 //-------------------------------------------------------------------------
125 // static
126 nsresult nsClipboard::CreateNativeDataObject(
127 nsITransferable* aTransferable, IDataObject** aDataObj, nsIURI* aUri,
128 MightNeedToFlush* aMightNeedToFlush) {
129 MOZ_ASSERT(aTransferable);
130 if (!aTransferable) {
131 return NS_ERROR_FAILURE;
134 // Create our native DataObject that implements the OLE IDataObject interface
135 RefPtr<nsDataObj> dataObj = new nsDataObj(aUri);
137 // Now set it up with all the right data flavors & enums
138 nsresult res =
139 SetupNativeDataObject(aTransferable, dataObj, aMightNeedToFlush);
140 if (NS_SUCCEEDED(res)) {
141 dataObj.forget(aDataObj);
143 return res;
146 static nsresult StoreValueInDataObject(nsDataObj* aObj,
147 LPCWSTR aClipboardFormat, DWORD value) {
148 HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
149 if (!hGlobalMemory) {
150 return NS_ERROR_OUT_OF_MEMORY;
152 DWORD* pdw = (DWORD*)::GlobalLock(hGlobalMemory);
153 *pdw = value;
154 ::GlobalUnlock(hGlobalMemory);
156 STGMEDIUM stg;
157 stg.tymed = TYMED_HGLOBAL;
158 stg.pUnkForRelease = nullptr;
159 stg.hGlobal = hGlobalMemory;
161 FORMATETC fe;
162 SET_FORMATETC(fe, ::RegisterClipboardFormat(aClipboardFormat), 0,
163 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
164 aObj->SetData(&fe, &stg, TRUE);
166 return NS_OK;
169 //-------------------------------------------------------------------------
170 nsresult nsClipboard::SetupNativeDataObject(
171 nsITransferable* aTransferable, IDataObject* aDataObj,
172 MightNeedToFlush* aMightNeedToFlush) {
173 MOZ_ASSERT(aTransferable);
174 MOZ_ASSERT(aDataObj);
175 if (!aTransferable || !aDataObj) {
176 return NS_ERROR_FAILURE;
179 auto* dObj = static_cast<nsDataObj*>(aDataObj);
180 if (aMightNeedToFlush) {
181 *aMightNeedToFlush = MightNeedToFlush::No;
184 // Now give the Transferable to the DataObject
185 // for getting the data out of it
186 dObj->SetTransferable(aTransferable);
188 // Get the transferable list of data flavors
189 nsTArray<nsCString> flavors;
190 aTransferable->FlavorsTransferableCanExport(flavors);
192 // Walk through flavors that contain data and register them
193 // into the DataObj as supported flavors
194 for (uint32_t i = 0; i < flavors.Length(); i++) {
195 nsCString& flavorStr = flavors[i];
197 // When putting data onto the clipboard, we want to maintain kHTMLMime
198 // ("text/html") and not map it to CF_HTML here since this will be done
199 // below.
200 UINT format = GetFormat(flavorStr.get(), false);
202 // Now tell the native IDataObject about both our mime type and
203 // the native data format
204 FORMATETC fe;
205 SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
206 dObj->AddDataFlavor(flavorStr.get(), &fe);
208 // Do various things internal to the implementation, like map one
209 // flavor to another or add additional flavors based on what's required
210 // for the win32 impl.
211 if (flavorStr.EqualsLiteral(kTextMime)) {
212 // if we find text/plain, also add CF_TEXT, but we can add it for
213 // text/plain as well.
214 FORMATETC textFE;
215 SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
216 dObj->AddDataFlavor(kTextMime, &textFE);
217 if (aMightNeedToFlush) {
218 *aMightNeedToFlush = MightNeedToFlush::Yes;
220 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
221 // if we find text/html, also advertise win32's html flavor (which we will
222 // convert on our own in nsDataObj::GetText().
223 FORMATETC htmlFE;
224 SET_FORMATETC(htmlFE, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
225 TYMED_HGLOBAL);
226 dObj->AddDataFlavor(kHTMLMime, &htmlFE);
227 } else if (flavorStr.EqualsLiteral(kURLMime)) {
228 // if we're a url, in addition to also being text, we need to register
229 // the "file" flavors so that the win32 shell knows to create an internet
230 // shortcut when it sees one of these beasts.
231 FORMATETC shortcutFE;
232 SET_FORMATETC(shortcutFE,
233 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
234 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
235 dObj->AddDataFlavor(kURLMime, &shortcutFE);
236 SET_FORMATETC(shortcutFE,
237 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
238 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
239 dObj->AddDataFlavor(kURLMime, &shortcutFE);
240 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
241 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
242 dObj->AddDataFlavor(kURLMime, &shortcutFE);
243 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
244 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
245 dObj->AddDataFlavor(kURLMime, &shortcutFE);
246 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
247 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
248 dObj->AddDataFlavor(kURLMime, &shortcutFE);
249 } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
250 flavorStr.EqualsLiteral(kJPEGImageMime) ||
251 flavorStr.EqualsLiteral(kJPGImageMime) ||
252 flavorStr.EqualsLiteral(kGIFImageMime) ||
253 flavorStr.EqualsLiteral(kNativeImageMime)) {
254 // if we're an image, register the native bitmap flavor
255 FORMATETC imageFE;
256 // Add DIBv5
257 SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
258 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
259 // Add DIBv3
260 SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
261 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
262 } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
263 // if we're a file promise flavor, also register the
264 // CFSTR_PREFERREDDROPEFFECT format. The data object
265 // returns a value of DROPEFFECTS_MOVE to the drop target
266 // when it asks for the value of this format. This causes
267 // the file to be moved from the temporary location instead
268 // of being copied. The right thing to do here is to call
269 // SetData() on the data object and set the value of this format
270 // to DROPEFFECTS_MOVE on this particular data object. But,
271 // since all the other clipboard formats follow the model of setting
272 // data on the data object only when the drop object calls GetData(),
273 // I am leaving this format's value hard coded in the data object.
274 // We can change this if other consumers of this format get added to this
275 // codebase and they need different values.
276 FORMATETC shortcutFE;
277 SET_FORMATETC(shortcutFE,
278 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
279 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
280 dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
284 if (!StaticPrefs::clipboard_copyPrivateDataToClipboardCloudOrHistory()) {
285 // Let Clipboard know that data is sensitive and must not be copied to
286 // the Cloud Clipboard, Clipboard History and similar.
287 // https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats
288 if (aTransferable->GetIsPrivateData()) {
289 nsresult rv =
290 StoreValueInDataObject(dObj, TEXT("CanUploadToCloudClipboard"), 0);
291 NS_ENSURE_SUCCESS(rv, rv);
292 rv =
293 StoreValueInDataObject(dObj, TEXT("CanIncludeInClipboardHistory"), 0);
294 NS_ENSURE_SUCCESS(rv, rv);
295 rv = StoreValueInDataObject(
296 dObj, TEXT("ExcludeClipboardContentFromMonitorProcessing"), 0);
297 NS_ENSURE_SUCCESS(rv, rv);
301 return NS_OK;
304 // See methods listed at
305 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
306 static void IDataObjectMethodResultToString(const HRESULT aHres,
307 nsACString& aResult) {
308 switch (aHres) {
309 case E_INVALIDARG:
310 aResult = "E_INVALIDARG";
311 break;
312 case E_UNEXPECTED:
313 aResult = "E_UNEXPECTED";
314 break;
315 case E_OUTOFMEMORY:
316 aResult = "E_OUTOFMEMORY";
317 break;
318 case DV_E_LINDEX:
319 aResult = "DV_E_LINDEX";
320 break;
321 case DV_E_FORMATETC:
322 aResult = "DV_E_FORMATETC";
323 break;
324 case DV_E_TYMED:
325 aResult = "DV_E_TYMED";
326 break;
327 case DV_E_DVASPECT:
328 aResult = "DV_E_DVASPECT";
329 break;
330 case OLE_E_NOTRUNNING:
331 aResult = "OLE_E_NOTRUNNING";
332 break;
333 case STG_E_MEDIUMFULL:
334 aResult = "STG_E_MEDIUMFULL";
335 break;
336 case DV_E_CLIPFORMAT:
337 aResult = "DV_E_CLIPFORMAT";
338 break;
339 case S_OK:
340 aResult = "S_OK";
341 break;
342 default:
343 // Explicit template instantiaton, because otherwise the call is
344 // ambiguous.
345 constexpr int kRadix = 16;
346 aResult = IntToCString<int32_t>(aHres, kRadix);
347 break;
351 // See
352 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
353 static void OleGetClipboardResultToString(const HRESULT aHres,
354 nsACString& aResult) {
355 switch (aHres) {
356 case S_OK:
357 aResult = "S_OK";
358 break;
359 case CLIPBRD_E_CANT_OPEN:
360 aResult = "CLIPBRD_E_CANT_OPEN";
361 break;
362 case CLIPBRD_E_CANT_CLOSE:
363 aResult = "CLIPBRD_E_CANT_CLOSE";
364 break;
365 default:
366 // Explicit template instantiaton, because otherwise the call is
367 // ambiguous.
368 constexpr int kRadix = 16;
369 aResult = IntToCString<int32_t>(aHres, kRadix);
370 break;
374 // See
375 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
376 static void LogOleGetClipboardResult(const HRESULT aHres) {
377 if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
378 nsAutoCString hresString;
379 OleGetClipboardResultToString(aHres, hresString);
380 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
381 ("OleGetClipboard result: %s", hresString.get()));
385 // See
386 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
387 static void OleSetClipboardResultToString(HRESULT aHres, nsACString& aResult) {
388 switch (aHres) {
389 case S_OK:
390 aResult = "S_OK";
391 break;
392 case CLIPBRD_E_CANT_OPEN:
393 aResult = "CLIPBRD_E_CANT_OPEN";
394 break;
395 case CLIPBRD_E_CANT_EMPTY:
396 aResult = "CLIPBRD_E_CANT_EMPTY";
397 break;
398 case CLIPBRD_E_CANT_CLOSE:
399 aResult = "CLIPBRD_E_CANT_CLOSE";
400 break;
401 case CLIPBRD_E_CANT_SET:
402 aResult = "CLIPBRD_E_CANT_SET";
403 break;
404 default:
405 // Explicit template instantiaton, because otherwise the call is
406 // ambiguous.
407 constexpr int kRadix = 16;
408 aResult = IntToCString<int32_t>(aHres, kRadix);
409 break;
413 // See
414 // <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
415 static void LogOleSetClipboardResult(const HRESULT aHres) {
416 if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
417 nsAutoCString hresString;
418 OleSetClipboardResultToString(aHres, hresString);
419 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
420 ("OleSetClipboard result: %s", hresString.get()));
424 template <typename Function, typename LogFunction, typename... Args>
425 static HRESULT RepeatedlyTry(Function aFunction, LogFunction aLogFunction,
426 Args... aArgs) {
427 // These are magic values based on local testing. They are chosen not higher
428 // to avoid jank (<https://developer.mozilla.org/en-US/docs/Glossary/Jank>).
429 // When changing them, be careful.
430 static constexpr int kNumberOfTries = 3;
431 static constexpr int kDelayInMs = 3;
433 HRESULT hres;
434 for (int i = 0; i < kNumberOfTries; ++i) {
435 hres = aFunction(aArgs...);
436 aLogFunction(hres);
438 if (hres == S_OK) {
439 break;
442 std::this_thread::sleep_for(std::chrono::milliseconds(kDelayInMs));
445 return hres;
448 // Other apps can block access to the clipboard. This repeatedly
449 // calls `::OleSetClipboard` for a fixed number of times and should be called
450 // instead of `::OleSetClipboard`.
451 static void RepeatedlyTryOleSetClipboard(IDataObject* aDataObj) {
452 RepeatedlyTry(::OleSetClipboard, LogOleSetClipboardResult, aDataObj);
455 //-------------------------------------------------------------------------
456 NS_IMETHODIMP nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
457 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
459 if (aWhichClipboard != kGlobalClipboard) {
460 return NS_ERROR_FAILURE;
463 // make sure we have a good transferable
464 if (!mTransferable) {
465 return NS_ERROR_FAILURE;
468 #ifdef ACCESSIBILITY
469 a11y::Compatibility::SuppressA11yForClipboardCopy();
470 #endif
472 RefPtr<IDataObject> dataObj;
473 auto mightNeedToFlush = MightNeedToFlush::No;
474 if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable,
475 getter_AddRefs(dataObj), nullptr,
476 &mightNeedToFlush))) {
477 RepeatedlyTryOleSetClipboard(dataObj);
479 const bool doFlush = [&] {
480 switch (StaticPrefs::widget_windows_sync_clipboard_flush()) {
481 case 0:
482 return false;
483 case 1:
484 return true;
485 default:
486 // Bug 1774285: Windows Suggested Actions (introduced in Windows 11
487 // 22H2) walks the entire a11y tree using UIA if something is placed
488 // on the clipboard using delayed rendering. (The OLE clipboard always
489 // uses delayed rendering.) This a11y tree walk causes an unacceptable
490 // hang, particularly when the a11y cache is disabled. We choose the
491 // lesser of the two performance/memory evils here and force immediate
492 // rendering.
493 return mightNeedToFlush == MightNeedToFlush::Yes &&
494 NeedsWindows11SuggestedActionsWorkaround();
496 }();
497 if (doFlush) {
498 RepeatedlyTry(::OleFlushClipboard, [](HRESULT) {});
500 } else {
501 // Clear the native clipboard
502 RepeatedlyTryOleSetClipboard(nullptr);
505 return NS_OK;
508 //-------------------------------------------------------------------------
509 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
510 uint32_t* aLen) {
511 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose, ("%s", __FUNCTION__));
513 // Allocate a new memory buffer and copy the data from global memory.
514 // Recall that win98 allocates to nearest DWORD boundary. As a safety
515 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
516 // and null them out to ensure that all of our NS_strlen calls will succeed.
517 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
518 // a full NUL char16_t when |*aLen| is odd.
519 nsresult result = NS_ERROR_FAILURE;
520 if (aHGBL != nullptr) {
521 LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
522 mozilla::CheckedInt<uint32_t> allocSize =
523 mozilla::CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
524 if (!allocSize.isValid()) {
525 return NS_ERROR_INVALID_ARG;
527 char* data = static_cast<char*>(malloc(allocSize.value()));
528 if (data) {
529 uint32_t size = allocSize.value() - 3;
530 memcpy(data, lpStr, size);
531 // null terminate for safety
532 data[size] = data[size + 1] = data[size + 2] = '\0';
534 GlobalUnlock(aHGBL);
535 *aData = data;
536 *aLen = size;
538 result = NS_OK;
540 } else {
541 // We really shouldn't ever get here
542 // but just in case
543 *aData = nullptr;
544 *aLen = 0;
545 LPVOID lpMsgBuf;
547 FormatMessageW(
548 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
549 GetLastError(),
550 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
551 (LPWSTR)&lpMsgBuf, 0, nullptr);
553 // Display the string.
554 MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
555 MB_OK | MB_ICONINFORMATION);
557 // Free the buffer.
558 LocalFree(lpMsgBuf);
561 return result;
564 //-------------------------------------------------------------------------
565 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
566 UINT /*aIndex*/, UINT aFormat,
567 void** aData, uint32_t* aLen) {
568 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
569 ("%s: overload taking nsIWidget*.", __FUNCTION__));
571 HGLOBAL hglb;
572 nsresult result = NS_ERROR_FAILURE;
574 HWND nativeWin = nullptr;
575 if (::OpenClipboard(nativeWin)) {
576 hglb = ::GetClipboardData(aFormat);
577 result = GetGlobalData(hglb, aData, aLen);
578 ::CloseClipboard();
580 return result;
583 // See methods listed at
584 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
585 static void LogIDataObjectMethodResult(const HRESULT aHres,
586 const nsCString& aMethodName) {
587 if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
588 nsAutoCString hresString;
589 IDataObjectMethodResultToString(aHres, hresString);
590 MOZ_LOG(
591 gWin32ClipboardLog, LogLevel::Debug,
592 ("IDataObject::%s result: %s", aMethodName.get(), hresString.get()));
596 // Other apps can block access to the clipboard. This repeatedly calls
597 // `GetData` for a fixed number of times and should be called instead of
598 // `GetData`. See
599 // <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-idataobject-getdata>.
600 // While Microsoft's documentation doesn't include `CLIPBRD_E_CANT_OPEN`
601 // explicitly, it allows it implicitly and in local experiments it was indeed
602 // returned.
603 static HRESULT RepeatedlyTryGetData(IDataObject& aDataObject, LPFORMATETC pFE,
604 LPSTGMEDIUM pSTM) {
605 return RepeatedlyTry(
606 [&aDataObject, &pFE, &pSTM]() { return aDataObject.GetData(pFE, pSTM); },
607 std::bind(LogIDataObjectMethodResult, std::placeholders::_1,
608 "GetData"_ns));
611 //-------------------------------------------------------------------------
612 // static
613 HRESULT nsClipboard::FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
614 LPFORMATETC pFE, LPSTGMEDIUM pSTM,
615 DWORD aTymed) {
616 SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
618 // Starting by querying for the data to see if we can get it as from global
619 // memory
620 HRESULT hres = S_FALSE;
621 hres = aDataObject->QueryGetData(pFE);
622 LogIDataObjectMethodResult(hres, "QueryGetData"_ns);
623 if (S_OK == hres) {
624 hres = RepeatedlyTryGetData(*aDataObject, pFE, pSTM);
626 return hres;
629 //-------------------------------------------------------------------------
630 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
631 // an image encoder (e.g. image/png).
632 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
633 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
634 UINT aIndex, UINT aFormat,
635 const char* aMIMEImageFormat,
636 void** aData, uint32_t* aLen) {
637 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
638 ("%s: overload taking IDataObject*.", __FUNCTION__));
640 nsresult result = NS_ERROR_FAILURE;
641 *aData = nullptr;
642 *aLen = 0;
644 if (!aDataObject) {
645 return result;
648 UINT format = aFormat;
649 HRESULT hres = S_FALSE;
651 // XXX at the moment we only support global memory transfers
652 // It is here where we will add support for native images
653 // and IStream
654 FORMATETC fe;
655 STGMEDIUM stm;
656 hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
658 // Currently this is only handling TYMED_HGLOBAL data
659 // For Text, Dibs, Files, and generic data (like HTML)
660 if (S_OK == hres) {
661 static CLIPFORMAT fileDescriptorFlavorA =
662 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
663 static CLIPFORMAT fileDescriptorFlavorW =
664 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
665 static CLIPFORMAT fileFlavor =
666 ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
667 static CLIPFORMAT preferredDropEffect =
668 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
670 switch (stm.tymed) {
671 case TYMED_HGLOBAL: {
672 switch (fe.cfFormat) {
673 case CF_TEXT: {
674 // Get the data out of the global data handle. The size we
675 // return should not include the null because the other
676 // platforms don't use nulls, so just return the length we get
677 // back from strlen(), since we know CF_TEXT is null
678 // terminated. Recall that GetGlobalData() returns the size of
679 // the allocated buffer, not the size of the data (on 98, these
680 // are not the same) so we can't use that.
681 uint32_t allocLen = 0;
682 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
683 *aLen = strlen(reinterpret_cast<char*>(*aData));
684 result = NS_OK;
686 } break;
688 case CF_UNICODETEXT: {
689 // Get the data out of the global data handle. The size we
690 // return should not include the null because the other
691 // platforms don't use nulls, so just return the length we get
692 // back from strlen(), since we know CF_UNICODETEXT is null
693 // terminated. Recall that GetGlobalData() returns the size of
694 // the allocated buffer, not the size of the data (on 98, these
695 // are not the same) so we can't use that.
696 uint32_t allocLen = 0;
697 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
698 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
699 result = NS_OK;
701 } break;
703 case CF_DIBV5:
704 if (aMIMEImageFormat) {
705 uint32_t allocLen = 0;
706 const char* clipboardData;
707 if (NS_SUCCEEDED(GetGlobalData(
708 stm.hGlobal, (void**)&clipboardData, &allocLen))) {
709 nsCOMPtr<imgIContainer> container;
710 nsCOMPtr<imgITools> imgTools =
711 do_CreateInstance("@mozilla.org/image/tools;1");
712 result = imgTools->DecodeImageFromBuffer(
713 clipboardData, allocLen,
714 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
715 getter_AddRefs(container));
716 if (NS_FAILED(result)) {
717 break;
720 nsAutoCString mimeType;
721 if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
722 mimeType.Assign(IMAGE_JPEG);
723 } else {
724 mimeType.Assign(aMIMEImageFormat);
727 nsCOMPtr<nsIInputStream> inputStream;
728 result = imgTools->EncodeImage(container, mimeType, u""_ns,
729 getter_AddRefs(inputStream));
730 if (NS_FAILED(result)) {
731 break;
734 if (!inputStream) {
735 result = NS_ERROR_FAILURE;
736 break;
739 *aData = inputStream.forget().take();
740 *aLen = sizeof(nsIInputStream*);
743 break;
745 case CF_HDROP: {
746 // in the case of a file drop, multiple files are stashed within a
747 // single data object. In order to match mozilla's D&D apis, we
748 // just pull out the file at the requested index, pretending as
749 // if there really are multiple drag items.
750 HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
752 UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
753 NS_ASSERTION(numFiles > 0,
754 "File drop flavor, but no files...hmmmm");
755 NS_ASSERTION(aIndex < numFiles,
756 "Asked for a file index out of range of list");
757 if (numFiles > 0) {
758 UINT fileNameLen =
759 ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
760 wchar_t* buffer = reinterpret_cast<wchar_t*>(
761 moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
762 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
763 *aData = buffer;
764 *aLen = fileNameLen * sizeof(char16_t);
765 result = NS_OK;
767 GlobalUnlock(stm.hGlobal);
769 } break;
771 default: {
772 if (fe.cfFormat == fileDescriptorFlavorA ||
773 fe.cfFormat == fileDescriptorFlavorW ||
774 fe.cfFormat == fileFlavor) {
775 NS_WARNING(
776 "Mozilla doesn't yet understand how to read this type of "
777 "file flavor");
778 } else {
779 // Get the data out of the global data handle. The size we
780 // return should not include the null because the other
781 // platforms don't use nulls, so just return the length we get
782 // back from strlen(), since we know CF_UNICODETEXT is null
783 // terminated. Recall that GetGlobalData() returns the size of
784 // the allocated buffer, not the size of the data (on 98, these
785 // are not the same) so we can't use that.
787 // NOTE: we are assuming that anything that falls into this
788 // default case is unicode. As we start to get more
789 // kinds of binary data, this may become an incorrect
790 // assumption. Stay tuned.
791 uint32_t allocLen = 0;
792 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
793 if (fe.cfFormat == GetHtmlClipboardFormat()) {
794 // CF_HTML is actually UTF8, not unicode, so disregard the
795 // assumption above. We have to check the header for the
796 // actual length, and we'll do that in FindPlatformHTML().
797 // For now, return the allocLen. This case is mostly to
798 // ensure we don't try to call strlen on the buffer.
799 *aLen = allocLen;
800 } else if (fe.cfFormat == GetCustomClipboardFormat()) {
801 // Binary data
802 *aLen = allocLen;
803 } else if (fe.cfFormat == preferredDropEffect) {
804 // As per the MSDN doc entitled: "Shell Clipboard Formats"
805 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
806 // Reference:
807 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
808 NS_ASSERTION(
809 allocLen == sizeof(DWORD),
810 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
811 *aLen = allocLen;
812 } else {
813 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
814 sizeof(char16_t);
816 result = NS_OK;
819 } break;
820 } // switch
821 } break;
823 case TYMED_GDI: {
824 #ifdef DEBUG
825 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
826 ("*********************** TYMED_GDI\n"));
827 #endif
828 } break;
830 default:
831 break;
832 } // switch
834 ReleaseStgMedium(&stm);
837 return result;
840 //-------------------------------------------------------------------------
841 nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
842 UINT anIndex, nsIWidget* aWindow,
843 nsITransferable* aTransferable) {
844 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
846 // make sure we have a good transferable
847 if (!aTransferable) {
848 return NS_ERROR_INVALID_ARG;
851 nsresult res = NS_ERROR_FAILURE;
853 // get flavor list that includes all flavors that can be written (including
854 // ones obtained through conversion)
855 nsTArray<nsCString> flavors;
856 res = aTransferable->FlavorsTransferableCanImport(flavors);
857 if (NS_FAILED(res)) {
858 return NS_ERROR_FAILURE;
861 // Walk through flavors and see which flavor is on the clipboard them on the
862 // native clipboard,
863 for (uint32_t i = 0; i < flavors.Length(); i++) {
864 nsCString& flavorStr = flavors[i];
865 UINT format = GetFormat(flavorStr.get());
867 // Try to get the data using the desired flavor. This might fail, but all is
868 // not lost.
869 void* data = nullptr;
870 uint32_t dataLen = 0;
871 bool dataFound = false;
872 if (nullptr != aDataObject) {
873 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
874 flavorStr.get(), &data,
875 &dataLen))) {
876 dataFound = true;
878 } else if (nullptr != aWindow) {
879 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
880 &data, &dataLen))) {
881 dataFound = true;
885 // This is our second chance to try to find some data, having not found it
886 // when directly asking for the flavor. Let's try digging around in other
887 // flavors to help satisfy our craving for data.
888 if (!dataFound) {
889 if (flavorStr.EqualsLiteral(kTextMime)) {
890 dataFound =
891 FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
892 } else if (flavorStr.EqualsLiteral(kURLMime)) {
893 // drags from other windows apps expose the native
894 // CFSTR_INETURL{A,W} flavor
895 dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
896 if (!dataFound) {
897 dataFound =
898 FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
901 } // if we try one last ditch effort to find our data
903 // Hopefully by this point we've found it and can go about our business
904 if (dataFound) {
905 nsCOMPtr<nsISupports> genericDataWrapper;
906 if (flavorStr.EqualsLiteral(kFileMime)) {
907 // we have a file path in |data|. Create an nsLocalFile object.
908 nsDependentString filepath(reinterpret_cast<char16_t*>(data));
909 nsCOMPtr<nsIFile> file;
910 if (NS_SUCCEEDED(
911 NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
912 genericDataWrapper = do_QueryInterface(file);
914 free(data);
915 } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
916 uint32_t dummy;
917 // the editor folks want CF_HTML exactly as it's on the clipboard, no
918 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
919 // a wrapper and hand it back to them.
920 if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
921 nsPrimitiveHelpers::CreatePrimitiveForData(
922 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
923 } else {
924 free(data);
925 continue; // something wrong with this flavor, keep looking for other
926 // data
928 free(data);
929 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
930 uint32_t startOfData = 0;
931 // The JS folks want CF_HTML exactly as it is on the clipboard, but
932 // minus the CF_HTML header index information.
933 // It also needs to be converted to UTF16 and have linebreaks changed.
934 if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
935 &dataLen)) {
936 dataLen -= startOfData;
937 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
938 static_cast<char*>(data) + startOfData, &dataLen,
939 getter_AddRefs(genericDataWrapper));
940 } else {
941 free(data);
942 continue; // something wrong with this flavor, keep looking for other
943 // data
945 free(data);
946 } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
947 flavorStr.EqualsLiteral(kJPGImageMime) ||
948 flavorStr.EqualsLiteral(kPNGImageMime)) {
949 nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
950 genericDataWrapper = do_QueryInterface(imageStream);
951 NS_IF_RELEASE(imageStream);
952 } else {
953 // Treat custom types as a string of bytes.
954 if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
955 bool isRTF = flavorStr.EqualsLiteral(kRTFMime);
956 // we probably have some form of text. The DOM only wants LF, so
957 // convert from Win32 line endings to DOM line endings.
958 int32_t signedLen = static_cast<int32_t>(dataLen);
959 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(isRTF, &data,
960 &signedLen);
961 dataLen = signedLen;
963 if (isRTF) {
964 // RTF on Windows is known to sometimes deliver an extra null byte.
965 if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
966 dataLen--;
971 nsPrimitiveHelpers::CreatePrimitiveForData(
972 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
973 free(data);
976 NS_ASSERTION(genericDataWrapper,
977 "About to put null data into the transferable");
978 aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
979 res = NS_OK;
981 // we found one, get out of the loop
982 break;
984 } // foreach flavor
986 return res;
990 // FindPlatformHTML
992 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
993 // as-is.
995 bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
996 void** outData, uint32_t* outStartOfData,
997 uint32_t* outDataLen) {
998 // Reference: MSDN doc entitled "HTML Clipboard Format"
999 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
1000 // CF_HTML is UTF8, not unicode. We also can't rely on it being
1001 // null-terminated so we have to check the CF_HTML header for the correct
1002 // length. The length we return is the bytecount from the beginning of the
1003 // selected data to the end of the selected data, without the null
1004 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
1006 if (!outData || !*outData) {
1007 return false;
1010 char version[8] = {0};
1011 int32_t startOfData = 0;
1012 int32_t endOfData = 0;
1013 int numFound =
1014 sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
1015 &startOfData, &endOfData);
1017 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
1018 return false;
1021 // Fixup the start and end markers if they have no context (set to -1)
1022 if (startOfData == -1) {
1023 startOfData = 0;
1025 if (endOfData == -1) {
1026 endOfData = *outDataLen;
1029 // Make sure we were passed sane values within our buffer size.
1030 // (Note that we've handled all cases of negative endOfData above, so we can
1031 // safely cast it to be unsigned here.)
1032 if (!endOfData || startOfData >= endOfData ||
1033 static_cast<uint32_t>(endOfData) > *outDataLen) {
1034 return false;
1037 // We want to return the buffer not offset by startOfData because it will be
1038 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
1039 // in CF_HTML format.
1041 // We return the byte offset from the start of the data buffer to where the
1042 // HTML data starts. The caller might want to extract the HTML only.
1043 *outStartOfData = startOfData;
1044 *outDataLen = endOfData;
1045 return true;
1049 // FindUnicodeFromPlainText
1051 // Looks for CF_TEXT on the clipboard and converts it into an UTF-16 string
1052 // if present. Returns this string in outData, and its length in outDataLen.
1053 // XXXndeakin Windows converts between CF_UNICODE and CF_TEXT automatically
1054 // so it doesn't seem like this is actually needed.
1056 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
1057 UINT inIndex, void** outData,
1058 uint32_t* outDataLen) {
1059 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
1061 // We are looking for text/plain and we failed to find it on the clipboard
1062 // first, so try again with CF_TEXT. If that is present, convert it to
1063 // unicode.
1064 nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, CF_TEXT,
1065 nullptr, outData, outDataLen);
1066 if (NS_FAILED(rv) || !*outData) {
1067 return false;
1070 const char* castedText = static_cast<char*>(*outData);
1071 nsAutoString tmp;
1072 rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
1073 tmp);
1074 if (NS_FAILED(rv)) {
1075 return false;
1078 // out with the old, in with the new
1079 free(*outData);
1080 *outData = ToNewUnicode(tmp);
1081 *outDataLen = tmp.Length() * sizeof(char16_t);
1083 return true;
1085 } // FindUnicodeFromPlainText
1088 // FindURLFromLocalFile
1090 // we are looking for a URL and couldn't find it, try again with looking for
1091 // a local file. If we have one, it may either be a normal file or an internet
1092 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
1093 // in the local file case).
1095 bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
1096 void** outData, uint32_t* outDataLen) {
1097 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
1099 bool dataFound = false;
1101 nsresult loadResult =
1102 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
1103 nullptr, outData, outDataLen);
1104 if (NS_SUCCEEDED(loadResult) && *outData) {
1105 // we have a file path in |data|. Is it an internet shortcut or a normal
1106 // file?
1107 const nsDependentString filepath(static_cast<char16_t*>(*outData));
1108 nsCOMPtr<nsIFile> file;
1109 nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
1110 if (NS_FAILED(rv)) {
1111 free(*outData);
1112 return dataFound;
1115 if (IsInternetShortcut(filepath)) {
1116 free(*outData);
1117 nsAutoCString url;
1118 ResolveShortcut(file, url);
1119 if (!url.IsEmpty()) {
1120 // convert it to unicode and pass it out
1121 NS_ConvertUTF8toUTF16 urlString(url);
1122 // the internal mozilla URL format, text/x-moz-url, contains
1123 // URL\ntitle. We can guess the title from the file's name.
1124 nsAutoString title;
1125 file->GetLeafName(title);
1126 // We rely on IsInternetShortcut check that file has a .url extension.
1127 title.SetLength(title.Length() - 4);
1128 if (title.IsEmpty()) {
1129 title = urlString;
1131 *outData = ToNewUnicode(urlString + u"\n"_ns + title);
1132 *outDataLen =
1133 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1135 dataFound = true;
1137 } else {
1138 // we have a normal file, use some Necko objects to get our file path
1139 nsAutoCString urlSpec;
1140 NS_GetURLSpecFromFile(file, urlSpec);
1142 // convert it to unicode and pass it out
1143 free(*outData);
1144 *outData = UTF8ToNewUnicode(urlSpec);
1145 *outDataLen =
1146 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1147 dataFound = true;
1148 } // else regular file
1151 return dataFound;
1152 } // FindURLFromLocalFile
1155 // FindURLFromNativeURL
1157 // we are looking for a URL and couldn't find it using our internal
1158 // URL flavor, so look for it using the native URL flavor,
1159 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
1161 bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
1162 void** outData, uint32_t* outDataLen) {
1163 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
1165 bool dataFound = false;
1167 void* tempOutData = nullptr;
1168 uint32_t tempDataLen = 0;
1170 nsresult loadResult = GetNativeDataOffClipboard(
1171 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
1172 &tempOutData, &tempDataLen);
1173 if (NS_SUCCEEDED(loadResult) && tempOutData) {
1174 nsDependentString urlString(static_cast<char16_t*>(tempOutData));
1175 // the internal mozilla URL format, text/x-moz-url, contains
1176 // URL\ntitle. Since we don't actually have a title here,
1177 // just repeat the URL to fake it.
1178 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
1179 *outDataLen =
1180 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1181 free(tempOutData);
1182 dataFound = true;
1183 } else {
1184 loadResult = GetNativeDataOffClipboard(
1185 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
1186 nullptr, &tempOutData, &tempDataLen);
1187 if (NS_SUCCEEDED(loadResult) && tempOutData) {
1188 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
1189 // CF_TEXT which is by definition ANSI encoded.
1190 nsCString urlUnescapedA;
1191 bool unescaped =
1192 NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
1193 esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
1195 nsString urlString;
1196 if (unescaped) {
1197 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
1198 } else {
1199 NS_CopyNativeToUnicode(
1200 nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
1201 urlString);
1204 // the internal mozilla URL format, text/x-moz-url, contains
1205 // URL\ntitle. Since we don't actually have a title here,
1206 // just repeat the URL to fake it.
1207 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
1208 *outDataLen =
1209 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1210 free(tempOutData);
1211 dataFound = true;
1215 return dataFound;
1216 } // FindURLFromNativeURL
1218 // Other apps can block access to the clipboard. This repeatedly
1219 // calls `::OleGetClipboard` for a fixed number of times and should be called
1220 // instead of `::OleGetClipboard`.
1221 static HRESULT RepeatedlyTryOleGetClipboard(IDataObject** aDataObj) {
1222 return RepeatedlyTry(::OleGetClipboard, LogOleGetClipboardResult, aDataObj);
1226 // ResolveShortcut
1228 void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
1229 nsCOMPtr<nsIFileProtocolHandler> fph;
1230 nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
1231 if (NS_FAILED(rv)) {
1232 return;
1235 nsCOMPtr<nsIURI> uri;
1236 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
1237 if (NS_FAILED(rv)) {
1238 return;
1241 uri->GetSpec(outURL);
1242 } // ResolveShortcut
1245 // IsInternetShortcut
1247 // A file is an Internet Shortcut if it ends with .URL
1249 bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
1250 return StringEndsWith(inFileName, u".url"_ns,
1251 nsCaseInsensitiveStringComparator);
1252 } // IsInternetShortcut
1254 //-------------------------------------------------------------------------
1255 NS_IMETHODIMP
1256 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
1257 int32_t aWhichClipboard) {
1258 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
1259 ("%s aWhichClipboard=%i", __FUNCTION__, aWhichClipboard));
1261 // make sure we have a good transferable
1262 if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
1263 return NS_ERROR_FAILURE;
1266 nsresult res;
1268 // This makes sure we can use the OLE functionality for the clipboard
1269 IDataObject* dataObj;
1270 if (S_OK == RepeatedlyTryOleGetClipboard(&dataObj)) {
1271 // Use OLE IDataObject for clipboard operations
1272 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose,
1273 ("%s: use OLE IDataObject.", __FUNCTION__));
1274 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1275 dataObj->Release();
1276 } else {
1277 // do it the old manual way
1278 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1280 return res;
1283 NS_IMETHODIMP
1284 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
1285 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1286 // clipboard to scan it. If we i) empty and then ii) set data, then the
1287 // 'set data' can sometimes fail with access denied becacuse another program
1288 // has the clipboard open. So to avoid this race condition for OpenClipboard
1289 // we do not empty the clipboard when we're setting it.
1290 if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
1291 RepeatedlyTryOleSetClipboard(nullptr);
1293 return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
1296 //-------------------------------------------------------------------------
1297 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
1298 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1299 bool* _retval) {
1300 *_retval = false;
1301 if (aWhichClipboard != kGlobalClipboard) {
1302 return NS_OK;
1305 for (auto& flavor : aFlavorList) {
1306 UINT format = GetFormat(flavor.get());
1307 if (IsClipboardFormatAvailable(format)) {
1308 *_retval = true;
1309 break;
1313 return NS_OK;