Bug 867104 - Add a crashtest. r=ehsan
[gecko.git] / widget / windows / nsClipboard.cpp
blob73b2b58f1bbadc6570222926ac2952dddf94498a
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 "nsCOMPtr.h"
15 #include "nsDataObj.h"
16 #include "nsIClipboardOwner.h"
17 #include "nsString.h"
18 #include "nsNativeCharsetUtils.h"
19 #include "nsIFormatConverter.h"
20 #include "nsITransferable.h"
21 #include "nsCOMPtr.h"
22 #include "nsXPCOM.h"
23 #include "nsISupportsPrimitives.h"
24 #include "nsXPIDLString.h"
25 #include "nsReadableUtils.h"
26 #include "nsUnicharUtils.h"
27 #include "nsPrimitiveHelpers.h"
28 #include "nsImageClipboard.h"
29 #include "nsIWidget.h"
30 #include "nsIComponentManager.h"
31 #include "nsWidgetsCID.h"
32 #include "nsCRT.h"
33 #include "nsNetUtil.h"
34 #include "nsEscape.h"
36 #ifdef PR_LOGGING
37 PRLogModuleInfo* gWin32ClipboardLog = nullptr;
38 #endif
40 // oddly, this isn't in the MSVC headers anywhere.
41 UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format");
44 //-------------------------------------------------------------------------
46 // nsClipboard constructor
48 //-------------------------------------------------------------------------
49 nsClipboard::nsClipboard() : nsBaseClipboard()
51 #ifdef PR_LOGGING
52 if (!gWin32ClipboardLog) {
53 gWin32ClipboardLog = PR_NewLogModule("nsClipboard");
55 #endif
57 mIgnoreEmptyNotification = false;
58 mWindow = nullptr;
61 //-------------------------------------------------------------------------
62 // nsClipboard destructor
63 //-------------------------------------------------------------------------
64 nsClipboard::~nsClipboard()
69 //-------------------------------------------------------------------------
70 UINT nsClipboard::GetFormat(const char* aMimeStr)
72 UINT format;
74 if (strcmp(aMimeStr, kTextMime) == 0)
75 format = CF_TEXT;
76 else if (strcmp(aMimeStr, kUnicodeMime) == 0)
77 format = CF_UNICODETEXT;
78 else if (strcmp(aMimeStr, kJPEGImageMime) == 0 ||
79 strcmp(aMimeStr, kJPGImageMime) == 0 ||
80 strcmp(aMimeStr, kPNGImageMime) == 0)
81 format = CF_DIBV5;
82 else if (strcmp(aMimeStr, kFileMime) == 0 ||
83 strcmp(aMimeStr, kFilePromiseMime) == 0)
84 format = CF_HDROP;
85 else if (strcmp(aMimeStr, kNativeHTMLMime) == 0)
86 format = CF_HTML;
87 else
88 format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
90 return format;
93 //-------------------------------------------------------------------------
94 nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, IDataObject ** aDataObj, nsIURI * uri)
96 if (nullptr == aTransferable) {
97 return NS_ERROR_FAILURE;
100 // Create our native DataObject that implements
101 // the OLE IDataObject interface
102 nsDataObj * dataObj = new nsDataObj(uri);
104 if (!dataObj)
105 return NS_ERROR_OUT_OF_MEMORY;
107 dataObj->AddRef();
109 // Now set it up with all the right data flavors & enums
110 nsresult res = SetupNativeDataObject(aTransferable, dataObj);
111 if (NS_OK == res) {
112 *aDataObj = dataObj;
113 } else {
114 delete dataObj;
116 return res;
119 //-------------------------------------------------------------------------
120 nsresult nsClipboard::SetupNativeDataObject(nsITransferable * aTransferable, IDataObject * aDataObj)
122 if (nullptr == aTransferable || nullptr == aDataObj) {
123 return NS_ERROR_FAILURE;
126 nsDataObj * dObj = static_cast<nsDataObj *>(aDataObj);
128 // Now give the Transferable to the DataObject
129 // for getting the data out of it
130 dObj->SetTransferable(aTransferable);
132 // Get the transferable list of data flavors
133 nsCOMPtr<nsISupportsArray> dfList;
134 aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList));
136 // Walk through flavors that contain data and register them
137 // into the DataObj as supported flavors
138 uint32_t i;
139 uint32_t cnt;
140 dfList->Count(&cnt);
141 for (i=0;i<cnt;i++) {
142 nsCOMPtr<nsISupports> genericFlavor;
143 dfList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
144 nsCOMPtr<nsISupportsCString> currentFlavor ( do_QueryInterface(genericFlavor) );
145 if ( currentFlavor ) {
146 nsXPIDLCString flavorStr;
147 currentFlavor->ToString(getter_Copies(flavorStr));
148 UINT format = GetFormat(flavorStr);
150 // Now tell the native IDataObject about both our mime type and
151 // the native data format
152 FORMATETC fe;
153 SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
154 dObj->AddDataFlavor(flavorStr, &fe);
156 // Do various things internal to the implementation, like map one
157 // flavor to another or add additional flavors based on what's required
158 // for the win32 impl.
159 if ( strcmp(flavorStr, kUnicodeMime) == 0 ) {
160 // if we find text/unicode, also advertise text/plain (which we will convert
161 // on our own in nsDataObj::GetText().
162 FORMATETC textFE;
163 SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
164 dObj->AddDataFlavor(kTextMime, &textFE);
166 else if ( strcmp(flavorStr, kHTMLMime) == 0 ) {
167 // if we find text/html, also advertise win32's html flavor (which we will convert
168 // on our own in nsDataObj::GetText().
169 FORMATETC htmlFE;
170 SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
171 dObj->AddDataFlavor(kHTMLMime, &htmlFE);
173 else if ( strcmp(flavorStr, kURLMime) == 0 ) {
174 // if we're a url, in addition to also being text, we need to register
175 // the "file" flavors so that the win32 shell knows to create an internet
176 // shortcut when it sees one of these beasts.
177 FORMATETC shortcutFE;
178 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
179 dObj->AddDataFlavor(kURLMime, &shortcutFE);
180 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
181 dObj->AddDataFlavor(kURLMime, &shortcutFE);
182 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
183 dObj->AddDataFlavor(kURLMime, &shortcutFE);
184 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
185 dObj->AddDataFlavor(kURLMime, &shortcutFE);
186 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
187 dObj->AddDataFlavor(kURLMime, &shortcutFE);
189 else if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 ||
190 strcmp(flavorStr, kJPGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 ||
191 strcmp(flavorStr, kNativeImageMime) == 0 ) {
192 // if we're an image, register the native bitmap flavor
193 FORMATETC imageFE;
194 // Add DIBv5
195 SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
196 dObj->AddDataFlavor(flavorStr, &imageFE);
197 // Add DIBv3
198 SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
199 dObj->AddDataFlavor(flavorStr, &imageFE);
201 else if ( strcmp(flavorStr, kFilePromiseMime) == 0 ) {
202 // if we're a file promise flavor, also register the
203 // CFSTR_PREFERREDDROPEFFECT format. The data object
204 // returns a value of DROPEFFECTS_MOVE to the drop target
205 // when it asks for the value of this format. This causes
206 // the file to be moved from the temporary location instead
207 // of being copied. The right thing to do here is to call
208 // SetData() on the data object and set the value of this format
209 // to DROPEFFECTS_MOVE on this particular data object. But,
210 // since all the other clipboard formats follow the model of setting
211 // data on the data object only when the drop object calls GetData(),
212 // I am leaving this format's value hard coded in the data object.
213 // We can change this if other consumers of this format get added to this
214 // codebase and they need different values.
215 FORMATETC shortcutFE;
216 SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
217 dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE);
222 return NS_OK;
225 //-------------------------------------------------------------------------
226 NS_IMETHODIMP nsClipboard::SetNativeClipboardData ( int32_t aWhichClipboard )
228 if ( aWhichClipboard != kGlobalClipboard )
229 return NS_ERROR_FAILURE;
231 mIgnoreEmptyNotification = true;
233 // make sure we have a good transferable
234 if (nullptr == mTransferable) {
235 return NS_ERROR_FAILURE;
238 IDataObject * dataObj;
239 if ( NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj, NULL)) ) { // this add refs dataObj
240 ::OleSetClipboard(dataObj);
241 dataObj->Release();
242 } else {
243 // Clear the native clipboard
244 ::OleSetClipboard(NULL);
247 mIgnoreEmptyNotification = false;
249 return NS_OK;
253 //-------------------------------------------------------------------------
254 nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void ** aData, uint32_t * aLen)
256 // Allocate a new memory buffer and copy the data from global memory.
257 // Recall that win98 allocates to nearest DWORD boundary. As a safety
258 // precaution, allocate an extra 2 bytes (but don't report them!) and
259 // null them out to ensure that all of our strlen calls will succeed.
260 nsresult result = NS_ERROR_FAILURE;
261 if (aHGBL != NULL) {
262 LPSTR lpStr = (LPSTR) GlobalLock(aHGBL);
263 DWORD allocSize = GlobalSize(aHGBL);
264 char* data = static_cast<char*>(nsMemory::Alloc(allocSize + sizeof(PRUnichar)));
265 if ( data ) {
266 memcpy ( data, lpStr, allocSize );
267 data[allocSize] = data[allocSize + 1] = '\0'; // null terminate for safety
269 GlobalUnlock(aHGBL);
270 *aData = data;
271 *aLen = allocSize;
273 result = NS_OK;
275 } else {
276 #ifdef MOZ_METRO
277 return result;
278 #endif
279 // We really shouldn't ever get here
280 // but just in case
281 *aData = nullptr;
282 *aLen = 0;
283 LPVOID lpMsgBuf;
285 FormatMessageW(
286 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
287 NULL,
288 GetLastError(),
289 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
290 (LPWSTR) &lpMsgBuf,
292 NULL
295 // Display the string.
296 MessageBoxW( NULL, (LPCWSTR) lpMsgBuf, L"GetLastError", MB_OK|MB_ICONINFORMATION );
298 // Free the buffer.
299 LocalFree( lpMsgBuf );
302 return result;
305 //-------------------------------------------------------------------------
306 nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget * aWidget, UINT /*aIndex*/, UINT aFormat, void ** aData, uint32_t * aLen)
308 HGLOBAL hglb;
309 nsresult result = NS_ERROR_FAILURE;
311 HWND nativeWin = nullptr;
312 if (::OpenClipboard(nativeWin)) {
313 hglb = ::GetClipboardData(aFormat);
314 result = GetGlobalData(hglb, aData, aLen);
315 ::CloseClipboard();
317 return result;
320 static void DisplayErrCode(HRESULT hres)
322 #if defined(DEBUG_rods) || defined(DEBUG_pinkerton)
323 if (hres == E_INVALIDARG) {
324 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_INVALIDARG\n"));
325 } else
326 if (hres == E_UNEXPECTED) {
327 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_UNEXPECTED\n"));
328 } else
329 if (hres == E_OUTOFMEMORY) {
330 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_OUTOFMEMORY\n"));
331 } else
332 if (hres == DV_E_LINDEX ) {
333 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_LINDEX\n"));
334 } else
335 if (hres == DV_E_FORMATETC) {
336 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_FORMATETC\n"));
337 } else
338 if (hres == DV_E_TYMED) {
339 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_TYMED\n"));
340 } else
341 if (hres == DV_E_DVASPECT) {
342 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_DVASPECT\n"));
343 } else
344 if (hres == OLE_E_NOTRUNNING) {
345 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("OLE_E_NOTRUNNING\n"));
346 } else
347 if (hres == STG_E_MEDIUMFULL) {
348 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("STG_E_MEDIUMFULL\n"));
349 } else
350 if (hres == DV_E_CLIPFORMAT) {
351 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_CLIPFORMAT\n"));
352 } else
353 if (hres == S_OK) {
354 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("S_OK\n"));
355 } else {
356 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS,
357 ("****** DisplayErrCode 0x%X\n", hres));
359 #endif
362 //-------------------------------------------------------------------------
363 static HRESULT FillSTGMedium(IDataObject * aDataObject, UINT aFormat, LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed)
365 SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed);
367 // Starting by querying for the data to see if we can get it as from global memory
368 HRESULT hres = S_FALSE;
369 hres = aDataObject->QueryGetData(pFE);
370 DisplayErrCode(hres);
371 if (S_OK == hres) {
372 hres = aDataObject->GetData(pFE, pSTM);
373 DisplayErrCode(hres);
375 return hres;
379 //-------------------------------------------------------------------------
380 // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have
381 // an image encoder (e.g. image/png).
382 // For other values of aFormat, it is OK to pass null for aMIMEImageFormat.
383 nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT aIndex, UINT aFormat, const char * aMIMEImageFormat, void ** aData, uint32_t * aLen)
385 nsresult result = NS_ERROR_FAILURE;
386 *aData = nullptr;
387 *aLen = 0;
389 if ( !aDataObject )
390 return result;
392 UINT format = aFormat;
393 HRESULT hres = S_FALSE;
395 // XXX at the moment we only support global memory transfers
396 // It is here where we will add support for native images
397 // and IStream
398 FORMATETC fe;
399 STGMEDIUM stm;
400 hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL);
402 // Currently this is only handling TYMED_HGLOBAL data
403 // For Text, Dibs, Files, and generic data (like HTML)
404 if (S_OK == hres) {
405 static CLIPFORMAT fileDescriptorFlavorA = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORA );
406 static CLIPFORMAT fileDescriptorFlavorW = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORW );
407 static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat( CFSTR_FILECONTENTS );
408 static CLIPFORMAT preferredDropEffect = ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
410 switch (stm.tymed) {
411 case TYMED_HGLOBAL:
413 switch (fe.cfFormat) {
414 case CF_TEXT:
416 // Get the data out of the global data handle. The size we return
417 // should not include the null because the other platforms don't
418 // use nulls, so just return the length we get back from strlen(),
419 // since we know CF_TEXT is null terminated. Recall that GetGlobalData()
420 // returns the size of the allocated buffer, not the size of the data
421 // (on 98, these are not the same) so we can't use that.
422 uint32_t allocLen = 0;
423 if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) {
424 *aLen = strlen ( reinterpret_cast<char*>(*aData) );
425 result = NS_OK;
427 } break;
429 case CF_UNICODETEXT:
431 // Get the data out of the global data handle. The size we return
432 // should not include the null because the other platforms don't
433 // use nulls, so just return the length we get back from strlen(),
434 // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData()
435 // returns the size of the allocated buffer, not the size of the data
436 // (on 98, these are not the same) so we can't use that.
437 uint32_t allocLen = 0;
438 if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) {
439 *aLen = NS_strlen(reinterpret_cast<PRUnichar*>(*aData)) * 2;
440 result = NS_OK;
442 } break;
444 case CF_DIBV5:
445 if (aMIMEImageFormat)
447 uint32_t allocLen = 0;
448 unsigned char * clipboardData;
449 if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, (void **)&clipboardData, &allocLen)))
451 nsImageFromClipboard converter;
452 nsIInputStream * inputStream;
453 converter.GetEncodedImageStream(clipboardData, aMIMEImageFormat, &inputStream); // addrefs for us, don't release
454 if ( inputStream ) {
455 *aData = inputStream;
456 *aLen = sizeof(nsIInputStream*);
457 result = NS_OK;
460 } break;
462 case CF_HDROP :
464 // in the case of a file drop, multiple files are stashed within a
465 // single data object. In order to match mozilla's D&D apis, we
466 // just pull out the file at the requested index, pretending as
467 // if there really are multiple drag items.
468 HDROP dropFiles = (HDROP) GlobalLock(stm.hGlobal);
470 UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, NULL, 0);
471 NS_ASSERTION ( numFiles > 0, "File drop flavor, but no files...hmmmm" );
472 NS_ASSERTION ( aIndex < numFiles, "Asked for a file index out of range of list" );
473 if (numFiles > 0) {
474 UINT fileNameLen = ::DragQueryFileW(dropFiles, aIndex, nullptr, 0);
475 PRUnichar* buffer = reinterpret_cast<PRUnichar*>(nsMemory::Alloc((fileNameLen + 1) * sizeof(PRUnichar)));
476 if ( buffer ) {
477 ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
478 *aData = buffer;
479 *aLen = fileNameLen * sizeof(PRUnichar);
480 result = NS_OK;
482 else
483 result = NS_ERROR_OUT_OF_MEMORY;
485 GlobalUnlock (stm.hGlobal) ;
487 } break;
489 default: {
490 if ( fe.cfFormat == fileDescriptorFlavorA || fe.cfFormat == fileDescriptorFlavorW || fe.cfFormat == fileFlavor ) {
491 NS_WARNING ( "Mozilla doesn't yet understand how to read this type of file flavor" );
493 else
495 // Get the data out of the global data handle. The size we return
496 // should not include the null because the other platforms don't
497 // use nulls, so just return the length we get back from strlen(),
498 // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData()
499 // returns the size of the allocated buffer, not the size of the data
500 // (on 98, these are not the same) so we can't use that.
502 // NOTE: we are assuming that anything that falls into this default case
503 // is unicode. As we start to get more kinds of binary data, this
504 // may become an incorrect assumption. Stay tuned.
505 uint32_t allocLen = 0;
506 if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) {
507 if ( fe.cfFormat == CF_HTML ) {
508 // CF_HTML is actually UTF8, not unicode, so disregard the assumption
509 // above. We have to check the header for the actual length, and we'll
510 // do that in FindPlatformHTML(). For now, return the allocLen. This
511 // case is mostly to ensure we don't try to call strlen on the buffer.
512 *aLen = allocLen;
513 } else if (fe.cfFormat == preferredDropEffect) {
514 // As per the MSDN doc entitled: "Shell Clipboard Formats"
515 // CFSTR_PREFERREDDROPEFFECT should return a DWORD
516 // Reference: http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx
517 NS_ASSERTION(allocLen == sizeof(DWORD),
518 "CFSTR_PREFERREDDROPEFFECT should return a DWORD");
519 *aLen = allocLen;
520 } else {
521 *aLen = NS_strlen(reinterpret_cast<PRUnichar*>(*aData)) *
522 sizeof(PRUnichar);
524 result = NS_OK;
527 } break;
528 } // switch
529 } break;
531 case TYMED_GDI:
533 #ifdef DEBUG
534 PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS,
535 ("*********************** TYMED_GDI\n"));
536 #endif
537 } break;
539 default:
540 break;
541 } //switch
543 ReleaseStgMedium(&stm);
546 return result;
550 //-------------------------------------------------------------------------
551 nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject,
552 UINT anIndex,
553 nsIWidget * aWindow,
554 nsITransferable * aTransferable)
556 // make sure we have a good transferable
557 if ( !aTransferable )
558 return NS_ERROR_INVALID_ARG;
560 nsresult res = NS_ERROR_FAILURE;
562 // get flavor list that includes all flavors that can be written (including ones
563 // obtained through conversion)
564 nsCOMPtr<nsISupportsArray> flavorList;
565 res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) );
566 if ( NS_FAILED(res) )
567 return NS_ERROR_FAILURE;
569 // Walk through flavors and see which flavor is on the clipboard them on the native clipboard,
570 uint32_t i;
571 uint32_t cnt;
572 flavorList->Count(&cnt);
573 for (i=0;i<cnt;i++) {
574 nsCOMPtr<nsISupports> genericFlavor;
575 flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) );
576 nsCOMPtr<nsISupportsCString> currentFlavor ( do_QueryInterface(genericFlavor) );
577 if ( currentFlavor ) {
578 nsXPIDLCString flavorStr;
579 currentFlavor->ToString(getter_Copies(flavorStr));
580 UINT format = GetFormat(flavorStr);
582 // Try to get the data using the desired flavor. This might fail, but all is
583 // not lost.
584 void* data = nullptr;
585 uint32_t dataLen = 0;
586 bool dataFound = false;
587 if (nullptr != aDataObject) {
588 if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, flavorStr, &data, &dataLen)) )
589 dataFound = true;
591 else if (nullptr != aWindow) {
592 if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, &data, &dataLen)) )
593 dataFound = true;
596 // This is our second chance to try to find some data, having not found it
597 // when directly asking for the flavor. Let's try digging around in other
598 // flavors to help satisfy our craving for data.
599 if ( !dataFound ) {
600 if ( strcmp(flavorStr, kUnicodeMime) == 0 )
601 dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen );
602 else if ( strcmp(flavorStr, kURLMime) == 0 ) {
603 // drags from other windows apps expose the native
604 // CFSTR_INETURL{A,W} flavor
605 dataFound = FindURLFromNativeURL ( aDataObject, anIndex, &data, &dataLen );
606 if ( !dataFound )
607 dataFound = FindURLFromLocalFile ( aDataObject, anIndex, &data, &dataLen );
609 } // if we try one last ditch effort to find our data
611 // Hopefully by this point we've found it and can go about our business
612 if ( dataFound ) {
613 nsCOMPtr<nsISupports> genericDataWrapper;
614 if ( strcmp(flavorStr, kFileMime) == 0 ) {
615 // we have a file path in |data|. Create an nsLocalFile object.
616 nsDependentString filepath(reinterpret_cast<PRUnichar*>(data));
617 nsCOMPtr<nsIFile> file;
618 if ( NS_SUCCEEDED(NS_NewLocalFile(filepath, false, getter_AddRefs(file))) )
619 genericDataWrapper = do_QueryInterface(file);
620 nsMemory::Free(data);
622 else if ( strcmp(flavorStr, kNativeHTMLMime) == 0) {
623 // the editor folks want CF_HTML exactly as it's on the clipboard, no conversions,
624 // no fancy stuff. Pull it off the clipboard, stuff it into a wrapper and hand
625 // it back to them.
626 if ( FindPlatformHTML(aDataObject, anIndex, &data, &dataLen) )
627 nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) );
628 else
630 nsMemory::Free(data);
631 continue; // something wrong with this flavor, keep looking for other data
633 nsMemory::Free(data);
635 else if ( strcmp(flavorStr, kJPEGImageMime) == 0 ||
636 strcmp(flavorStr, kJPGImageMime) == 0 ||
637 strcmp(flavorStr, kPNGImageMime) == 0) {
638 nsIInputStream * imageStream = reinterpret_cast<nsIInputStream*>(data);
639 genericDataWrapper = do_QueryInterface(imageStream);
640 NS_IF_RELEASE(imageStream);
642 else {
643 // we probably have some form of text. The DOM only wants LF, so convert from Win32 line
644 // endings to DOM line endings.
645 int32_t signedLen = static_cast<int32_t>(dataLen);
646 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &data, &signedLen );
647 dataLen = signedLen;
649 nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) );
650 nsMemory::Free(data);
653 NS_ASSERTION ( genericDataWrapper, "About to put null data into the transferable" );
654 aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLen);
655 res = NS_OK;
657 // we found one, get out of the loop
658 break;
662 } // foreach flavor
664 return res;
671 // FindPlatformHTML
673 // Someone asked for the OS CF_HTML flavor. We give it back to them exactly as-is.
675 bool
676 nsClipboard :: FindPlatformHTML ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen )
678 // Reference: MSDN doc entitled "HTML Clipboard Format"
679 // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854
680 // CF_HTML is UTF8, not unicode. We also can't rely on it being null-terminated
681 // so we have to check the CF_HTML header for the correct length.
682 // The length we return is the bytecount from the beginning of the selected data to the end
683 // of the selected data, without the null termination. Because it's UTF8, we're guaranteed
684 // the header is ASCII.
686 if (!outData || !*outData) {
687 return false;
690 char version[8] = { 0 };
691 int32_t startOfData = 0;
692 int32_t endOfData = 0;
693 int numFound = sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d",
694 version, &startOfData, &endOfData);
696 if (numFound != 3 || startOfData < -1 || endOfData < -1) {
697 return false;
700 // Fixup the start and end markers if they have no context (set to -1)
701 if (startOfData == -1) {
702 startOfData = 0;
704 if (endOfData == -1) {
705 endOfData = *outDataLen;
708 // Make sure we were passed sane values within our buffer size.
709 if (!endOfData || startOfData >= endOfData ||
710 endOfData > *outDataLen) {
711 return false;
714 // We want to return the buffer not offset by startOfData because it will be
715 // parsed out later (probably by nsHTMLEditor::ParseCFHTML) when it is still
716 // in CF_HTML format.
717 *outDataLen = endOfData;
718 return true;
723 // FindUnicodeFromPlainText
725 // we are looking for text/unicode and we failed to find it on the clipboard first,
726 // try again with text/plain. If that is present, convert it to unicode.
728 bool
729 nsClipboard :: FindUnicodeFromPlainText ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen )
731 bool dataFound = false;
733 // we are looking for text/unicode and we failed to find it on the clipboard first,
734 // try again with text/plain. If that is present, convert it to unicode.
735 nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime), nullptr, outData, outDataLen);
736 if ( NS_SUCCEEDED(loadResult) && *outData ) {
737 const char* castedText = reinterpret_cast<char*>(*outData);
738 PRUnichar* convertedText = nullptr;
739 int32_t convertedTextLen = 0;
740 nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode ( castedText, *outDataLen,
741 &convertedText, &convertedTextLen );
742 if ( convertedText ) {
743 // out with the old, in with the new
744 nsMemory::Free(*outData);
745 *outData = convertedText;
746 *outDataLen = convertedTextLen * sizeof(PRUnichar);
747 dataFound = true;
749 } // if plain text data on clipboard
751 return dataFound;
753 } // FindUnicodeFromPlainText
757 // FindURLFromLocalFile
759 // we are looking for a URL and couldn't find it, try again with looking for
760 // a local file. If we have one, it may either be a normal file or an internet shortcut.
761 // In both cases, however, we can get a URL (it will be a file:// url in the
762 // local file case).
764 bool
765 nsClipboard :: FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen )
767 bool dataFound = false;
769 nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime), nullptr, outData, outDataLen);
770 if ( NS_SUCCEEDED(loadResult) && *outData ) {
771 // we have a file path in |data|. Is it an internet shortcut or a normal file?
772 const nsDependentString filepath(static_cast<PRUnichar*>(*outData));
773 nsCOMPtr<nsIFile> file;
774 nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file));
775 if (NS_FAILED(rv)) {
776 nsMemory::Free(*outData);
777 return dataFound;
780 if ( IsInternetShortcut(filepath) ) {
781 nsMemory::Free(*outData);
782 nsAutoCString url;
783 ResolveShortcut( file, url );
784 if ( !url.IsEmpty() ) {
785 // convert it to unicode and pass it out
786 nsDependentString urlString(UTF8ToNewUnicode(url));
787 // the internal mozilla URL format, text/x-moz-url, contains
788 // URL\ntitle. We can guess the title from the file's name.
789 nsAutoString title;
790 file->GetLeafName(title);
791 // We rely on IsInternetShortcut check that file has a .url extension.
792 title.SetLength(title.Length() - 4);
793 if (title.IsEmpty())
794 title = urlString;
795 *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + title);
796 *outDataLen = NS_strlen(static_cast<PRUnichar*>(*outData)) * sizeof(PRUnichar);
798 dataFound = true;
801 else {
802 // we have a normal file, use some Necko objects to get our file path
803 nsAutoCString urlSpec;
804 NS_GetURLSpecFromFile(file, urlSpec);
806 // convert it to unicode and pass it out
807 nsMemory::Free(*outData);
808 *outData = UTF8ToNewUnicode(urlSpec);
809 *outDataLen = NS_strlen(static_cast<PRUnichar*>(*outData)) * sizeof(PRUnichar);
810 dataFound = true;
811 } // else regular file
814 return dataFound;
815 } // FindURLFromLocalFile
818 // FindURLFromNativeURL
820 // we are looking for a URL and couldn't find it using our internal
821 // URL flavor, so look for it using the native URL flavor,
822 // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently)
824 bool
825 nsClipboard :: FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen )
827 bool dataFound = false;
829 void* tempOutData = nullptr;
830 uint32_t tempDataLen = 0;
832 nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr, &tempOutData, &tempDataLen);
833 if ( NS_SUCCEEDED(loadResult) && tempOutData ) {
834 nsDependentString urlString(static_cast<PRUnichar*>(tempOutData));
835 // the internal mozilla URL format, text/x-moz-url, contains
836 // URL\ntitle. Since we don't actually have a title here,
837 // just repeat the URL to fake it.
838 *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString);
839 *outDataLen = NS_strlen(static_cast<PRUnichar*>(*outData)) * sizeof(PRUnichar);
840 nsMemory::Free(tempOutData);
841 dataFound = true;
843 else {
844 loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA), nullptr, &tempOutData, &tempDataLen);
845 if ( NS_SUCCEEDED(loadResult) && tempOutData ) {
846 // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to CF_TEXT
847 // which is by definition ANSI encoded.
848 nsCString urlUnescapedA;
849 bool unescaped = NS_UnescapeURL(static_cast<char*>(tempOutData), tempDataLen, esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA);
851 nsString urlString;
852 if (unescaped)
853 NS_CopyNativeToUnicode(urlUnescapedA, urlString);
854 else
855 NS_CopyNativeToUnicode(nsDependentCString(static_cast<char*>(tempOutData), tempDataLen), urlString);
857 // the internal mozilla URL format, text/x-moz-url, contains
858 // URL\ntitle. Since we don't actually have a title here,
859 // just repeat the URL to fake it.
860 *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString);
861 *outDataLen = NS_strlen(static_cast<PRUnichar*>(*outData)) * sizeof(PRUnichar);
862 nsMemory::Free(tempOutData);
863 dataFound = true;
867 return dataFound;
868 } // FindURLFromNativeURL
871 // ResolveShortcut
873 void
874 nsClipboard :: ResolveShortcut ( nsIFile* aFile, nsACString& outURL )
876 nsCOMPtr<nsIFileProtocolHandler> fph;
877 nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph));
878 if (NS_FAILED(rv))
879 return;
881 nsCOMPtr<nsIURI> uri;
882 rv = fph->ReadURLFile(aFile, getter_AddRefs(uri));
883 if (NS_FAILED(rv))
884 return;
886 uri->GetSpec(outURL);
887 } // ResolveShortcut
891 // IsInternetShortcut
893 // A file is an Internet Shortcut if it ends with .URL
895 bool
896 nsClipboard :: IsInternetShortcut ( const nsAString& inFileName )
898 return StringEndsWith(inFileName, NS_LITERAL_STRING(".url"), nsCaseInsensitiveStringComparator());
899 } // IsInternetShortcut
902 //-------------------------------------------------------------------------
903 NS_IMETHODIMP
904 nsClipboard::GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard )
906 // make sure we have a good transferable
907 if ( !aTransferable || aWhichClipboard != kGlobalClipboard )
908 return NS_ERROR_FAILURE;
910 nsresult res;
912 // This makes sure we can use the OLE functionality for the clipboard
913 IDataObject * dataObj;
914 if (S_OK == ::OleGetClipboard(&dataObj)) {
915 // Use OLE IDataObject for clipboard operations
916 res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
917 dataObj->Release();
919 else {
920 // do it the old manual way
921 res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
923 return res;
927 NS_IMETHODIMP
928 nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
930 if (::OpenClipboard(nullptr)) {
931 ::EmptyClipboard();
932 ::CloseClipboard();
934 return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
937 //-------------------------------------------------------------------------
938 NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList,
939 uint32_t aLength,
940 int32_t aWhichClipboard,
941 bool *_retval)
943 *_retval = false;
944 if (aWhichClipboard != kGlobalClipboard || !aFlavorList)
945 return NS_OK;
947 for (uint32_t i = 0;i < aLength; ++i) {
948 #ifdef DEBUG
949 if (strcmp(aFlavorList[i], kTextMime) == 0)
950 NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" );
951 #endif
953 UINT format = GetFormat(aFlavorList[i]);
954 if (IsClipboardFormatAvailable(format)) {
955 *_retval = true;
956 break;
958 else {
959 // We haven't found the exact flavor the client asked for, but maybe we can
960 // still find it from something else that's on the clipboard...
961 if (strcmp(aFlavorList[i], kUnicodeMime) == 0) {
962 // client asked for unicode and it wasn't present, check if we have CF_TEXT.
963 // We'll handle the actual data substitution in the data object.
964 if (IsClipboardFormatAvailable(GetFormat(kTextMime)))
965 *_retval = true;
970 return NS_OK;