Bug 1728955: part 1) Remove ancient compile time flags for Windows' Clipboard logging...
[gecko.git] / widget / windows / nsClipboard.cpp
blobddc46e9e135a0e56ad2ee6e3c6b58f9d9d2025a9
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 // Allocate a new memory buffer and copy the data from global memory.
286 // Recall that win98 allocates to nearest DWORD boundary. As a safety
287 // precaution, allocate an extra 3 bytes (but don't report them in |aLen|!)
288 // and null them out to ensure that all of our NS_strlen calls will succeed.
289 // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
290 // a full NUL char16_t when |*aLen| is odd.
291 nsresult result = NS_ERROR_FAILURE;
292 if (aHGBL != nullptr) {
293 LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
294 CheckedInt<uint32_t> allocSize =
295 CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
296 if (!allocSize.isValid()) {
297 return NS_ERROR_INVALID_ARG;
299 char* data = static_cast<char*>(malloc(allocSize.value()));
300 if (data) {
301 uint32_t size = allocSize.value() - 3;
302 memcpy(data, lpStr, size);
303 // null terminate for safety
304 data[size] = data[size + 1] = data[size + 2] = '\0';
306 GlobalUnlock(aHGBL);
307 *aData = data;
308 *aLen = size;
310 result = NS_OK;
312 } else {
313 // We really shouldn't ever get here
314 // but just in case
315 *aData = nullptr;
316 *aLen = 0;
317 LPVOID lpMsgBuf;
319 FormatMessageW(
320 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr,
321 GetLastError(),
322 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
323 (LPWSTR)&lpMsgBuf, 0, nullptr);
325 // Display the string.
326 MessageBoxW(nullptr, (LPCWSTR)lpMsgBuf, L"GetLastError",
327 MB_OK | MB_ICONINFORMATION);
329 // Free the buffer.
330 LocalFree(lpMsgBuf);
333 return result;
336 //-------------------------------------------------------------------------
337 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget* aWidget,
338 UINT /*aIndex*/, UINT aFormat,
339 void** aData, uint32_t* aLen) {
340 HGLOBAL hglb;
341 nsresult result = NS_ERROR_FAILURE;
343 HWND nativeWin = nullptr;
344 if (::OpenClipboard(nativeWin)) {
345 hglb = ::GetClipboardData(aFormat);
346 result = GetGlobalData(hglb, aData, aLen);
347 ::CloseClipboard();
349 return result;
352 static void DisplayErrCode(HRESULT hres) {
353 if (hres == E_INVALIDARG) {
354 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_INVALIDARG\n"));
355 } else if (hres == E_UNEXPECTED) {
356 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_UNEXPECTED\n"));
357 } else if (hres == E_OUTOFMEMORY) {
358 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("E_OUTOFMEMORY\n"));
359 } else if (hres == DV_E_LINDEX) {
360 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_LINDEX\n"));
361 } else if (hres == DV_E_FORMATETC) {
362 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_FORMATETC\n"));
363 } else if (hres == DV_E_TYMED) {
364 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_TYMED\n"));
365 } else if (hres == DV_E_DVASPECT) {
366 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_DVASPECT\n"));
367 } else if (hres == OLE_E_NOTRUNNING) {
368 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("OLE_E_NOTRUNNING\n"));
369 } else if (hres == STG_E_MEDIUMFULL) {
370 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("STG_E_MEDIUMFULL\n"));
371 } else if (hres == DV_E_CLIPFORMAT) {
372 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("DV_E_CLIPFORMAT\n"));
373 } else if (hres == S_OK) {
374 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info, ("S_OK\n"));
375 } else {
376 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
377 ("****** DisplayErrCode 0x%X\n", hres));
381 //-------------------------------------------------------------------------
382 static HRESULT FillSTGMedium(IDataObject* aDataObject, UINT aFormat,
383 LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) {
384 SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
386 // Starting by querying for the data to see if we can get it as from global
387 // memory
388 HRESULT hres = S_FALSE;
389 hres = aDataObject->QueryGetData(pFE);
390 DisplayErrCode(hres);
391 if (S_OK == hres) {
392 hres = aDataObject->GetData(pFE, pSTM);
393 DisplayErrCode(hres);
395 return hres;
398 //-------------------------------------------------------------------------
399 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
400 // an image encoder (e.g. image/png).
401 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
402 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject* aDataObject,
403 UINT aIndex, UINT aFormat,
404 const char* aMIMEImageFormat,
405 void** aData, uint32_t* aLen) {
406 nsresult result = NS_ERROR_FAILURE;
407 *aData = nullptr;
408 *aLen = 0;
410 if (!aDataObject) {
411 return result;
414 UINT format = aFormat;
415 HRESULT hres = S_FALSE;
417 // XXX at the moment we only support global memory transfers
418 // It is here where we will add support for native images
419 // and IStream
420 FORMATETC fe;
421 STGMEDIUM stm;
422 hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
424 // Currently this is only handling TYMED_HGLOBAL data
425 // For Text, Dibs, Files, and generic data (like HTML)
426 if (S_OK == hres) {
427 static CLIPFORMAT fileDescriptorFlavorA =
428 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA);
429 static CLIPFORMAT fileDescriptorFlavorW =
430 ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW);
431 static CLIPFORMAT fileFlavor =
432 ::RegisterClipboardFormat(CFSTR_FILECONTENTS);
433 static CLIPFORMAT preferredDropEffect =
434 ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
436 switch (stm.tymed) {
437 case TYMED_HGLOBAL: {
438 switch (fe.cfFormat) {
439 case CF_TEXT: {
440 // Get the data out of the global data handle. The size we
441 // return should not include the null because the other
442 // platforms don't use nulls, so just return the length we get
443 // back from strlen(), since we know CF_TEXT is null
444 // terminated. Recall that GetGlobalData() returns the size of
445 // the allocated buffer, not the size of the data (on 98, these
446 // are not the same) so we can't use that.
447 uint32_t allocLen = 0;
448 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
449 *aLen = strlen(reinterpret_cast<char*>(*aData));
450 result = NS_OK;
452 } break;
454 case CF_UNICODETEXT: {
455 // Get the data out of the global data handle. The size we
456 // return should not include the null because the other
457 // platforms don't use nulls, so just return the length we get
458 // back from strlen(), since we know CF_UNICODETEXT is null
459 // terminated. Recall that GetGlobalData() returns the size of
460 // the allocated buffer, not the size of the data (on 98, these
461 // are not the same) so we can't use that.
462 uint32_t allocLen = 0;
463 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
464 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) * 2;
465 result = NS_OK;
467 } break;
469 case CF_DIBV5:
470 if (aMIMEImageFormat) {
471 uint32_t allocLen = 0;
472 const char* clipboardData;
473 if (NS_SUCCEEDED(GetGlobalData(
474 stm.hGlobal, (void**)&clipboardData, &allocLen))) {
475 nsCOMPtr<imgIContainer> container;
476 nsCOMPtr<imgITools> imgTools =
477 do_CreateInstance("@mozilla.org/image/tools;1");
478 result = imgTools->DecodeImageFromBuffer(
479 clipboardData, allocLen,
480 nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
481 getter_AddRefs(container));
482 if (NS_FAILED(result)) {
483 break;
486 nsAutoCString mimeType;
487 if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
488 mimeType.Assign(IMAGE_JPEG);
489 } else {
490 mimeType.Assign(aMIMEImageFormat);
493 nsCOMPtr<nsIInputStream> inputStream;
494 result = imgTools->EncodeImage(container, mimeType, u""_ns,
495 getter_AddRefs(inputStream));
496 if (NS_FAILED(result)) {
497 break;
500 if (!inputStream) {
501 result = NS_ERROR_FAILURE;
502 break;
505 *aData = inputStream.forget().take();
506 *aLen = sizeof(nsIInputStream*);
509 break;
511 case CF_HDROP: {
512 // in the case of a file drop, multiple files are stashed within a
513 // single data object. In order to match mozilla's D&D apis, we
514 // just pull out the file at the requested index, pretending as
515 // if there really are multiple drag items.
516 HDROP dropFiles = (HDROP)GlobalLock(stm.hGlobal);
518 UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0);
519 NS_ASSERTION(numFiles > 0,
520 "File drop flavor, but no files...hmmmm");
521 NS_ASSERTION(aIndex < numFiles,
522 "Asked for a file index out of range of list");
523 if (numFiles > 0) {
524 UINT fileNameLen =
525 ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
526 wchar_t* buffer = reinterpret_cast<wchar_t*>(
527 moz_xmalloc((fileNameLen + 1) * sizeof(wchar_t)));
528 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
529 *aData = buffer;
530 *aLen = fileNameLen * sizeof(char16_t);
531 result = NS_OK;
533 GlobalUnlock(stm.hGlobal);
535 } break;
537 default: {
538 if (fe.cfFormat == fileDescriptorFlavorA ||
539 fe.cfFormat == fileDescriptorFlavorW ||
540 fe.cfFormat == fileFlavor) {
541 NS_WARNING(
542 "Mozilla doesn't yet understand how to read this type of "
543 "file flavor");
544 } else {
545 // Get the data out of the global data handle. The size we
546 // return should not include the null because the other
547 // platforms don't use nulls, so just return the length we get
548 // back from strlen(), since we know CF_UNICODETEXT is null
549 // terminated. Recall that GetGlobalData() returns the size of
550 // the allocated buffer, not the size of the data (on 98, these
551 // are not the same) so we can't use that.
553 // NOTE: we are assuming that anything that falls into this
554 // default case is unicode. As we start to get more
555 // kinds of binary data, this may become an incorrect
556 // assumption. Stay tuned.
557 uint32_t allocLen = 0;
558 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen))) {
559 if (fe.cfFormat == GetHtmlClipboardFormat()) {
560 // CF_HTML is actually UTF8, not unicode, so disregard the
561 // assumption above. We have to check the header for the
562 // actual length, and we'll do that in FindPlatformHTML().
563 // For now, return the allocLen. This case is mostly to
564 // ensure we don't try to call strlen on the buffer.
565 *aLen = allocLen;
566 } else if (fe.cfFormat == GetCustomClipboardFormat()) {
567 // Binary data
568 *aLen = allocLen;
569 } else if (fe.cfFormat == preferredDropEffect) {
570 // As per the MSDN doc entitled: "Shell Clipboard Formats"
571 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
572 // Reference:
573 // http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
574 NS_ASSERTION(
575 allocLen == sizeof(DWORD),
576 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
577 *aLen = allocLen;
578 } else {
579 *aLen = NS_strlen(reinterpret_cast<char16_t*>(*aData)) *
580 sizeof(char16_t);
582 result = NS_OK;
585 } break;
586 } // switch
587 } break;
589 case TYMED_GDI: {
590 #ifdef DEBUG
591 MOZ_LOG(gWin32ClipboardLog, LogLevel::Info,
592 ("*********************** TYMED_GDI\n"));
593 #endif
594 } break;
596 default:
597 break;
598 } // switch
600 ReleaseStgMedium(&stm);
603 return result;
606 //-------------------------------------------------------------------------
607 nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
608 UINT anIndex, nsIWidget* aWindow,
609 nsITransferable* aTransferable) {
610 // make sure we have a good transferable
611 if (!aTransferable) {
612 return NS_ERROR_INVALID_ARG;
615 nsresult res = NS_ERROR_FAILURE;
617 // get flavor list that includes all flavors that can be written (including
618 // ones obtained through conversion)
619 nsTArray<nsCString> flavors;
620 res = aTransferable->FlavorsTransferableCanImport(flavors);
621 if (NS_FAILED(res)) {
622 return NS_ERROR_FAILURE;
625 // Walk through flavors and see which flavor is on the clipboard them on the
626 // native clipboard,
627 for (uint32_t i = 0; i < flavors.Length(); i++) {
628 nsCString& flavorStr = flavors[i];
629 UINT format = GetFormat(flavorStr.get());
631 // Try to get the data using the desired flavor. This might fail, but all is
632 // not lost.
633 void* data = nullptr;
634 uint32_t dataLen = 0;
635 bool dataFound = false;
636 if (nullptr != aDataObject) {
637 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format,
638 flavorStr.get(), &data,
639 &dataLen))) {
640 dataFound = true;
642 } else if (nullptr != aWindow) {
643 if (NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format,
644 &data, &dataLen))) {
645 dataFound = true;
649 // This is our second chance to try to find some data, having not found it
650 // when directly asking for the flavor. Let's try digging around in other
651 // flavors to help satisfy our craving for data.
652 if (!dataFound) {
653 if (flavorStr.EqualsLiteral(kUnicodeMime)) {
654 dataFound =
655 FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
656 } else if (flavorStr.EqualsLiteral(kURLMime)) {
657 // drags from other windows apps expose the native
658 // CFSTR_INETURL{A,W} flavor
659 dataFound = FindURLFromNativeURL(aDataObject, anIndex, &data, &dataLen);
660 if (!dataFound) {
661 dataFound =
662 FindURLFromLocalFile(aDataObject, anIndex, &data, &dataLen);
665 } // if we try one last ditch effort to find our data
667 // Hopefully by this point we've found it and can go about our business
668 if (dataFound) {
669 nsCOMPtr<nsISupports> genericDataWrapper;
670 if (flavorStr.EqualsLiteral(kFileMime)) {
671 // we have a file path in |data|. Create an nsLocalFile object.
672 nsDependentString filepath(reinterpret_cast<char16_t*>(data));
673 nsCOMPtr<nsIFile> file;
674 if (NS_SUCCEEDED(
675 NS_NewLocalFile(filepath, false, getter_AddRefs(file)))) {
676 genericDataWrapper = do_QueryInterface(file);
678 free(data);
679 } else if (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
680 uint32_t dummy;
681 // the editor folks want CF_HTML exactly as it's on the clipboard, no
682 // conversions, no fancy stuff. Pull it off the clipboard, stuff it into
683 // a wrapper and hand it back to them.
684 if (FindPlatformHTML(aDataObject, anIndex, &data, &dummy, &dataLen)) {
685 nsPrimitiveHelpers::CreatePrimitiveForData(
686 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
687 } else {
688 free(data);
689 continue; // something wrong with this flavor, keep looking for other
690 // data
692 free(data);
693 } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
694 uint32_t startOfData = 0;
695 // The JS folks want CF_HTML exactly as it is on the clipboard, but
696 // minus the CF_HTML header index information.
697 // It also needs to be converted to UTF16 and have linebreaks changed.
698 if (FindPlatformHTML(aDataObject, anIndex, &data, &startOfData,
699 &dataLen)) {
700 dataLen -= startOfData;
701 nsPrimitiveHelpers::CreatePrimitiveForCFHTML(
702 static_cast<char*>(data) + startOfData, &dataLen,
703 getter_AddRefs(genericDataWrapper));
704 } else {
705 free(data);
706 continue; // something wrong with this flavor, keep looking for other
707 // data
709 free(data);
710 } else if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
711 flavorStr.EqualsLiteral(kJPGImageMime) ||
712 flavorStr.EqualsLiteral(kPNGImageMime)) {
713 nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
714 genericDataWrapper = do_QueryInterface(imageStream);
715 NS_IF_RELEASE(imageStream);
716 } else {
717 // Treat custom types as a string of bytes.
718 if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
719 // we probably have some form of text. The DOM only wants LF, so
720 // convert from Win32 line endings to DOM line endings.
721 int32_t signedLen = static_cast<int32_t>(dataLen);
722 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(flavorStr, &data,
723 &signedLen);
724 dataLen = signedLen;
726 if (flavorStr.EqualsLiteral(kRTFMime)) {
727 // RTF on Windows is known to sometimes deliver an extra null byte.
728 if (dataLen > 0 && static_cast<char*>(data)[dataLen - 1] == '\0') {
729 dataLen--;
734 nsPrimitiveHelpers::CreatePrimitiveForData(
735 flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper));
736 free(data);
739 NS_ASSERTION(genericDataWrapper,
740 "About to put null data into the transferable");
741 aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
742 res = NS_OK;
744 // we found one, get out of the loop
745 break;
747 } // foreach flavor
749 return res;
753 // FindPlatformHTML
755 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly
756 // as-is.
758 bool nsClipboard ::FindPlatformHTML(IDataObject* inDataObject, UINT inIndex,
759 void** outData, uint32_t* outStartOfData,
760 uint32_t* outDataLen) {
761 // Reference: MSDN doc entitled "HTML Clipboard Format"
762 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
763 // CF_HTML is UTF8, not unicode. We also can't rely on it being
764 // null-terminated so we have to check the CF_HTML header for the correct
765 // length. The length we return is the bytecount from the beginning of the
766 // selected data to the end of the selected data, without the null
767 // termination. Because it's UTF8, we're guaranteed the header is ASCII.
769 if (!outData || !*outData) {
770 return false;
773 char version[8] = {0};
774 int32_t startOfData = 0;
775 int32_t endOfData = 0;
776 int numFound =
777 sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", version,
778 &startOfData, &endOfData);
780 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
781 return false;
784 // Fixup the start and end markers if they have no context (set to -1)
785 if (startOfData == -1) {
786 startOfData = 0;
788 if (endOfData == -1) {
789 endOfData = *outDataLen;
792 // Make sure we were passed sane values within our buffer size.
793 // (Note that we've handled all cases of negative endOfData above, so we can
794 // safely cast it to be unsigned here.)
795 if (!endOfData || startOfData >= endOfData ||
796 static_cast<uint32_t>(endOfData) > *outDataLen) {
797 return false;
800 // We want to return the buffer not offset by startOfData because it will be
801 // parsed out later (probably by HTMLEditor::ParseCFHTML) when it is still
802 // in CF_HTML format.
804 // We return the byte offset from the start of the data buffer to where the
805 // HTML data starts. The caller might want to extract the HTML only.
806 *outStartOfData = startOfData;
807 *outDataLen = endOfData;
808 return true;
812 // FindUnicodeFromPlainText
814 // we are looking for text/unicode and we failed to find it on the clipboard
815 // first, try again with text/plain. If that is present, convert it to unicode.
817 bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
818 UINT inIndex, void** outData,
819 uint32_t* outDataLen) {
820 // we are looking for text/unicode and we failed to find it on the clipboard
821 // first, try again with text/plain. If that is present, convert it to
822 // unicode.
823 nsresult rv =
824 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime),
825 nullptr, outData, outDataLen);
826 if (NS_FAILED(rv) || !*outData) {
827 return false;
830 const char* castedText = static_cast<char*>(*outData);
831 nsAutoString tmp;
832 rv = NS_CopyNativeToUnicode(nsDependentCSubstring(castedText, *outDataLen),
833 tmp);
834 if (NS_FAILED(rv)) {
835 return false;
838 // out with the old, in with the new
839 free(*outData);
840 *outData = ToNewUnicode(tmp);
841 *outDataLen = tmp.Length() * sizeof(char16_t);
843 return true;
845 } // FindUnicodeFromPlainText
848 // FindURLFromLocalFile
850 // we are looking for a URL and couldn't find it, try again with looking for
851 // a local file. If we have one, it may either be a normal file or an internet
852 // shortcut. In both cases, however, we can get a URL (it will be a file:// url
853 // in the local file case).
855 bool nsClipboard ::FindURLFromLocalFile(IDataObject* inDataObject, UINT inIndex,
856 void** outData, uint32_t* outDataLen) {
857 bool dataFound = false;
859 nsresult loadResult =
860 GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime),
861 nullptr, outData, outDataLen);
862 if (NS_SUCCEEDED(loadResult) && *outData) {
863 // we have a file path in |data|. Is it an internet shortcut or a normal
864 // file?
865 const nsDependentString filepath(static_cast<char16_t*>(*outData));
866 nsCOMPtr<nsIFile> file;
867 nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
868 if (NS_FAILED(rv)) {
869 free(*outData);
870 return dataFound;
873 if (IsInternetShortcut(filepath)) {
874 free(*outData);
875 nsAutoCString url;
876 ResolveShortcut(file, url);
877 if (!url.IsEmpty()) {
878 // convert it to unicode and pass it out
879 NS_ConvertUTF8toUTF16 urlString(url);
880 // the internal mozilla URL format, text/x-moz-url, contains
881 // URL\ntitle. We can guess the title from the file's name.
882 nsAutoString title;
883 file->GetLeafName(title);
884 // We rely on IsInternetShortcut check that file has a .url extension.
885 title.SetLength(title.Length() - 4);
886 if (title.IsEmpty()) {
887 title = urlString;
889 *outData = ToNewUnicode(urlString + u"\n"_ns + title);
890 *outDataLen =
891 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
893 dataFound = true;
895 } else {
896 // we have a normal file, use some Necko objects to get our file path
897 nsAutoCString urlSpec;
898 NS_GetURLSpecFromFile(file, urlSpec);
900 // convert it to unicode and pass it out
901 free(*outData);
902 *outData = UTF8ToNewUnicode(urlSpec);
903 *outDataLen =
904 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
905 dataFound = true;
906 } // else regular file
909 return dataFound;
910 } // FindURLFromLocalFile
913 // FindURLFromNativeURL
915 // we are looking for a URL and couldn't find it using our internal
916 // URL flavor, so look for it using the native URL flavor,
917 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
919 bool nsClipboard ::FindURLFromNativeURL(IDataObject* inDataObject, UINT inIndex,
920 void** outData, uint32_t* outDataLen) {
921 bool dataFound = false;
923 void* tempOutData = nullptr;
924 uint32_t tempDataLen = 0;
926 nsresult loadResult = GetNativeDataOffClipboard(
927 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr,
928 &tempOutData, &tempDataLen);
929 if (NS_SUCCEEDED(loadResult) && tempOutData) {
930 nsDependentString urlString(static_cast<char16_t*>(tempOutData));
931 // the internal mozilla URL format, text/x-moz-url, contains
932 // URL\ntitle. Since we don't actually have a title here,
933 // just repeat the URL to fake it.
934 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
935 *outDataLen =
936 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
937 free(tempOutData);
938 dataFound = true;
939 } else {
940 loadResult = GetNativeDataOffClipboard(
941 inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA),
942 nullptr, &tempOutData, &tempDataLen);
943 if (NS_SUCCEEDED(loadResult) && tempOutData) {
944 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to
945 // CF_TEXT which is by definition ANSI encoded.
946 nsCString urlUnescapedA;
947 bool unescaped =
948 NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen,
949 esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
951 nsString urlString;
952 if (unescaped) {
953 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
954 } else {
955 NS_CopyNativeToUnicode(
956 nsDependentCString(static_cast<char*>(tempOutData), tempDataLen),
957 urlString);
960 // the internal mozilla URL format, text/x-moz-url, contains
961 // URL\ntitle. Since we don't actually have a title here,
962 // just repeat the URL to fake it.
963 *outData = ToNewUnicode(urlString + u"\n"_ns + urlString);
964 *outDataLen =
965 NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
966 free(tempOutData);
967 dataFound = true;
971 return dataFound;
972 } // FindURLFromNativeURL
975 // ResolveShortcut
977 void nsClipboard ::ResolveShortcut(nsIFile* aFile, nsACString& outURL) {
978 nsCOMPtr<nsIFileProtocolHandler> fph;
979 nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
980 if (NS_FAILED(rv)) {
981 return;
984 nsCOMPtr<nsIURI> uri;
985 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
986 if (NS_FAILED(rv)) {
987 return;
990 uri->GetSpec(outURL);
991 } // ResolveShortcut
994 // IsInternetShortcut
996 // A file is an Internet Shortcut if it ends with .URL
998 bool nsClipboard ::IsInternetShortcut(const nsAString& inFileName) {
999 return StringEndsWith(inFileName, u".url"_ns,
1000 nsCaseInsensitiveStringComparator);
1001 } // IsInternetShortcut
1003 //-------------------------------------------------------------------------
1004 NS_IMETHODIMP
1005 nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
1006 int32_t aWhichClipboard) {
1007 // make sure we have a good transferable
1008 if (!aTransferable || aWhichClipboard != kGlobalClipboard) {
1009 return NS_ERROR_FAILURE;
1012 nsresult res;
1014 // This makes sure we can use the OLE functionality for the clipboard
1015 IDataObject* dataObj;
1016 if (S_OK == ::OleGetClipboard(&dataObj)) {
1017 // Use OLE IDataObject for clipboard operations
1018 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
1019 dataObj->Release();
1020 } else {
1021 // do it the old manual way
1022 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
1024 return res;
1027 NS_IMETHODIMP
1028 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
1029 // Some programs such as ZoneAlarm monitor clipboard usage and then open the
1030 // clipboard to scan it. If we i) empty and then ii) set data, then the
1031 // 'set data' can sometimes fail with access denied becacuse another program
1032 // has the clipboard open. So to avoid this race condition for OpenClipboard
1033 // we do not empty the clipboard when we're setting it.
1034 if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) {
1035 OleSetClipboard(nullptr);
1037 return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
1040 //-------------------------------------------------------------------------
1041 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
1042 const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
1043 bool* _retval) {
1044 *_retval = false;
1045 if (aWhichClipboard != kGlobalClipboard) {
1046 return NS_OK;
1049 for (auto& flavor : aFlavorList) {
1050 #ifdef DEBUG
1051 if (flavor.EqualsLiteral(kTextMime)) {
1052 NS_WARNING(
1053 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
1054 "INSTEAD");
1056 #endif
1058 UINT format = GetFormat(flavor.get());
1059 if (IsClipboardFormatAvailable(format)) {
1060 *_retval = true;
1061 break;
1062 } else {
1063 // We haven't found the exact flavor the client asked for, but maybe we
1064 // can still find it from something else that's on the clipboard...
1065 if (flavor.EqualsLiteral(kUnicodeMime)) {
1066 // client asked for unicode and it wasn't present, check if we have
1067 // CF_TEXT. We'll handle the actual data substitution in the data
1068 // object.
1069 if (IsClipboardFormatAvailable(GetFormat(kTextMime))) {
1070 *_retval = true;
1071 break;
1077 return NS_OK;