Bug 1728955: part 2) Add more logging to Windows-specific clipboard code. r=masayuki
[gecko.git] / widget / windows / nsClipboard.cpp
blob12e58895fd8b39847aee339b584a6fcccf7a7021
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 nsresult nsClipboard::CreateNativeDataObject(nsITransferable* aTransferable,
119 IDataObject** aDataObj,
120 nsIURI* uri) {
121 if (nullptr == aTransferable) {
122 return NS_ERROR_FAILURE;
125 // Create our native DataObject that implements
126 // the OLE IDataObject interface
127 nsDataObj* dataObj = new nsDataObj(uri);
129 if (!dataObj) {
130 return NS_ERROR_OUT_OF_MEMORY;
133 dataObj->AddRef();
135 // Now set it up with all the right data flavors & enums
136 nsresult res = SetupNativeDataObject(aTransferable, dataObj);
137 if (NS_OK == res) {
138 *aDataObj = dataObj;
139 } else {
140 dataObj->Release();
142 return res;
145 //-------------------------------------------------------------------------
146 nsresult nsClipboard::SetupNativeDataObject(nsITransferable* aTransferable,
147 IDataObject* aDataObj) {
148 if (nullptr == aTransferable || nullptr == aDataObj) {
149 return NS_ERROR_FAILURE;
152 nsDataObj* dObj = static_cast<nsDataObj*>(aDataObj);
154 // Now give the Transferable to the DataObject
155 // for getting the data out of it
156 dObj->SetTransferable(aTransferable);
158 // Get the transferable list of data flavors
159 nsTArray<nsCString> flavors;
160 aTransferable->FlavorsTransferableCanExport(flavors);
162 // Walk through flavors that contain data and register them
163 // into the DataObj as supported flavors
164 for (uint32_t i = 0; i < flavors.Length(); i++) {
165 nsCString& flavorStr = flavors[i];
167 // When putting data onto the clipboard, we want to maintain kHTMLMime
168 // ("text/html") and not map it to CF_HTML here since this will be done
169 // below.
170 UINT format = GetFormat(flavorStr.get(), false);
172 // Now tell the native IDataObject about both our mime type and
173 // the native data format
174 FORMATETC fe;
175 SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
176 dObj->AddDataFlavor(flavorStr.get(), &fe);
178 // Do various things internal to the implementation, like map one
179 // flavor to another or add additional flavors based on what's required
180 // for the win32 impl.
181 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
182 // if we find text/unicode, also advertise text/plain (which we will
183 // convert on our own in nsDataObj::GetText().
184 FORMATETC textFE;
185 SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
186 dObj->AddDataFlavor(kTextMime, &textFE);
187 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
188 // if we find text/html, also advertise win32's html flavor (which we will
189 // convert on our own in nsDataObj::GetText().
190 FORMATETC htmlFE;
191 SET_FORMATETC(htmlFE, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
192 TYMED_HGLOBAL);
193 dObj->AddDataFlavor(kHTMLMime, &htmlFE);
194 } else if (flavorStr.EqualsLiteral(kURLMime)) {
195 // if we're a url, in addition to also being text, we need to register
196 // the "file" flavors so that the win32 shell knows to create an internet
197 // shortcut when it sees one of these beasts.
198 FORMATETC shortcutFE;
199 SET_FORMATETC(shortcutFE,
200 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0,
201 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
202 dObj->AddDataFlavor(kURLMime, &shortcutFE);
203 SET_FORMATETC(shortcutFE,
204 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0,
205 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
206 dObj->AddDataFlavor(kURLMime, &shortcutFE);
207 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS),
208 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
209 dObj->AddDataFlavor(kURLMime, &shortcutFE);
210 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0,
211 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
212 dObj->AddDataFlavor(kURLMime, &shortcutFE);
213 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0,
214 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
215 dObj->AddDataFlavor(kURLMime, &shortcutFE);
216 } else if (flavorStr.EqualsLiteral(kPNGImageMime) ||
217 flavorStr.EqualsLiteral(kJPEGImageMime) ||
218 flavorStr.EqualsLiteral(kJPGImageMime) ||
219 flavorStr.EqualsLiteral(kGIFImageMime) ||
220 flavorStr.EqualsLiteral(kNativeImageMime)) {
221 // if we're an image, register the native bitmap flavor
222 FORMATETC imageFE;
223 // Add DIBv5
224 SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
225 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
226 // Add DIBv3
227 SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
228 dObj->AddDataFlavor(flavorStr.get(), &imageFE);
229 } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
230 // if we're a file promise flavor, also register the
231 // CFSTR_PREFERREDDROPEFFECT format. The data object
232 // returns a value of DROPEFFECTS_MOVE to the drop target
233 // when it asks for the value of this format. This causes
234 // the file to be moved from the temporary location instead
235 // of being copied. The right thing to do here is to call
236 // SetData() on the data object and set the value of this format
237 // to DROPEFFECTS_MOVE on this particular data object. But,
238 // since all the other clipboard formats follow the model of setting
239 // data on the data object only when the drop object calls GetData(),
240 // I am leaving this format's value hard coded in the data object.
241 // We can change this if other consumers of this format get added to this
242 // codebase and they need different values.
243 FORMATETC shortcutFE;
244 SET_FORMATETC(shortcutFE,
245 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0,
246 DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
247 dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
251 return NS_OK;
254 //-------------------------------------------------------------------------
255 NS_IMETHODIMP nsClipboard::SetNativeClipboardData(int32_t aWhichClipboard) {
256 if (aWhichClipboard != kGlobalClipboard) {
257 return NS_ERROR_FAILURE;
260 mIgnoreEmptyNotification = true;
262 // make sure we have a good transferable
263 if (nullptr == mTransferable) {
264 return NS_ERROR_FAILURE;
267 IDataObject* dataObj;
268 if (NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj,
269 nullptr))) { // this add refs dataObj
270 ::OleSetClipboard(dataObj);
271 dataObj->Release();
272 } else {
273 // Clear the native clipboard
274 ::OleSetClipboard(nullptr);
277 mIgnoreEmptyNotification = false;
279 return NS_OK;
282 //-------------------------------------------------------------------------
283 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
284 uint32_t* aLen) {
285 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose, ("%s", __FUNCTION__));
287 // Allocate a new memory buffer and copy the data from global memory.
288 // Recall that win98 allocates to nearest DWORD boundary. As a safety
289 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
290 // and null them out to ensure that all of our NS_strlen calls will succeed.
291 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
292 // a full NUL char16_t when |*aLen| is odd.
293 nsresult result = NS_ERROR_FAILURE;
294 if (aHGBL != nullptr) {
295 LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
296 CheckedInt<uint32_t> allocSize =
297 CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
298 if (!allocSize.isValid()) {
299 return NS_ERROR_INVALID_ARG;
301 char* data = static_cast<char*>(malloc(allocSize.value()));
302 if (data) {
303 uint32_t size = allocSize.value() - 3;
304 memcpy(data, lpStr, size);
305 // null terminate for safety
306 data[size] = data[size + 1] = data[size + 2] = '\0';
308 GlobalUnlock(aHGBL);
309 *aData = data;
310 *aLen = size;
312 result = NS_OK;
314 } else {
315 // We really shouldn't ever get here
316 // but just in case
317 *aData = nullptr;
318 *aLen = 0;
319 LPVOID lpMsgBuf;
321 FormatMessageW(
322 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
323 GetLastError(),
324 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
325 (LPWSTR)&lpMsgBuf, 0, nullptr);
327 // Display the string.
328 MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
329 MB_OK | MB_ICONINFORMATION);
331 // Free the buffer.
332 LocalFree(lpMsgBuf);
335 return result;
338 //-------------------------------------------------------------------------
339 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
340 UINT /*aIndex*/, UINT aFormat,
341 void** aData, uint32_t* aLen) {
342 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
343 ("%s: overload taking nsIWidget*.", __FUNCTION__));
345 HGLOBAL hglb;
346 nsresult result = NS_ERROR_FAILURE;
348 HWND nativeWin = nullptr;
349 if (::OpenClipboard(nativeWin)) {
350 hglb = ::GetClipboardData(aFormat);
351 result = GetGlobalData(hglb, aData, aLen);
352 ::CloseClipboard();
354 return result;
357 static void DisplayErrCode(HRESULT hres) {
358 if (hres == E_INVALIDARG) {
359 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
360 } else if (hres == E_UNEXPECTED) {
361 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
362 } else if (hres == E_OUTOFMEMORY) {
363 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
364 } else if (hres == DV_E_LINDEX) {
365 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
366 } else if (hres == DV_E_FORMATETC) {
367 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
368 } else if (hres == DV_E_TYMED) {
369 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
370 } else if (hres == DV_E_DVASPECT) {
371 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
372 } else if (hres == OLE_E_NOTRUNNING) {
373 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
374 } else if (hres == STG_E_MEDIUMFULL) {
375 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
376 } else if (hres == DV_E_CLIPFORMAT) {
377 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
378 } else if (hres == S_OK) {
379 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
380 } else {
381 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
382 ("****** DisplayErrCode 0x%X\n", hres));
386 //-------------------------------------------------------------------------
387 static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
388 LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) {
389 SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
391 // Starting by querying for the data to see if we can get it as from global
392 // memory
393 HRESULT hres = S_FALSE;
394 hres = aDataObject->QueryGetData(pFE);
395 DisplayErrCode(hres);
396 if (S_OK == hres) {
397 hres = aDataObject->GetData(pFE, pSTM);
398 DisplayErrCode(hres);
400 return hres;
403 //-------------------------------------------------------------------------
404 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
405 // an image encoder (e.g. image/png).
406 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
407 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
408 UINT aIndex, UINT aFormat,
409 const char* aMIMEImageFormat,
410 void** aData, uint32_t* aLen) {
411 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
412 ("%s: overload taking IDataObject*.", __FUNCTION__));
414 nsresult result = NS_ERROR_FAILURE;
415 *aData = nullptr;
416 *aLen = 0;
418 if (!aDataObject) {
419 return result;
422 UINT format = aFormat;
423 HRESULT hres = S_FALSE;
425 // XXX at the moment we only support global memory transfers
426 // It is here where we will add support for native images
427 // and IStream
428 FORMATETC fe;
429 STGMEDIUM stm;
430 hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
432 // Currently this is only handling TYMED_HGLOBAL data
433 // For Text, Dibs, Files, and generic data (like HTML)
434 if (S_OK == hres) {
435 static CLIPFORMAT fileDescriptorFlavorA =
436 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
437 static CLIPFORMAT fileDescriptorFlavorW =
438 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
439 static CLIPFORMAT fileFlavor =
440 ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
441 static CLIPFORMAT preferredDropEffect =
442 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
444 switch (stm.tymed) {
445 case TYMED_HGLOBAL: {
446 switch (fe.cfFormat) {
447 case CF_TEXT: {
448 // Get the data out of the global data handle. The size we
449 // return should not include the null because the other
450 // platforms don't use nulls, so just return the length we get
451 // back from strlen(), since we know CF_TEXT is null
452 // terminated. Recall that GetGlobalData() returns the size of
453 // the allocated buffer, not the size of the data (on 98, these
454 // are not the same) so we can't use that.
455 uint32_t allocLen = 0;
456 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
457 *aLen = strlen(reinterpret_cast<char*>(*aData));
458 result = NS_OK;
460 } break;
462 case CF_UNICODETEXT: {
463 // Get the data out of the global data handle. The size we
464 // return should not include the null because the other
465 // platforms don't use nulls, so just return the length we get
466 // back from strlen(), since we know CF_UNICODETEXT is null
467 // terminated. Recall that GetGlobalData() returns the size of
468 // the allocated buffer, not the size of the data (on 98, these
469 // are not the same) so we can't use that.
470 uint32_t allocLen = 0;
471 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
472 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
473 result = NS_OK;
475 } break;
477 case CF_DIBV5:
478 if (aMIMEImageFormat) {
479 uint32_t allocLen = 0;
480 const char* clipboardData;
481 if (NS_SUCCEEDED(GetGlobalData(
482 stm.hGlobal, (void**)&clipboardData, &allocLen))) {
483 nsCOMPtr<imgIContainer> container;
484 nsCOMPtr<imgITools> imgTools =
485 do_CreateInstance("@mozilla.org/image/tools;1");
486 result = imgTools->DecodeImageFromBuffer(
487 clipboardData, allocLen,
488 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
489 getter_AddRefs(container));
490 if (NS_FAILED(result)) {
491 break;
494 nsAutoCString mimeType;
495 if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
496 mimeType.Assign(IMAGE_JPEG);
497 } else {
498 mimeType.Assign(aMIMEImageFormat);
501 nsCOMPtr<nsIInputStream> inputStream;
502 result = imgTools->EncodeImage(container, mimeType, u""_ns,
503 getter_AddRefs(inputStream));
504 if (NS_FAILED(result)) {
505 break;
508 if (!inputStream) {
509 result = NS_ERROR_FAILURE;
510 break;
513 *aData = inputStream.forget().take();
514 *aLen = sizeof(nsIInputStream*);
517 break;
519 case CF_HDROP: {
520 // in the case of a file drop, multiple files are stashed within a
521 // single data object. In order to match mozilla's D&D apis, we
522 // just pull out the file at the requested index, pretending as
523 // if there really are multiple drag items.
524 HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
526 UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
527 NS_ASSERTION(numFiles > 0,
528 "File drop flavor, but no files...hmmmm");
529 NS_ASSERTION(aIndex < numFiles,
530 "Asked for a file index out of range of list");
531 if (numFiles > 0) {
532 UINT fileNameLen =
533 ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
534 wchar_t* buffer = reinterpret_cast<wchar_t*>(
535 moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
536 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
537 *aData = buffer;
538 *aLen = fileNameLen * sizeof(char16_t);
539 result = NS_OK;
541 GlobalUnlock(stm.hGlobal);
543 } break;
545 default: {
546 if (fe.cfFormat == fileDescriptorFlavorA ||
547 fe.cfFormat == fileDescriptorFlavorW ||
548 fe.cfFormat == fileFlavor) {
549 NS_WARNING(
550 "Mozilla doesn't yet understand how to read this type of "
551 "file flavor");
552 } else {
553 // Get the data out of the global data handle. The size we
554 // return should not include the null because the other
555 // platforms don't use nulls, so just return the length we get
556 // back from strlen(), since we know CF_UNICODETEXT is null
557 // terminated. Recall that GetGlobalData() returns the size of
558 // the allocated buffer, not the size of the data (on 98, these
559 // are not the same) so we can't use that.
561 // NOTE: we are assuming that anything that falls into this
562 // default case is unicode. As we start to get more
563 // kinds of binary data, this may become an incorrect
564 // assumption. Stay tuned.
565 uint32_t allocLen = 0;
566 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
567 if (fe.cfFormat == GetHtmlClipboardFormat()) {
568 // CF_HTML is actually UTF8, not unicode, so disregard the
569 // assumption above. We have to check the header for the
570 // actual length, and we'll do that in FindPlatformHTML().
571 // For now, return the allocLen. This case is mostly to
572 // ensure we don't try to call strlen on the buffer.
573 *aLen = allocLen;
574 } else if (fe.cfFormat == GetCustomClipboardFormat()) {
575 // Binary data
576 *aLen = allocLen;
577 } else if (fe.cfFormat == preferredDropEffect) {
578 // As per the MSDN doc entitled: "Shell Clipboard Formats"
579 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
580 // Reference:
581 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
582 NS_ASSERTION(
583 allocLen == sizeof(DWORD),
584 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
585 *aLen = allocLen;
586 } else {
587 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
588 sizeof(char16_t);
590 result = NS_OK;
593 } break;
594 } // switch
595 } break;
597 case TYMED_GDI: {
598 #ifdef DEBUG
599 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
600 ("*********************** TYMED_GDI\n"));
601 #endif
602 } break;
604 default:
605 break;
606 } // switch
608 ReleaseStgMedium(&stm);
611 return result;
614 //-------------------------------------------------------------------------
615 nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
616 UINT anIndex, nsIWidget* aWindow,
617 nsITransferable* aTransferable) {
618 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
620 // make sure we have a good transferable
621 if (!aTransferable) {
622 return NS_ERROR_INVALID_ARG;
625 nsresult res = NS_ERROR_FAILURE;
627 // get flavor list that includes all flavors that can be written (including
628 // ones obtained through conversion)
629 nsTArray<nsCString> flavors;
630 res = aTransferable->FlavorsTransferableCanImport(flavors);
631 if (NS_FAILED(res)) {
632 return NS_ERROR_FAILURE;
635 // Walk through flavors and see which flavor is on the clipboard them on the
636 // native clipboard,
637 for (uint32_t i = 0; i < flavors.Length(); i++) {
638 nsCString& flavorStr = flavors[i];
639 UINT format = GetFormat(flavorStr.get());
641 // Try to get the data using the desired flavor. This might fail, but all is
642 // not lost.
643 void* data = nullptr;
644 uint32_t dataLen = 0;
645 bool dataFound = false;
646 if (nullptr != aDataObject) {
647 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
648 flavorStr.get(), &data,
649 &dataLen))) {
650 dataFound = true;
652 } else if (nullptr != aWindow) {
653 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
654 &data, &dataLen))) {
655 dataFound = true;
659 // This is our second chance to try to find some data, having not found it
660 // when directly asking for the flavor. Let's try digging around in other
661 // flavors to help satisfy our craving for data.
662 if (!dataFound) {
663 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
664 dataFound =
665 FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
666 } else if (flavorStr.EqualsLiteral(kURLMime)) {
667 // drags from other windows apps expose the native
668 // CFSTR_INETURL{A,W} flavor
669 dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
670 if (!dataFound) {
671 dataFound =
672 FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
675 } // if we try one last ditch effort to find our data
677 // Hopefully by this point we've found it and can go about our business
678 if (dataFound) {
679 nsCOMPtr<nsISupports> genericDataWrapper;
680 if (flavorStr.EqualsLiteral(kFileMime)) {
681 // we have a file path in |data|. Create an nsLocalFile object.
682 nsDependentString filepath(reinterpret_cast<char16_t*>(data));
683 nsCOMPtr<nsIFile> file;
684 if (NS_SUCCEEDED(
685 NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
686 genericDataWrapper = do_QueryInterface(file);
688 free(data);
689 } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
690 uint32_t dummy;
691 // the editor folks want CF_HTML exactly as it's on the clipboard, no
692 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
693 // a wrapper and hand it back to them.
694 if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
695 nsPrimitiveHelpers::CreatePrimitiveForData(
696 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
697 } else {
698 free(data);
699 continue; // something wrong with this flavor, keep looking for other
700 // data
702 free(data);
703 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
704 uint32_t startOfData = 0;
705 // The JS folks want CF_HTML exactly as it is on the clipboard, but
706 // minus the CF_HTML header index information.
707 // It also needs to be converted to UTF16 and have linebreaks changed.
708 if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
709 &dataLen)) {
710 dataLen -= startOfData;
711 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
712 static_cast<char*>(data) + startOfData, &dataLen,
713 getter_AddRefs(genericDataWrapper));
714 } else {
715 free(data);
716 continue; // something wrong with this flavor, keep looking for other
717 // data
719 free(data);
720 } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
721 flavorStr.EqualsLiteral(kJPGImageMime) ||
722 flavorStr.EqualsLiteral(kPNGImageMime)) {
723 nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
724 genericDataWrapper = do_QueryInterface(imageStream);
725 NS_IF_RELEASE(imageStream);
726 } else {
727 // Treat custom types as a string of bytes.
728 if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
729 // we probably have some form of text. The DOM only wants LF, so
730 // convert from Win32 line endings to DOM line endings.
731 int32_t signedLen = static_cast<int32_t>(dataLen);
732 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &data,
733 &signedLen);
734 dataLen = signedLen;
736 if (flavorStr.EqualsLiteral(kRTFMime)) {
737 // RTF on Windows is known to sometimes deliver an extra null byte.
738 if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
739 dataLen--;
744 nsPrimitiveHelpers::CreatePrimitiveForData(
745 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
746 free(data);
749 NS_ASSERTION(genericDataWrapper,
750 "About to put null data into the transferable");
751 aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
752 res = NS_OK;
754 // we found one, get out of the loop
755 break;
757 } // foreach flavor
759 return res;
763 // FindPlatformHTML
765 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
766 // as-is.
768 bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
769 void** outData, uint32_t* outStartOfData,
770 uint32_t* outDataLen) {
771 // Reference: MSDN doc entitled "HTML Clipboard Format"
772 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
773 // CF_HTML is UTF8, not unicode. We also can't rely on it being
774 // null-terminated so we have to check the CF_HTML header for the correct
775 // length. The length we return is the bytecount from the beginning of the
776 // selected data to the end of the selected data, without the null
777 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
779 if (!outData || !*outData) {
780 return false;
783 char version[8] = {0};
784 int32_t startOfData = 0;
785 int32_t endOfData = 0;
786 int numFound =
787 sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
788 &startOfData, &endOfData);
790 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
791 return false;
794 // Fixup the start and end markers if they have no context (set to -1)
795 if (startOfData == -1) {
796 startOfData = 0;
798 if (endOfData == -1) {
799 endOfData = *outDataLen;
802 // Make sure we were passed sane values within our buffer size.
803 // (Note that we've handled all cases of negative endOfData above, so we can
804 // safely cast it to be unsigned here.)
805 if (!endOfData || startOfData >= endOfData ||
806 static_cast<uint32_t>(endOfData) > *outDataLen) {
807 return false;
810 // We want to return the buffer not offset by startOfData because it will be
811 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
812 // in CF_HTML format.
814 // We return the byte offset from the start of the data buffer to where the
815 // HTML data starts. The caller might want to extract the HTML only.
816 *outStartOfData = startOfData;
817 *outDataLen = endOfData;
818 return true;
822 // FindUnicodeFromPlainText
824 // we are looking for text/unicode and we failed to find it on the clipboard
825 // first, try again with text/plain. If that is present, convert it to unicode.
827 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
828 UINT inIndex, void** outData,
829 uint32_t* outDataLen) {
830 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
832 // we are looking for text/unicode and we failed to find it on the clipboard
833 // first, try again with text/plain. If that is present, convert it to
834 // unicode.
835 nsresult rv =
836 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime),
837 nullptr, outData, outDataLen);
838 if (NS_FAILED(rv) || !*outData) {
839 return false;
842 const char* castedText = static_cast<char*>(*outData);
843 nsAutoString tmp;
844 rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
845 tmp);
846 if (NS_FAILED(rv)) {
847 return false;
850 // out with the old, in with the new
851 free(*outData);
852 *outData = ToNewUnicode(tmp);
853 *outDataLen = tmp.Length() * sizeof(char16_t);
855 return true;
857 } // FindUnicodeFromPlainText
860 // FindURLFromLocalFile
862 // we are looking for a URL and couldn't find it, try again with looking for
863 // a local file. If we have one, it may either be a normal file or an internet
864 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
865 // in the local file case).
867 bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
868 void** outData, uint32_t* outDataLen) {
869 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
871 bool dataFound = false;
873 nsresult loadResult =
874 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
875 nullptr, outData, outDataLen);
876 if (NS_SUCCEEDED(loadResult) && *outData) {
877 // we have a file path in |data|. Is it an internet shortcut or a normal
878 // file?
879 const nsDependentString filepath(static_cast<char16_t*>(*outData));
880 nsCOMPtr<nsIFile> file;
881 nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
882 if (NS_FAILED(rv)) {
883 free(*outData);
884 return dataFound;
887 if (IsInternetShortcut(filepath)) {
888 free(*outData);
889 nsAutoCString url;
890 ResolveShortcut(file, url);
891 if (!url.IsEmpty()) {
892 // convert it to unicode and pass it out
893 NS_ConvertUTF8toUTF16 urlString(url);
894 // the internal mozilla URL format, text/x-moz-url, contains
895 // URL\ntitle. We can guess the title from the file's name.
896 nsAutoString title;
897 file->GetLeafName(title);
898 // We rely on IsInternetShortcut check that file has a .url extension.
899 title.SetLength(title.Length() - 4);
900 if (title.IsEmpty()) {
901 title = urlString;
903 *outData = ToNewUnicode(urlString + u"\n"_ns + title);
904 *outDataLen =
905 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
907 dataFound = true;
909 } else {
910 // we have a normal file, use some Necko objects to get our file path
911 nsAutoCString urlSpec;
912 NS_GetURLSpecFromFile(file, urlSpec);
914 // convert it to unicode and pass it out
915 free(*outData);
916 *outData = UTF8ToNewUnicode(urlSpec);
917 *outDataLen =
918 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
919 dataFound = true;
920 } // else regular file
923 return dataFound;
924 } // FindURLFromLocalFile
927 // FindURLFromNativeURL
929 // we are looking for a URL and couldn't find it using our internal
930 // URL flavor, so look for it using the native URL flavor,
931 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
933 bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
934 void** outData, uint32_t* outDataLen) {
935 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
937 bool dataFound = false;
939 void* tempOutData = nullptr;
940 uint32_t tempDataLen = 0;
942 nsresult loadResult = GetNativeDataOffClipboard(
943 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
944 &tempOutData, &tempDataLen);
945 if (NS_SUCCEEDED(loadResult) && tempOutData) {
946 nsDependentString urlString(static_cast<char16_t*>(tempOutData));
947 // the internal mozilla URL format, text/x-moz-url, contains
948 // URL\ntitle. Since we don't actually have a title here,
949 // just repeat the URL to fake it.
950 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
951 *outDataLen =
952 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
953 free(tempOutData);
954 dataFound = true;
955 } else {
956 loadResult = GetNativeDataOffClipboard(
957 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
958 nullptr, &tempOutData, &tempDataLen);
959 if (NS_SUCCEEDED(loadResult) && tempOutData) {
960 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
961 // CF_TEXT which is by definition ANSI encoded.
962 nsCString urlUnescapedA;
963 bool unescaped =
964 NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
965 esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
967 nsString urlString;
968 if (unescaped) {
969 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
970 } else {
971 NS_CopyNativeToUnicode(
972 nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
973 urlString);
976 // the internal mozilla URL format, text/x-moz-url, contains
977 // URL\ntitle. Since we don't actually have a title here,
978 // just repeat the URL to fake it.
979 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
980 *outDataLen =
981 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
982 free(tempOutData);
983 dataFound = true;
987 return dataFound;
988 } // FindURLFromNativeURL
991 // ResolveShortcut
993 void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
994 nsCOMPtr<nsIFileProtocolHandler> fph;
995 nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
996 if (NS_FAILED(rv)) {
997 return;
1000 nsCOMPtr<nsIURI> uri;
1001 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
1002 if (NS_FAILED(rv)) {
1003 return;
1006 uri->GetSpec(outURL);
1007 } // ResolveShortcut
1010 // IsInternetShortcut
1012 // A file is an Internet Shortcut if it ends with .URL
1014 bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
1015 return StringEndsWith(inFileName, u".url"_ns,
1016 nsCaseInsensitiveStringComparator);
1017 } // IsInternetShortcut
1019 //-------------------------------------------------------------------------
1020 NS_IMETHODIMP
1021 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
1022 int32_t aWhichClipboard) {
1023 MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
1024 ("%s aWhichClipboard=%i", __FUNCTION__, aWhichClipboard));
1026 // make sure we have a good transferable
1027 if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
1028 return NS_ERROR_FAILURE;
1031 nsresult res;
1033 // This makes sure we can use the OLE functionality for the clipboard
1034 IDataObject* dataObj;
1035 if (S_OK == ::OleGetClipboard(&dataObj)) {
1036 // Use OLE IDataObject for clipboard operations
1037 MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose,
1038 ("%s: use OLE IDataObject.", __FUNCTION__));
1039 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1040 dataObj->Release();
1041 } else {
1042 // do it the old manual way
1043 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1045 return res;
1048 NS_IMETHODIMP
1049 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
1050 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1051 // clipboard to scan it. If we i) empty and then ii) set data, then the
1052 // 'set data' can sometimes fail with access denied becacuse another program
1053 // has the clipboard open. So to avoid this race condition for OpenClipboard
1054 // we do not empty the clipboard when we're setting it.
1055 if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
1056 OleSetClipboard(nullptr);
1058 return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
1061 //-------------------------------------------------------------------------
1062 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
1063 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1064 bool* _retval) {
1065 *_retval = false;
1066 if (aWhichClipboard != kGlobalClipboard) {
1067 return NS_OK;
1070 for (auto& flavor : aFlavorList) {
1071 #ifdef DEBUG
1072 if (flavor.EqualsLiteral(kTextMime)) {
1073 NS_WARNING(
1074 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
1075 "INSTEAD");
1077 #endif
1079 UINT format = GetFormat(flavor.get());
1080 if (IsClipboardFormatAvailable(format)) {
1081 *_retval = true;
1082 break;
1083 } else {
1084 // We haven't found the exact flavor the client asked for, but maybe we
1085 // can still find it from something else that's on the clipboard...
1086 if (flavor.EqualsLiteral(kUnicodeMime)) {
1087 // client asked for unicode and it wasn't present, check if we have
1088 // CF_TEXT. We'll handle the actual data substitution in the data
1089 // object.
1090 if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
1091 *_retval = true;
1092 break;
1098 return NS_OK;