Bug 1728955: part 6) Log result of Windows' `OleSetClipboardResult`. r=masayuki
[gecko.git] / widget / windows / nsClipboard.cpp
blob747be906ad9faa5b4ca0d994687c421b89b7b6cf
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"
7 #include <ole2.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 "nsArrayUtils.h"
15 #include "nsCOMPtr.h"
16 #include "nsDataObj.h"
17 #include "nsString.h"
18 #include "nsNativeCharsetUtils.h"
19 #include "nsITransferable.h"
20 #include "nsCOMPtr.h"
21 #include "nsXPCOM.h"
22 #include "nsReadableUtils.h"
23 #include "nsUnicharUtils.h"
24 #include "nsPrimitiveHelpers.h"
25 #include "nsIWidget.h"
26 #include "nsWidgetsCID.h"
27 #include "nsCRT.h"
28 #include "nsNetUtil.h"
29 #include "nsIFileProtocolHandler.h"
30 #include "nsEscape.h"
31 #include "nsIObserverService.h"
32 #include "nsMimeTypes.h"
33 #include "imgITools.h"
35 using mozilla::LogLevel;
37 static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard");
39 /* static */
40 UINT nsClipboard::GetHtmlClipboardFormat() {
41 static UINT format = ::RegisterClipboardFormatW(L"HTML Format");
42 return format;
45 /* static */
46 UINT nsClipboard::GetCustomClipboardFormat() {
47 static UINT format =
48 ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
49 return format;
52 //-------------------------------------------------------------------------
54 // nsClipboard constructor
56 //-------------------------------------------------------------------------
57 nsClipboard::nsClipboard() : nsBaseClipboard() {
58 mIgnoreEmptyNotification = false;
59 mWindow = nullptr;
61 // Register for a shutdown notification so that we can flush data
62 // to the OS clipboard.
63 nsCOMPtr<nsIObserverService> observerService =
64 do_GetService("@mozilla.org/observer-service;1");
65 if (observerService) {
66 observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
67 PR_FALSE);
71 //-------------------------------------------------------------------------
72 // nsClipboard destructor
73 //-------------------------------------------------------------------------
74 nsClipboard::~nsClipboard() {}
76 NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
78 NS_IMETHODIMP
79 nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
80 const char16_t* aData) {
81 // This will be called on shutdown.
82 ::OleFlushClipboard();
83 ::CloseClipboard();
85 return NS_OK;
88 //-------------------------------------------------------------------------
89 UINT nsClipboard::GetFormat(const char* aMimeStr, bool aMapHTMLMime) {
90 UINT format;
92 if (strcmp(aMimeStr, kTextMime) == 0) {
93 format = CF_TEXT;
94 } else if (strcmp(aMimeStr, kUnicodeMime) == 0) {
95 format = CF_UNICODETEXT;
96 } else if (strcmp(aMimeStr, kRTFMime) == 0) {
97 format = ::RegisterClipboardFormat(L"Rich Text Format");
98 } else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
99 strcmp(aMimeStr, kJPGImageMime) == 0 ||
100 strcmp(aMimeStr, kPNGImageMime) == 0) {
101 format = CF_DIBV5;
102 } else if (strcmp(aMimeStr, kFileMime) == 0 ||
103 strcmp(aMimeStr, kFilePromiseMime) == 0) {
104 format = CF_HDROP;
105 } else if ((strcmp(aMimeStr, kNativeHTMLMime) == 0) ||
106 (aMapHTMLMime && strcmp(aMimeStr, kHTMLMime) == 0)) {
107 format = GetHtmlClipboardFormat();
108 } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
109 format = GetCustomClipboardFormat();
110 } else {
111 format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
114 return format;
117 //-------------------------------------------------------------------------
118 // static
119 nsresult nsClipboard::CreateNativeDataObject(nsITransferable* aTransferable,
120 IDataObject** aDataObj,
121 nsIURI* uri) {
122 if (nullptr == aTransferable) {
123 return NS_ERROR_FAILURE;
126 // Create our native DataObject that implements
127 // the OLE IDataObject interface
128 nsDataObj* dataObj = new nsDataObj(uri);
130 if (!dataObj) {
131 return NS_ERROR_OUT_OF_MEMORY;
134 dataObj->AddRef();
136 // Now set it up with all the right data flavors & enums
137 nsresult res = SetupNativeDataObject(aTransferable, dataObj);
138 if (NS_OK == res) {
139 *aDataObj = dataObj;
140 } else {
141 dataObj->Release();
143 return res;
146 //-------------------------------------------------------------------------
147 nsresult nsClipboard::SetupNativeDataObject(nsITransferable* aTransferable,
148 IDataObject* aDataObj) {
149 if (nullptr == aTransferable || nullptr == aDataObj) {
150 return NS_ERROR_FAILURE;
153 nsDataObj* dObj = static_cast<nsDataObj*>(aDataObj);
155 // Now give the Transferable to the DataObject
156 // for getting the data out of it
157 dObj->SetTransferable(aTransferable);
159 // Get the transferable list of data flavors
160 nsTArray<nsCString> flavors;
161 aTransferable->FlavorsTransferableCanExport(flavors);
163 // Walk through flavors that contain data and register them
164 // into the DataObj as supported flavors
165 for (uint32_t i = 0; i < flavors.Length(); i++) {
166 nsCString& flavorStr = flavors[i];
168 // When putting data onto the clipboard, we want to maintain kHTMLMime
169 // ("text/html") and not map it to CF_HTML here since this will be done
170 // below.
171 UINT format = GetFormat(flavorStr.get(), false);
173 // Now tell the native IDataObject about both our mime type and
174 // the native data format
175 FORMATETC fe;
176 SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
177 dObj->AddDataFlavor(flavorStr.get(), &fe);
179 // Do various things internal to the implementation, like map one
180 // flavor to another or add additional flavors based on what's required
181 // for the win32 impl.
182 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
183 // if we find text/unicode, also advertise text/plain (which we will
184 // convert on our own in nsDataObj::GetText().
185 FORMATETC textFE;
186 SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
187 dObj->AddDataFlavor(kTextMime, &textFE);
188 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
189 // if we find text/html, also advertise win32's html flavor (which we will
190 // convert on our own in nsDataObj::GetText().
191 FORMATETC htmlFE;
192 SET_FORMATETC(htmlFE, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
193 TYMED_HGLOBAL);
194 dObj->AddDataFlavor(kHTMLMime, &htmlFE);
195 } else if (flavorStr.EqualsLiteral(kURLMime)) {
196 // if we're a url, in addition to also being text, we need to register
197 // the "file" flavors so that the win32 shell knows to create an internet
198 // shortcut when it sees one of these beasts.
199 FORMATETC shortcutFE;
200 SET_FORMATETC(shortcutFE,
201 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
202 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
203 dObj->AddDataFlavor(kURLMime, &shortcutFE);
204 SET_FORMATETC(shortcutFE,
205 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
206 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
207 dObj->AddDataFlavor(kURLMime, &shortcutFE);
208 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
209 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
210 dObj->AddDataFlavor(kURLMime, &shortcutFE);
211 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
212 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
213 dObj->AddDataFlavor(kURLMime, &shortcutFE);
214 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
215 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
216 dObj->AddDataFlavor(kURLMime, &shortcutFE);
217 } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
218 flavorStr.EqualsLiteral(kJPEGImageMime) ||
219 flavorStr.EqualsLiteral(kJPGImageMime) ||
220 flavorStr.EqualsLiteral(kGIFImageMime) ||
221 flavorStr.EqualsLiteral(kNativeImageMime)) {
222 // if we're an image, register the native bitmap flavor
223 FORMATETC imageFE;
224 // Add DIBv5
225 SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
226 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
227 // Add DIBv3
228 SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
229 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
230 } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
231 // if we're a file promise flavor, also register the
232 // CFSTR_PREFERREDDROPEFFECT format. The data object
233 // returns a value of DROPEFFECTS_MOVE to the drop target
234 // when it asks for the value of this format. This causes
235 // the file to be moved from the temporary location instead
236 // of being copied. The right thing to do here is to call
237 // SetData() on the data object and set the value of this format
238 // to DROPEFFECTS_MOVE on this particular data object. But,
239 // since all the other clipboard formats follow the model of setting
240 // data on the data object only when the drop object calls GetData(),
241 // I am leaving this format's value hard coded in the data object.
242 // We can change this if other consumers of this format get added to this
243 // codebase and they need different values.
244 FORMATETC shortcutFE;
245 SET_FORMATETC(shortcutFE,
246 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
247 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
248 dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
252 return NS_OK;
255 // static
256 void nsClipboard::OleSetClipboardResultToString(HRESULT aHres,
257 nsACString& aResult) {
258 switch (aHres) {
259 case S_OK:
260 aResult = "S_OK";
261 break;
262 case CLIPBRD_E_CANT_OPEN:
263 aResult = "CLIPBRD_E_CANT_OPEN";
264 break;
265 case CLIPBRD_E_CANT_EMPTY:
266 aResult = "CLIPBRD_E_CANT_EMPTY";
267 break;
268 case CLIPBRD_E_CANT_CLOSE:
269 aResult = "CLIPBRD_E_CANT_CLOSE";
270 break;
271 case CLIPBRD_E_CANT_SET:
272 aResult = "CLIPBRD_E_CANT_SET";
273 break;
274 default:
275 // Explicit template instantiaton, because otherwise the call is
276 // ambiguous.
277 constexpr int kRadix = 16;
278 aResult = IntToCString<int32_t>(aHres, kRadix);
279 break;
283 // static
284 void nsClipboard::LogOleSetClipboardResult(const HRESULT aHres) {
285 if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
286 nsAutoCString hresString;
287 OleSetClipboardResultToString(aHres, hresString);
288 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
289 ("OleSetClipboard result: %s", hresString.get()));
293 //-------------------------------------------------------------------------
294 NS_IMETHODIMP nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
295 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
297 if (aWhichClipboard != kGlobalClipboard) {
298 return NS_ERROR_FAILURE;
301 mIgnoreEmptyNotification = true;
303 // make sure we have a good transferable
304 if (nullptr == mTransferable) {
305 return NS_ERROR_FAILURE;
308 IDataObject* dataObj;
309 if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj,
310 nullptr))) { // this add refs dataObj
311 LogOleSetClipboardResult(::OleSetClipboard(dataObj));
312 dataObj->Release();
313 } else {
314 // Clear the native clipboard
315 LogOleSetClipboardResult(::OleSetClipboard(nullptr));
318 mIgnoreEmptyNotification = false;
320 return NS_OK;
323 //-------------------------------------------------------------------------
324 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
325 uint32_t* aLen) {
326 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose, ("%s", __FUNCTION__));
328 // Allocate a new memory buffer and copy the data from global memory.
329 // Recall that win98 allocates to nearest DWORD boundary. As a safety
330 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
331 // and null them out to ensure that all of our NS_strlen calls will succeed.
332 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
333 // a full NUL char16_t when |*aLen| is odd.
334 nsresult result = NS_ERROR_FAILURE;
335 if (aHGBL != nullptr) {
336 LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
337 CheckedInt<uint32_t> allocSize =
338 CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
339 if (!allocSize.isValid()) {
340 return NS_ERROR_INVALID_ARG;
342 char* data = static_cast<char*>(malloc(allocSize.value()));
343 if (data) {
344 uint32_t size = allocSize.value() - 3;
345 memcpy(data, lpStr, size);
346 // null terminate for safety
347 data[size] = data[size + 1] = data[size + 2] = '\0';
349 GlobalUnlock(aHGBL);
350 *aData = data;
351 *aLen = size;
353 result = NS_OK;
355 } else {
356 // We really shouldn't ever get here
357 // but just in case
358 *aData = nullptr;
359 *aLen = 0;
360 LPVOID lpMsgBuf;
362 FormatMessageW(
363 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
364 GetLastError(),
365 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
366 (LPWSTR)&lpMsgBuf, 0, nullptr);
368 // Display the string.
369 MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
370 MB_OK | MB_ICONINFORMATION);
372 // Free the buffer.
373 LocalFree(lpMsgBuf);
376 return result;
379 //-------------------------------------------------------------------------
380 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
381 UINT /*aIndex*/, UINT aFormat,
382 void** aData, uint32_t* aLen) {
383 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
384 ("%s: overload taking nsIWidget*.", __FUNCTION__));
386 HGLOBAL hglb;
387 nsresult result = NS_ERROR_FAILURE;
389 HWND nativeWin = nullptr;
390 if (::OpenClipboard(nativeWin)) {
391 hglb = ::GetClipboardData(aFormat);
392 result = GetGlobalData(hglb, aData, aLen);
393 ::CloseClipboard();
395 return result;
398 static void DisplayErrCode(HRESULT hres) {
399 if (hres == E_INVALIDARG) {
400 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
401 } else if (hres == E_UNEXPECTED) {
402 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
403 } else if (hres == E_OUTOFMEMORY) {
404 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
405 } else if (hres == DV_E_LINDEX) {
406 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
407 } else if (hres == DV_E_FORMATETC) {
408 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
409 } else if (hres == DV_E_TYMED) {
410 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
411 } else if (hres == DV_E_DVASPECT) {
412 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
413 } else if (hres == OLE_E_NOTRUNNING) {
414 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
415 } else if (hres == STG_E_MEDIUMFULL) {
416 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
417 } else if (hres == DV_E_CLIPFORMAT) {
418 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
419 } else if (hres == S_OK) {
420 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
421 } else {
422 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
423 ("****** DisplayErrCode 0x%X\n", hres));
427 //-------------------------------------------------------------------------
428 static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
429 LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) {
430 SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
432 // Starting by querying for the data to see if we can get it as from global
433 // memory
434 HRESULT hres = S_FALSE;
435 hres = aDataObject->QueryGetData(pFE);
436 DisplayErrCode(hres);
437 if (S_OK == hres) {
438 hres = aDataObject->GetData(pFE, pSTM);
439 DisplayErrCode(hres);
441 return hres;
444 //-------------------------------------------------------------------------
445 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
446 // an image encoder (e.g. image/png).
447 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
448 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
449 UINT aIndex, UINT aFormat,
450 const char* aMIMEImageFormat,
451 void** aData, uint32_t* aLen) {
452 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
453 ("%s: overload taking IDataObject*.", __FUNCTION__));
455 nsresult result = NS_ERROR_FAILURE;
456 *aData = nullptr;
457 *aLen = 0;
459 if (!aDataObject) {
460 return result;
463 UINT format = aFormat;
464 HRESULT hres = S_FALSE;
466 // XXX at the moment we only support global memory transfers
467 // It is here where we will add support for native images
468 // and IStream
469 FORMATETC fe;
470 STGMEDIUM stm;
471 hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
473 // Currently this is only handling TYMED_HGLOBAL data
474 // For Text, Dibs, Files, and generic data (like HTML)
475 if (S_OK == hres) {
476 static CLIPFORMAT fileDescriptorFlavorA =
477 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
478 static CLIPFORMAT fileDescriptorFlavorW =
479 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
480 static CLIPFORMAT fileFlavor =
481 ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
482 static CLIPFORMAT preferredDropEffect =
483 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
485 switch (stm.tymed) {
486 case TYMED_HGLOBAL: {
487 switch (fe.cfFormat) {
488 case CF_TEXT: {
489 // Get the data out of the global data handle. The size we
490 // return should not include the null because the other
491 // platforms don't use nulls, so just return the length we get
492 // back from strlen(), since we know CF_TEXT is null
493 // terminated. Recall that GetGlobalData() returns the size of
494 // the allocated buffer, not the size of the data (on 98, these
495 // are not the same) so we can't use that.
496 uint32_t allocLen = 0;
497 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
498 *aLen = strlen(reinterpret_cast<char*>(*aData));
499 result = NS_OK;
501 } break;
503 case CF_UNICODETEXT: {
504 // Get the data out of the global data handle. The size we
505 // return should not include the null because the other
506 // platforms don't use nulls, so just return the length we get
507 // back from strlen(), since we know CF_UNICODETEXT is null
508 // terminated. Recall that GetGlobalData() returns the size of
509 // the allocated buffer, not the size of the data (on 98, these
510 // are not the same) so we can't use that.
511 uint32_t allocLen = 0;
512 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
513 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
514 result = NS_OK;
516 } break;
518 case CF_DIBV5:
519 if (aMIMEImageFormat) {
520 uint32_t allocLen = 0;
521 const char* clipboardData;
522 if (NS_SUCCEEDED(GetGlobalData(
523 stm.hGlobal, (void**)&clipboardData, &allocLen))) {
524 nsCOMPtr<imgIContainer> container;
525 nsCOMPtr<imgITools> imgTools =
526 do_CreateInstance("@mozilla.org/image/tools;1");
527 result = imgTools->DecodeImageFromBuffer(
528 clipboardData, allocLen,
529 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
530 getter_AddRefs(container));
531 if (NS_FAILED(result)) {
532 break;
535 nsAutoCString mimeType;
536 if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
537 mimeType.Assign(IMAGE_JPEG);
538 } else {
539 mimeType.Assign(aMIMEImageFormat);
542 nsCOMPtr<nsIInputStream> inputStream;
543 result = imgTools->EncodeImage(container, mimeType, u""_ns,
544 getter_AddRefs(inputStream));
545 if (NS_FAILED(result)) {
546 break;
549 if (!inputStream) {
550 result = NS_ERROR_FAILURE;
551 break;
554 *aData = inputStream.forget().take();
555 *aLen = sizeof(nsIInputStream*);
558 break;
560 case CF_HDROP: {
561 // in the case of a file drop, multiple files are stashed within a
562 // single data object. In order to match mozilla's D&D apis, we
563 // just pull out the file at the requested index, pretending as
564 // if there really are multiple drag items.
565 HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
567 UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
568 NS_ASSERTION(numFiles > 0,
569 "File drop flavor, but no files...hmmmm");
570 NS_ASSERTION(aIndex < numFiles,
571 "Asked for a file index out of range of list");
572 if (numFiles > 0) {
573 UINT fileNameLen =
574 ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
575 wchar_t* buffer = reinterpret_cast<wchar_t*>(
576 moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
577 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
578 *aData = buffer;
579 *aLen = fileNameLen * sizeof(char16_t);
580 result = NS_OK;
582 GlobalUnlock(stm.hGlobal);
584 } break;
586 default: {
587 if (fe.cfFormat == fileDescriptorFlavorA ||
588 fe.cfFormat == fileDescriptorFlavorW ||
589 fe.cfFormat == fileFlavor) {
590 NS_WARNING(
591 "Mozilla doesn't yet understand how to read this type of "
592 "file flavor");
593 } else {
594 // Get the data out of the global data handle. The size we
595 // return should not include the null because the other
596 // platforms don't use nulls, so just return the length we get
597 // back from strlen(), since we know CF_UNICODETEXT is null
598 // terminated. Recall that GetGlobalData() returns the size of
599 // the allocated buffer, not the size of the data (on 98, these
600 // are not the same) so we can't use that.
602 // NOTE: we are assuming that anything that falls into this
603 // default case is unicode. As we start to get more
604 // kinds of binary data, this may become an incorrect
605 // assumption. Stay tuned.
606 uint32_t allocLen = 0;
607 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
608 if (fe.cfFormat == GetHtmlClipboardFormat()) {
609 // CF_HTML is actually UTF8, not unicode, so disregard the
610 // assumption above. We have to check the header for the
611 // actual length, and we'll do that in FindPlatformHTML().
612 // For now, return the allocLen. This case is mostly to
613 // ensure we don't try to call strlen on the buffer.
614 *aLen = allocLen;
615 } else if (fe.cfFormat == GetCustomClipboardFormat()) {
616 // Binary data
617 *aLen = allocLen;
618 } else if (fe.cfFormat == preferredDropEffect) {
619 // As per the MSDN doc entitled: "Shell Clipboard Formats"
620 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
621 // Reference:
622 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
623 NS_ASSERTION(
624 allocLen == sizeof(DWORD),
625 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
626 *aLen = allocLen;
627 } else {
628 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
629 sizeof(char16_t);
631 result = NS_OK;
634 } break;
635 } // switch
636 } break;
638 case TYMED_GDI: {
639 #ifdef DEBUG
640 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
641 ("*********************** TYMED_GDI\n"));
642 #endif
643 } break;
645 default:
646 break;
647 } // switch
649 ReleaseStgMedium(&stm);
652 return result;
655 //-------------------------------------------------------------------------
656 nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
657 UINT anIndex, nsIWidget* aWindow,
658 nsITransferable* aTransferable) {
659 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
661 // make sure we have a good transferable
662 if (!aTransferable) {
663 return NS_ERROR_INVALID_ARG;
666 nsresult res = NS_ERROR_FAILURE;
668 // get flavor list that includes all flavors that can be written (including
669 // ones obtained through conversion)
670 nsTArray<nsCString> flavors;
671 res = aTransferable->FlavorsTransferableCanImport(flavors);
672 if (NS_FAILED(res)) {
673 return NS_ERROR_FAILURE;
676 // Walk through flavors and see which flavor is on the clipboard them on the
677 // native clipboard,
678 for (uint32_t i = 0; i < flavors.Length(); i++) {
679 nsCString& flavorStr = flavors[i];
680 UINT format = GetFormat(flavorStr.get());
682 // Try to get the data using the desired flavor. This might fail, but all is
683 // not lost.
684 void* data = nullptr;
685 uint32_t dataLen = 0;
686 bool dataFound = false;
687 if (nullptr != aDataObject) {
688 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
689 flavorStr.get(), &data,
690 &dataLen))) {
691 dataFound = true;
693 } else if (nullptr != aWindow) {
694 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
695 &data, &dataLen))) {
696 dataFound = true;
700 // This is our second chance to try to find some data, having not found it
701 // when directly asking for the flavor. Let's try digging around in other
702 // flavors to help satisfy our craving for data.
703 if (!dataFound) {
704 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
705 dataFound =
706 FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
707 } else if (flavorStr.EqualsLiteral(kURLMime)) {
708 // drags from other windows apps expose the native
709 // CFSTR_INETURL{A,W} flavor
710 dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
711 if (!dataFound) {
712 dataFound =
713 FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
716 } // if we try one last ditch effort to find our data
718 // Hopefully by this point we've found it and can go about our business
719 if (dataFound) {
720 nsCOMPtr<nsISupports> genericDataWrapper;
721 if (flavorStr.EqualsLiteral(kFileMime)) {
722 // we have a file path in |data|. Create an nsLocalFile object.
723 nsDependentString filepath(reinterpret_cast<char16_t*>(data));
724 nsCOMPtr<nsIFile> file;
725 if (NS_SUCCEEDED(
726 NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
727 genericDataWrapper = do_QueryInterface(file);
729 free(data);
730 } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
731 uint32_t dummy;
732 // the editor folks want CF_HTML exactly as it's on the clipboard, no
733 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
734 // a wrapper and hand it back to them.
735 if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
736 nsPrimitiveHelpers::CreatePrimitiveForData(
737 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
738 } else {
739 free(data);
740 continue; // something wrong with this flavor, keep looking for other
741 // data
743 free(data);
744 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
745 uint32_t startOfData = 0;
746 // The JS folks want CF_HTML exactly as it is on the clipboard, but
747 // minus the CF_HTML header index information.
748 // It also needs to be converted to UTF16 and have linebreaks changed.
749 if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
750 &dataLen)) {
751 dataLen -= startOfData;
752 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
753 static_cast<char*>(data) + startOfData, &dataLen,
754 getter_AddRefs(genericDataWrapper));
755 } else {
756 free(data);
757 continue; // something wrong with this flavor, keep looking for other
758 // data
760 free(data);
761 } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
762 flavorStr.EqualsLiteral(kJPGImageMime) ||
763 flavorStr.EqualsLiteral(kPNGImageMime)) {
764 nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
765 genericDataWrapper = do_QueryInterface(imageStream);
766 NS_IF_RELEASE(imageStream);
767 } else {
768 // Treat custom types as a string of bytes.
769 if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
770 // we probably have some form of text. The DOM only wants LF, so
771 // convert from Win32 line endings to DOM line endings.
772 int32_t signedLen = static_cast<int32_t>(dataLen);
773 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &data,
774 &signedLen);
775 dataLen = signedLen;
777 if (flavorStr.EqualsLiteral(kRTFMime)) {
778 // RTF on Windows is known to sometimes deliver an extra null byte.
779 if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
780 dataLen--;
785 nsPrimitiveHelpers::CreatePrimitiveForData(
786 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
787 free(data);
790 NS_ASSERTION(genericDataWrapper,
791 "About to put null data into the transferable");
792 aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
793 res = NS_OK;
795 // we found one, get out of the loop
796 break;
798 } // foreach flavor
800 return res;
804 // FindPlatformHTML
806 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
807 // as-is.
809 bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
810 void** outData, uint32_t* outStartOfData,
811 uint32_t* outDataLen) {
812 // Reference: MSDN doc entitled "HTML Clipboard Format"
813 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
814 // CF_HTML is UTF8, not unicode. We also can't rely on it being
815 // null-terminated so we have to check the CF_HTML header for the correct
816 // length. The length we return is the bytecount from the beginning of the
817 // selected data to the end of the selected data, without the null
818 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
820 if (!outData || !*outData) {
821 return false;
824 char version[8] = {0};
825 int32_t startOfData = 0;
826 int32_t endOfData = 0;
827 int numFound =
828 sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
829 &startOfData, &endOfData);
831 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
832 return false;
835 // Fixup the start and end markers if they have no context (set to -1)
836 if (startOfData == -1) {
837 startOfData = 0;
839 if (endOfData == -1) {
840 endOfData = *outDataLen;
843 // Make sure we were passed sane values within our buffer size.
844 // (Note that we've handled all cases of negative endOfData above, so we can
845 // safely cast it to be unsigned here.)
846 if (!endOfData || startOfData >= endOfData ||
847 static_cast<uint32_t>(endOfData) > *outDataLen) {
848 return false;
851 // We want to return the buffer not offset by startOfData because it will be
852 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
853 // in CF_HTML format.
855 // We return the byte offset from the start of the data buffer to where the
856 // HTML data starts. The caller might want to extract the HTML only.
857 *outStartOfData = startOfData;
858 *outDataLen = endOfData;
859 return true;
863 // FindUnicodeFromPlainText
865 // we are looking for text/unicode and we failed to find it on the clipboard
866 // first, try again with text/plain. If that is present, convert it to unicode.
868 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
869 UINT inIndex, void** outData,
870 uint32_t* outDataLen) {
871 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
873 // we are looking for text/unicode and we failed to find it on the clipboard
874 // first, try again with text/plain. If that is present, convert it to
875 // unicode.
876 nsresult rv =
877 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime),
878 nullptr, outData, outDataLen);
879 if (NS_FAILED(rv) || !*outData) {
880 return false;
883 const char* castedText = static_cast<char*>(*outData);
884 nsAutoString tmp;
885 rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
886 tmp);
887 if (NS_FAILED(rv)) {
888 return false;
891 // out with the old, in with the new
892 free(*outData);
893 *outData = ToNewUnicode(tmp);
894 *outDataLen = tmp.Length() * sizeof(char16_t);
896 return true;
898 } // FindUnicodeFromPlainText
901 // FindURLFromLocalFile
903 // we are looking for a URL and couldn't find it, try again with looking for
904 // a local file. If we have one, it may either be a normal file or an internet
905 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
906 // in the local file case).
908 bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
909 void** outData, uint32_t* outDataLen) {
910 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
912 bool dataFound = false;
914 nsresult loadResult =
915 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
916 nullptr, outData, outDataLen);
917 if (NS_SUCCEEDED(loadResult) && *outData) {
918 // we have a file path in |data|. Is it an internet shortcut or a normal
919 // file?
920 const nsDependentString filepath(static_cast<char16_t*>(*outData));
921 nsCOMPtr<nsIFile> file;
922 nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
923 if (NS_FAILED(rv)) {
924 free(*outData);
925 return dataFound;
928 if (IsInternetShortcut(filepath)) {
929 free(*outData);
930 nsAutoCString url;
931 ResolveShortcut(file, url);
932 if (!url.IsEmpty()) {
933 // convert it to unicode and pass it out
934 NS_ConvertUTF8toUTF16 urlString(url);
935 // the internal mozilla URL format, text/x-moz-url, contains
936 // URL\ntitle. We can guess the title from the file's name.
937 nsAutoString title;
938 file->GetLeafName(title);
939 // We rely on IsInternetShortcut check that file has a .url extension.
940 title.SetLength(title.Length() - 4);
941 if (title.IsEmpty()) {
942 title = urlString;
944 *outData = ToNewUnicode(urlString + u"\n"_ns + title);
945 *outDataLen =
946 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
948 dataFound = true;
950 } else {
951 // we have a normal file, use some Necko objects to get our file path
952 nsAutoCString urlSpec;
953 NS_GetURLSpecFromFile(file, urlSpec);
955 // convert it to unicode and pass it out
956 free(*outData);
957 *outData = UTF8ToNewUnicode(urlSpec);
958 *outDataLen =
959 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
960 dataFound = true;
961 } // else regular file
964 return dataFound;
965 } // FindURLFromLocalFile
968 // FindURLFromNativeURL
970 // we are looking for a URL and couldn't find it using our internal
971 // URL flavor, so look for it using the native URL flavor,
972 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
974 bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
975 void** outData, uint32_t* outDataLen) {
976 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
978 bool dataFound = false;
980 void* tempOutData = nullptr;
981 uint32_t tempDataLen = 0;
983 nsresult loadResult = GetNativeDataOffClipboard(
984 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
985 &tempOutData, &tempDataLen);
986 if (NS_SUCCEEDED(loadResult) && tempOutData) {
987 nsDependentString urlString(static_cast<char16_t*>(tempOutData));
988 // the internal mozilla URL format, text/x-moz-url, contains
989 // URL\ntitle. Since we don't actually have a title here,
990 // just repeat the URL to fake it.
991 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
992 *outDataLen =
993 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
994 free(tempOutData);
995 dataFound = true;
996 } else {
997 loadResult = GetNativeDataOffClipboard(
998 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
999 nullptr, &tempOutData, &tempDataLen);
1000 if (NS_SUCCEEDED(loadResult) && tempOutData) {
1001 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
1002 // CF_TEXT which is by definition ANSI encoded.
1003 nsCString urlUnescapedA;
1004 bool unescaped =
1005 NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
1006 esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
1008 nsString urlString;
1009 if (unescaped) {
1010 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
1011 } else {
1012 NS_CopyNativeToUnicode(
1013 nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
1014 urlString);
1017 // the internal mozilla URL format, text/x-moz-url, contains
1018 // URL\ntitle. Since we don't actually have a title here,
1019 // just repeat the URL to fake it.
1020 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
1021 *outDataLen =
1022 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
1023 free(tempOutData);
1024 dataFound = true;
1028 return dataFound;
1029 } // FindURLFromNativeURL
1032 // ResolveShortcut
1034 void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
1035 nsCOMPtr<nsIFileProtocolHandler> fph;
1036 nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
1037 if (NS_FAILED(rv)) {
1038 return;
1041 nsCOMPtr<nsIURI> uri;
1042 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
1043 if (NS_FAILED(rv)) {
1044 return;
1047 uri->GetSpec(outURL);
1048 } // ResolveShortcut
1051 // IsInternetShortcut
1053 // A file is an Internet Shortcut if it ends with .URL
1055 bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
1056 return StringEndsWith(inFileName, u".url"_ns,
1057 nsCaseInsensitiveStringComparator);
1058 } // IsInternetShortcut
1060 //-------------------------------------------------------------------------
1061 NS_IMETHODIMP
1062 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
1063 int32_t aWhichClipboard) {
1064 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
1065 ("%s aWhichClipboard=%i", __FUNCTION__, aWhichClipboard));
1067 // make sure we have a good transferable
1068 if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
1069 return NS_ERROR_FAILURE;
1072 nsresult res;
1074 // This makes sure we can use the OLE functionality for the clipboard
1075 IDataObject* dataObj;
1076 if (S_OK == ::OleGetClipboard(&dataObj)) {
1077 // Use OLE IDataObject for clipboard operations
1078 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose,
1079 ("%s: use OLE IDataObject.", __FUNCTION__));
1080 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1081 dataObj->Release();
1082 } else {
1083 // do it the old manual way
1084 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1086 return res;
1089 NS_IMETHODIMP
1090 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
1091 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1092 // clipboard to scan it. If we i) empty and then ii) set data, then the
1093 // 'set data' can sometimes fail with access denied becacuse another program
1094 // has the clipboard open. So to avoid this race condition for OpenClipboard
1095 // we do not empty the clipboard when we're setting it.
1096 if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
1097 LogOleSetClipboardResult(::OleSetClipboard(nullptr));
1099 return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
1102 //-------------------------------------------------------------------------
1103 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
1104 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1105 bool* _retval) {
1106 *_retval = false;
1107 if (aWhichClipboard != kGlobalClipboard) {
1108 return NS_OK;
1111 for (auto& flavor : aFlavorList) {
1112 #ifdef DEBUG
1113 if (flavor.EqualsLiteral(kTextMime)) {
1114 NS_WARNING(
1115 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
1116 "INSTEAD");
1118 #endif
1120 UINT format = GetFormat(flavor.get());
1121 if (IsClipboardFormatAvailable(format)) {
1122 *_retval = true;
1123 break;
1124 } else {
1125 // We haven't found the exact flavor the client asked for, but maybe we
1126 // can still find it from something else that's on the clipboard...
1127 if (flavor.EqualsLiteral(kUnicodeMime)) {
1128 // client asked for unicode and it wasn't present, check if we have
1129 // CF_TEXT. We'll handle the actual data substitution in the data
1130 // object.
1131 if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
1132 *_retval = true;
1133 break;
1139 return NS_OK;