michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsClipboard.h" michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN michael@0: #include michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDataObj.h" michael@0: #include "nsIClipboardOwner.h" michael@0: #include "nsString.h" michael@0: #include "nsNativeCharsetUtils.h" michael@0: #include "nsIFormatConverter.h" michael@0: #include "nsITransferable.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsPrimitiveHelpers.h" michael@0: #include "nsImageClipboard.h" michael@0: #include "nsIWidget.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsCRT.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsEscape.h" michael@0: #include "nsIObserverService.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gWin32ClipboardLog = nullptr; michael@0: #endif michael@0: michael@0: // oddly, this isn't in the MSVC headers anywhere. michael@0: UINT nsClipboard::CF_HTML = ::RegisterClipboardFormatW(L"HTML Format"); michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // michael@0: // nsClipboard constructor michael@0: // michael@0: //------------------------------------------------------------------------- michael@0: nsClipboard::nsClipboard() : nsBaseClipboard() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gWin32ClipboardLog) { michael@0: gWin32ClipboardLog = PR_NewLogModule("nsClipboard"); michael@0: } michael@0: #endif michael@0: michael@0: mIgnoreEmptyNotification = false; michael@0: mWindow = nullptr; michael@0: michael@0: // Register for a shutdown notification so that we can flush data michael@0: // to the OS clipboard. michael@0: nsCOMPtr observerService = michael@0: do_GetService("@mozilla.org/observer-service;1"); michael@0: if (observerService) michael@0: observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE); michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // nsClipboard destructor michael@0: //------------------------------------------------------------------------- michael@0: nsClipboard::~nsClipboard() michael@0: { michael@0: michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: nsClipboard::Observe(nsISupports *aSubject, const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: // This will be called on shutdown. michael@0: ::OleFlushClipboard(); michael@0: ::CloseClipboard(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: UINT nsClipboard::GetFormat(const char* aMimeStr) michael@0: { michael@0: UINT format; michael@0: michael@0: if (strcmp(aMimeStr, kTextMime) == 0) michael@0: format = CF_TEXT; michael@0: else if (strcmp(aMimeStr, kUnicodeMime) == 0) michael@0: format = CF_UNICODETEXT; michael@0: else if (strcmp(aMimeStr, kJPEGImageMime) == 0 || michael@0: strcmp(aMimeStr, kJPGImageMime) == 0 || michael@0: strcmp(aMimeStr, kPNGImageMime) == 0) michael@0: format = CF_DIBV5; michael@0: else if (strcmp(aMimeStr, kFileMime) == 0 || michael@0: strcmp(aMimeStr, kFilePromiseMime) == 0) michael@0: format = CF_HDROP; michael@0: else if (strcmp(aMimeStr, kNativeHTMLMime) == 0) michael@0: format = CF_HTML; michael@0: else michael@0: format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get()); michael@0: michael@0: return format; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult nsClipboard::CreateNativeDataObject(nsITransferable * aTransferable, IDataObject ** aDataObj, nsIURI * uri) michael@0: { michael@0: if (nullptr == aTransferable) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Create our native DataObject that implements michael@0: // the OLE IDataObject interface michael@0: nsDataObj * dataObj = new nsDataObj(uri); michael@0: michael@0: if (!dataObj) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: dataObj->AddRef(); michael@0: michael@0: // Now set it up with all the right data flavors & enums michael@0: nsresult res = SetupNativeDataObject(aTransferable, dataObj); michael@0: if (NS_OK == res) { michael@0: *aDataObj = dataObj; michael@0: } else { michael@0: delete dataObj; michael@0: } michael@0: return res; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult nsClipboard::SetupNativeDataObject(nsITransferable * aTransferable, IDataObject * aDataObj) michael@0: { michael@0: if (nullptr == aTransferable || nullptr == aDataObj) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsDataObj * dObj = static_cast(aDataObj); michael@0: michael@0: // Now give the Transferable to the DataObject michael@0: // for getting the data out of it michael@0: dObj->SetTransferable(aTransferable); michael@0: michael@0: // Get the transferable list of data flavors michael@0: nsCOMPtr dfList; michael@0: aTransferable->FlavorsTransferableCanExport(getter_AddRefs(dfList)); michael@0: michael@0: // Walk through flavors that contain data and register them michael@0: // into the DataObj as supported flavors michael@0: uint32_t i; michael@0: uint32_t cnt; michael@0: dfList->Count(&cnt); michael@0: for (i=0;i genericFlavor; michael@0: dfList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); michael@0: nsCOMPtr currentFlavor ( do_QueryInterface(genericFlavor) ); michael@0: if ( currentFlavor ) { michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: UINT format = GetFormat(flavorStr); michael@0: michael@0: // Now tell the native IDataObject about both our mime type and michael@0: // the native data format michael@0: FORMATETC fe; michael@0: SET_FORMATETC(fe, format, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); michael@0: dObj->AddDataFlavor(flavorStr, &fe); michael@0: michael@0: // Do various things internal to the implementation, like map one michael@0: // flavor to another or add additional flavors based on what's required michael@0: // for the win32 impl. michael@0: if ( strcmp(flavorStr, kUnicodeMime) == 0 ) { michael@0: // if we find text/unicode, also advertise text/plain (which we will convert michael@0: // on our own in nsDataObj::GetText(). michael@0: FORMATETC textFE; michael@0: SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); michael@0: dObj->AddDataFlavor(kTextMime, &textFE); michael@0: } michael@0: else if ( strcmp(flavorStr, kHTMLMime) == 0 ) { michael@0: // if we find text/html, also advertise win32's html flavor (which we will convert michael@0: // on our own in nsDataObj::GetText(). michael@0: FORMATETC htmlFE; michael@0: SET_FORMATETC(htmlFE, CF_HTML, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); michael@0: dObj->AddDataFlavor(kHTMLMime, &htmlFE); michael@0: } michael@0: else if ( strcmp(flavorStr, kURLMime) == 0 ) { michael@0: // if we're a url, in addition to also being text, we need to register michael@0: // the "file" flavors so that the win32 shell knows to create an internet michael@0: // shortcut when it sees one of these beasts. michael@0: FORMATETC shortcutFE; michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kURLMime, &shortcutFE); michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILEDESCRIPTORW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kURLMime, &shortcutFE); michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_FILECONTENTS), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kURLMime, &shortcutFE); michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLA), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kURLMime, &shortcutFE); michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_INETURLW), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kURLMime, &shortcutFE); michael@0: } michael@0: else if ( strcmp(flavorStr, kPNGImageMime) == 0 || strcmp(flavorStr, kJPEGImageMime) == 0 || michael@0: strcmp(flavorStr, kJPGImageMime) == 0 || strcmp(flavorStr, kGIFImageMime) == 0 || michael@0: strcmp(flavorStr, kNativeImageMime) == 0 ) { michael@0: // if we're an image, register the native bitmap flavor michael@0: FORMATETC imageFE; michael@0: // Add DIBv5 michael@0: SET_FORMATETC(imageFE, CF_DIBV5, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(flavorStr, &imageFE); michael@0: // Add DIBv3 michael@0: SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(flavorStr, &imageFE); michael@0: } michael@0: else if ( strcmp(flavorStr, kFilePromiseMime) == 0 ) { michael@0: // if we're a file promise flavor, also register the michael@0: // CFSTR_PREFERREDDROPEFFECT format. The data object michael@0: // returns a value of DROPEFFECTS_MOVE to the drop target michael@0: // when it asks for the value of this format. This causes michael@0: // the file to be moved from the temporary location instead michael@0: // of being copied. The right thing to do here is to call michael@0: // SetData() on the data object and set the value of this format michael@0: // to DROPEFFECTS_MOVE on this particular data object. But, michael@0: // since all the other clipboard formats follow the model of setting michael@0: // data on the data object only when the drop object calls GetData(), michael@0: // I am leaving this format's value hard coded in the data object. michael@0: // We can change this if other consumers of this format get added to this michael@0: // codebase and they need different values. michael@0: FORMATETC shortcutFE; michael@0: SET_FORMATETC(shortcutFE, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT), 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL) michael@0: dObj->AddDataFlavor(kFilePromiseMime, &shortcutFE); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: NS_IMETHODIMP nsClipboard::SetNativeClipboardData ( int32_t aWhichClipboard ) michael@0: { michael@0: if ( aWhichClipboard != kGlobalClipboard ) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mIgnoreEmptyNotification = true; michael@0: michael@0: // make sure we have a good transferable michael@0: if (nullptr == mTransferable) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: IDataObject * dataObj; michael@0: if ( NS_SUCCEEDED(CreateNativeDataObject(mTransferable, &dataObj, nullptr)) ) { // this add refs dataObj michael@0: ::OleSetClipboard(dataObj); michael@0: dataObj->Release(); michael@0: } else { michael@0: // Clear the native clipboard michael@0: ::OleSetClipboard(nullptr); michael@0: } michael@0: michael@0: mIgnoreEmptyNotification = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void ** aData, uint32_t * aLen) michael@0: { michael@0: // Allocate a new memory buffer and copy the data from global memory. michael@0: // Recall that win98 allocates to nearest DWORD boundary. As a safety michael@0: // precaution, allocate an extra 2 bytes (but don't report them!) and michael@0: // null them out to ensure that all of our strlen calls will succeed. michael@0: nsresult result = NS_ERROR_FAILURE; michael@0: if (aHGBL != nullptr) { michael@0: LPSTR lpStr = (LPSTR) GlobalLock(aHGBL); michael@0: DWORD allocSize = GlobalSize(aHGBL); michael@0: char* data = static_cast(nsMemory::Alloc(allocSize + sizeof(char16_t))); michael@0: if ( data ) { michael@0: memcpy ( data, lpStr, allocSize ); michael@0: data[allocSize] = data[allocSize + 1] = '\0'; // null terminate for safety michael@0: michael@0: GlobalUnlock(aHGBL); michael@0: *aData = data; michael@0: *aLen = allocSize; michael@0: michael@0: result = NS_OK; michael@0: } michael@0: } else { michael@0: #ifdef MOZ_METRO michael@0: return result; michael@0: #endif michael@0: // We really shouldn't ever get here michael@0: // but just in case michael@0: *aData = nullptr; michael@0: *aLen = 0; michael@0: LPVOID lpMsgBuf; michael@0: michael@0: FormatMessageW( michael@0: FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, michael@0: nullptr, michael@0: GetLastError(), michael@0: MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language michael@0: (LPWSTR) &lpMsgBuf, michael@0: 0, michael@0: nullptr michael@0: ); michael@0: michael@0: // Display the string. michael@0: MessageBoxW( nullptr, (LPCWSTR) lpMsgBuf, L"GetLastError", michael@0: MB_OK | MB_ICONINFORMATION ); michael@0: michael@0: // Free the buffer. michael@0: LocalFree( lpMsgBuf ); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult nsClipboard::GetNativeDataOffClipboard(nsIWidget * aWidget, UINT /*aIndex*/, UINT aFormat, void ** aData, uint32_t * aLen) michael@0: { michael@0: HGLOBAL hglb; michael@0: nsresult result = NS_ERROR_FAILURE; michael@0: michael@0: HWND nativeWin = nullptr; michael@0: if (::OpenClipboard(nativeWin)) { michael@0: hglb = ::GetClipboardData(aFormat); michael@0: result = GetGlobalData(hglb, aData, aLen); michael@0: ::CloseClipboard(); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: static void DisplayErrCode(HRESULT hres) michael@0: { michael@0: #if defined(DEBUG_rods) || defined(DEBUG_pinkerton) michael@0: if (hres == E_INVALIDARG) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_INVALIDARG\n")); michael@0: } else michael@0: if (hres == E_UNEXPECTED) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_UNEXPECTED\n")); michael@0: } else michael@0: if (hres == E_OUTOFMEMORY) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("E_OUTOFMEMORY\n")); michael@0: } else michael@0: if (hres == DV_E_LINDEX ) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_LINDEX\n")); michael@0: } else michael@0: if (hres == DV_E_FORMATETC) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_FORMATETC\n")); michael@0: } else michael@0: if (hres == DV_E_TYMED) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_TYMED\n")); michael@0: } else michael@0: if (hres == DV_E_DVASPECT) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_DVASPECT\n")); michael@0: } else michael@0: if (hres == OLE_E_NOTRUNNING) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("OLE_E_NOTRUNNING\n")); michael@0: } else michael@0: if (hres == STG_E_MEDIUMFULL) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("STG_E_MEDIUMFULL\n")); michael@0: } else michael@0: if (hres == DV_E_CLIPFORMAT) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("DV_E_CLIPFORMAT\n")); michael@0: } else michael@0: if (hres == S_OK) { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, ("S_OK\n")); michael@0: } else { michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, michael@0: ("****** DisplayErrCode 0x%X\n", hres)); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: static HRESULT FillSTGMedium(IDataObject * aDataObject, UINT aFormat, LPFORMATETC pFE, LPSTGMEDIUM pSTM, DWORD aTymed) michael@0: { michael@0: SET_FORMATETC(*pFE, aFormat, 0, DVASPECT_CONTENT, -1, aTymed); michael@0: michael@0: // Starting by querying for the data to see if we can get it as from global memory michael@0: HRESULT hres = S_FALSE; michael@0: hres = aDataObject->QueryGetData(pFE); michael@0: DisplayErrCode(hres); michael@0: if (S_OK == hres) { michael@0: hres = aDataObject->GetData(pFE, pSTM); michael@0: DisplayErrCode(hres); michael@0: } michael@0: return hres; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // If aFormat is CF_DIBV5, aMIMEImageFormat must be a type for which we have michael@0: // an image encoder (e.g. image/png). michael@0: // For other values of aFormat, it is OK to pass null for aMIMEImageFormat. michael@0: nsresult nsClipboard::GetNativeDataOffClipboard(IDataObject * aDataObject, UINT aIndex, UINT aFormat, const char * aMIMEImageFormat, void ** aData, uint32_t * aLen) michael@0: { michael@0: nsresult result = NS_ERROR_FAILURE; michael@0: *aData = nullptr; michael@0: *aLen = 0; michael@0: michael@0: if ( !aDataObject ) michael@0: return result; michael@0: michael@0: UINT format = aFormat; michael@0: HRESULT hres = S_FALSE; michael@0: michael@0: // XXX at the moment we only support global memory transfers michael@0: // It is here where we will add support for native images michael@0: // and IStream michael@0: FORMATETC fe; michael@0: STGMEDIUM stm; michael@0: hres = FillSTGMedium(aDataObject, format, &fe, &stm, TYMED_HGLOBAL); michael@0: michael@0: // Currently this is only handling TYMED_HGLOBAL data michael@0: // For Text, Dibs, Files, and generic data (like HTML) michael@0: if (S_OK == hres) { michael@0: static CLIPFORMAT fileDescriptorFlavorA = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORA ); michael@0: static CLIPFORMAT fileDescriptorFlavorW = ::RegisterClipboardFormat( CFSTR_FILEDESCRIPTORW ); michael@0: static CLIPFORMAT fileFlavor = ::RegisterClipboardFormat( CFSTR_FILECONTENTS ); michael@0: static CLIPFORMAT preferredDropEffect = ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT); michael@0: michael@0: switch (stm.tymed) { michael@0: case TYMED_HGLOBAL: michael@0: { michael@0: switch (fe.cfFormat) { michael@0: case CF_TEXT: michael@0: { michael@0: // Get the data out of the global data handle. The size we return michael@0: // should not include the null because the other platforms don't michael@0: // use nulls, so just return the length we get back from strlen(), michael@0: // since we know CF_TEXT is null terminated. Recall that GetGlobalData() michael@0: // returns the size of the allocated buffer, not the size of the data michael@0: // (on 98, these are not the same) so we can't use that. michael@0: uint32_t allocLen = 0; michael@0: if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { michael@0: *aLen = strlen ( reinterpret_cast(*aData) ); michael@0: result = NS_OK; michael@0: } michael@0: } break; michael@0: michael@0: case CF_UNICODETEXT: michael@0: { michael@0: // Get the data out of the global data handle. The size we return michael@0: // should not include the null because the other platforms don't michael@0: // use nulls, so just return the length we get back from strlen(), michael@0: // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData() michael@0: // returns the size of the allocated buffer, not the size of the data michael@0: // (on 98, these are not the same) so we can't use that. michael@0: uint32_t allocLen = 0; michael@0: if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { michael@0: *aLen = NS_strlen(reinterpret_cast(*aData)) * 2; michael@0: result = NS_OK; michael@0: } michael@0: } break; michael@0: michael@0: case CF_DIBV5: michael@0: if (aMIMEImageFormat) michael@0: { michael@0: uint32_t allocLen = 0; michael@0: unsigned char * clipboardData; michael@0: if (NS_SUCCEEDED(GetGlobalData(stm.hGlobal, (void **)&clipboardData, &allocLen))) michael@0: { michael@0: nsImageFromClipboard converter; michael@0: nsIInputStream * inputStream; michael@0: converter.GetEncodedImageStream(clipboardData, aMIMEImageFormat, &inputStream); // addrefs for us, don't release michael@0: if ( inputStream ) { michael@0: *aData = inputStream; michael@0: *aLen = sizeof(nsIInputStream*); michael@0: result = NS_OK; michael@0: } michael@0: } michael@0: } break; michael@0: michael@0: case CF_HDROP : michael@0: { michael@0: // in the case of a file drop, multiple files are stashed within a michael@0: // single data object. In order to match mozilla's D&D apis, we michael@0: // just pull out the file at the requested index, pretending as michael@0: // if there really are multiple drag items. michael@0: HDROP dropFiles = (HDROP) GlobalLock(stm.hGlobal); michael@0: michael@0: UINT numFiles = ::DragQueryFileW(dropFiles, 0xFFFFFFFF, nullptr, 0); michael@0: NS_ASSERTION ( numFiles > 0, "File drop flavor, but no files...hmmmm" ); michael@0: NS_ASSERTION ( aIndex < numFiles, "Asked for a file index out of range of list" ); michael@0: if (numFiles > 0) { michael@0: UINT fileNameLen = ::DragQueryFileW(dropFiles, aIndex, nullptr, 0); michael@0: wchar_t* buffer = reinterpret_cast(nsMemory::Alloc((fileNameLen + 1) * sizeof(wchar_t))); michael@0: if ( buffer ) { michael@0: ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1); michael@0: *aData = buffer; michael@0: *aLen = fileNameLen * sizeof(char16_t); michael@0: result = NS_OK; michael@0: } michael@0: else michael@0: result = NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: GlobalUnlock (stm.hGlobal) ; michael@0: michael@0: } break; michael@0: michael@0: default: { michael@0: if ( fe.cfFormat == fileDescriptorFlavorA || fe.cfFormat == fileDescriptorFlavorW || fe.cfFormat == fileFlavor ) { michael@0: NS_WARNING ( "Mozilla doesn't yet understand how to read this type of file flavor" ); michael@0: } michael@0: else michael@0: { michael@0: // Get the data out of the global data handle. The size we return michael@0: // should not include the null because the other platforms don't michael@0: // use nulls, so just return the length we get back from strlen(), michael@0: // since we know CF_UNICODETEXT is null terminated. Recall that GetGlobalData() michael@0: // returns the size of the allocated buffer, not the size of the data michael@0: // (on 98, these are not the same) so we can't use that. michael@0: // michael@0: // NOTE: we are assuming that anything that falls into this default case michael@0: // is unicode. As we start to get more kinds of binary data, this michael@0: // may become an incorrect assumption. Stay tuned. michael@0: uint32_t allocLen = 0; michael@0: if ( NS_SUCCEEDED(GetGlobalData(stm.hGlobal, aData, &allocLen)) ) { michael@0: if ( fe.cfFormat == CF_HTML ) { michael@0: // CF_HTML is actually UTF8, not unicode, so disregard the assumption michael@0: // above. We have to check the header for the actual length, and we'll michael@0: // do that in FindPlatformHTML(). For now, return the allocLen. This michael@0: // case is mostly to ensure we don't try to call strlen on the buffer. michael@0: *aLen = allocLen; michael@0: } else if (fe.cfFormat == preferredDropEffect) { michael@0: // As per the MSDN doc entitled: "Shell Clipboard Formats" michael@0: // CFSTR_PREFERREDDROPEFFECT should return a DWORD michael@0: // Reference: http://msdn.microsoft.com/en-us/library/bb776902(v=vs.85).aspx michael@0: NS_ASSERTION(allocLen == sizeof(DWORD), michael@0: "CFSTR_PREFERREDDROPEFFECT should return a DWORD"); michael@0: *aLen = allocLen; michael@0: } else { michael@0: *aLen = NS_strlen(reinterpret_cast(*aData)) * michael@0: sizeof(char16_t); michael@0: } michael@0: result = NS_OK; michael@0: } michael@0: } michael@0: } break; michael@0: } // switch michael@0: } break; michael@0: michael@0: case TYMED_GDI: michael@0: { michael@0: #ifdef DEBUG michael@0: PR_LOG(gWin32ClipboardLog, PR_LOG_ALWAYS, michael@0: ("*********************** TYMED_GDI\n")); michael@0: #endif michael@0: } break; michael@0: michael@0: default: michael@0: break; michael@0: } //switch michael@0: michael@0: ReleaseStgMedium(&stm); michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult nsClipboard::GetDataFromDataObject(IDataObject * aDataObject, michael@0: UINT anIndex, michael@0: nsIWidget * aWindow, michael@0: nsITransferable * aTransferable) michael@0: { michael@0: // make sure we have a good transferable michael@0: if ( !aTransferable ) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsresult res = NS_ERROR_FAILURE; michael@0: michael@0: // get flavor list that includes all flavors that can be written (including ones michael@0: // obtained through conversion) michael@0: nsCOMPtr flavorList; michael@0: res = aTransferable->FlavorsTransferableCanImport ( getter_AddRefs(flavorList) ); michael@0: if ( NS_FAILED(res) ) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Walk through flavors and see which flavor is on the clipboard them on the native clipboard, michael@0: uint32_t i; michael@0: uint32_t cnt; michael@0: flavorList->Count(&cnt); michael@0: for (i=0;i genericFlavor; michael@0: flavorList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); michael@0: nsCOMPtr currentFlavor ( do_QueryInterface(genericFlavor) ); michael@0: if ( currentFlavor ) { michael@0: nsXPIDLCString flavorStr; michael@0: currentFlavor->ToString(getter_Copies(flavorStr)); michael@0: UINT format = GetFormat(flavorStr); michael@0: michael@0: // Try to get the data using the desired flavor. This might fail, but all is michael@0: // not lost. michael@0: void* data = nullptr; michael@0: uint32_t dataLen = 0; michael@0: bool dataFound = false; michael@0: if (nullptr != aDataObject) { michael@0: if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aDataObject, anIndex, format, flavorStr, &data, &dataLen)) ) michael@0: dataFound = true; michael@0: } michael@0: else if (nullptr != aWindow) { michael@0: if ( NS_SUCCEEDED(GetNativeDataOffClipboard(aWindow, anIndex, format, &data, &dataLen)) ) michael@0: dataFound = true; michael@0: } michael@0: michael@0: // This is our second chance to try to find some data, having not found it michael@0: // when directly asking for the flavor. Let's try digging around in other michael@0: // flavors to help satisfy our craving for data. michael@0: if ( !dataFound ) { michael@0: if ( strcmp(flavorStr, kUnicodeMime) == 0 ) michael@0: dataFound = FindUnicodeFromPlainText ( aDataObject, anIndex, &data, &dataLen ); michael@0: else if ( strcmp(flavorStr, kURLMime) == 0 ) { michael@0: // drags from other windows apps expose the native michael@0: // CFSTR_INETURL{A,W} flavor michael@0: dataFound = FindURLFromNativeURL ( aDataObject, anIndex, &data, &dataLen ); michael@0: if ( !dataFound ) michael@0: dataFound = FindURLFromLocalFile ( aDataObject, anIndex, &data, &dataLen ); michael@0: } michael@0: } // if we try one last ditch effort to find our data michael@0: michael@0: // Hopefully by this point we've found it and can go about our business michael@0: if ( dataFound ) { michael@0: nsCOMPtr genericDataWrapper; michael@0: if ( strcmp(flavorStr, kFileMime) == 0 ) { michael@0: // we have a file path in |data|. Create an nsLocalFile object. michael@0: nsDependentString filepath(reinterpret_cast(data)); michael@0: nsCOMPtr file; michael@0: if ( NS_SUCCEEDED(NS_NewLocalFile(filepath, false, getter_AddRefs(file))) ) michael@0: genericDataWrapper = do_QueryInterface(file); michael@0: nsMemory::Free(data); michael@0: } michael@0: else if ( strcmp(flavorStr, kNativeHTMLMime) == 0) { michael@0: // the editor folks want CF_HTML exactly as it's on the clipboard, no conversions, michael@0: // no fancy stuff. Pull it off the clipboard, stuff it into a wrapper and hand michael@0: // it back to them. michael@0: if ( FindPlatformHTML(aDataObject, anIndex, &data, &dataLen) ) michael@0: nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) ); michael@0: else michael@0: { michael@0: nsMemory::Free(data); michael@0: continue; // something wrong with this flavor, keep looking for other data michael@0: } michael@0: nsMemory::Free(data); michael@0: } michael@0: else if ( strcmp(flavorStr, kJPEGImageMime) == 0 || michael@0: strcmp(flavorStr, kJPGImageMime) == 0 || michael@0: strcmp(flavorStr, kPNGImageMime) == 0) { michael@0: nsIInputStream * imageStream = reinterpret_cast(data); michael@0: genericDataWrapper = do_QueryInterface(imageStream); michael@0: NS_IF_RELEASE(imageStream); michael@0: } michael@0: else { michael@0: // we probably have some form of text. The DOM only wants LF, so convert from Win32 line michael@0: // endings to DOM line endings. michael@0: int32_t signedLen = static_cast(dataLen); michael@0: nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks ( flavorStr, &data, &signedLen ); michael@0: dataLen = signedLen; michael@0: michael@0: nsPrimitiveHelpers::CreatePrimitiveForData ( flavorStr, data, dataLen, getter_AddRefs(genericDataWrapper) ); michael@0: nsMemory::Free(data); michael@0: } michael@0: michael@0: NS_ASSERTION ( genericDataWrapper, "About to put null data into the transferable" ); michael@0: aTransferable->SetTransferData(flavorStr, genericDataWrapper, dataLen); michael@0: res = NS_OK; michael@0: michael@0: // we found one, get out of the loop michael@0: break; michael@0: } michael@0: michael@0: } michael@0: } // foreach flavor michael@0: michael@0: return res; michael@0: michael@0: } michael@0: michael@0: michael@0: michael@0: // michael@0: // FindPlatformHTML michael@0: // michael@0: // Someone asked for the OS CF_HTML flavor. We give it back to them exactly as-is. michael@0: // michael@0: bool michael@0: nsClipboard :: FindPlatformHTML ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) michael@0: { michael@0: // Reference: MSDN doc entitled "HTML Clipboard Format" michael@0: // http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx#unknown_854 michael@0: // CF_HTML is UTF8, not unicode. We also can't rely on it being null-terminated michael@0: // so we have to check the CF_HTML header for the correct length. michael@0: // The length we return is the bytecount from the beginning of the selected data to the end michael@0: // of the selected data, without the null termination. Because it's UTF8, we're guaranteed michael@0: // the header is ASCII. michael@0: michael@0: if (!outData || !*outData) { michael@0: return false; michael@0: } michael@0: michael@0: char version[8] = { 0 }; michael@0: int32_t startOfData = 0; michael@0: int32_t endOfData = 0; michael@0: int numFound = sscanf((char*)*outData, "Version:%7s\nStartHTML:%d\nEndHTML:%d", michael@0: version, &startOfData, &endOfData); michael@0: michael@0: if (numFound != 3 || startOfData < -1 || endOfData < -1) { michael@0: return false; michael@0: } michael@0: michael@0: // Fixup the start and end markers if they have no context (set to -1) michael@0: if (startOfData == -1) { michael@0: startOfData = 0; michael@0: } michael@0: if (endOfData == -1) { michael@0: endOfData = *outDataLen; michael@0: } michael@0: michael@0: // Make sure we were passed sane values within our buffer size. michael@0: // (Note that we've handled all cases of negative endOfData above, so we can michael@0: // safely cast it to be unsigned here.) michael@0: if (!endOfData || startOfData >= endOfData || michael@0: static_cast(endOfData) > *outDataLen) { michael@0: return false; michael@0: } michael@0: michael@0: // We want to return the buffer not offset by startOfData because it will be michael@0: // parsed out later (probably by nsHTMLEditor::ParseCFHTML) when it is still michael@0: // in CF_HTML format. michael@0: *outDataLen = endOfData; michael@0: return true; michael@0: } michael@0: michael@0: michael@0: // michael@0: // FindUnicodeFromPlainText michael@0: // michael@0: // we are looking for text/unicode and we failed to find it on the clipboard first, michael@0: // try again with text/plain. If that is present, convert it to unicode. michael@0: // michael@0: bool michael@0: nsClipboard :: FindUnicodeFromPlainText ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) michael@0: { michael@0: bool dataFound = false; michael@0: michael@0: // we are looking for text/unicode and we failed to find it on the clipboard first, michael@0: // try again with text/plain. If that is present, convert it to unicode. michael@0: nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kTextMime), nullptr, outData, outDataLen); michael@0: if ( NS_SUCCEEDED(loadResult) && *outData ) { michael@0: const char* castedText = reinterpret_cast(*outData); michael@0: char16_t* convertedText = nullptr; michael@0: int32_t convertedTextLen = 0; michael@0: nsPrimitiveHelpers::ConvertPlatformPlainTextToUnicode ( castedText, *outDataLen, michael@0: &convertedText, &convertedTextLen ); michael@0: if ( convertedText ) { michael@0: // out with the old, in with the new michael@0: nsMemory::Free(*outData); michael@0: *outData = convertedText; michael@0: *outDataLen = convertedTextLen * sizeof(char16_t); michael@0: dataFound = true; michael@0: } michael@0: } // if plain text data on clipboard michael@0: michael@0: return dataFound; michael@0: michael@0: } // FindUnicodeFromPlainText michael@0: michael@0: michael@0: // michael@0: // FindURLFromLocalFile michael@0: // michael@0: // we are looking for a URL and couldn't find it, try again with looking for michael@0: // a local file. If we have one, it may either be a normal file or an internet shortcut. michael@0: // In both cases, however, we can get a URL (it will be a file:// url in the michael@0: // local file case). michael@0: // michael@0: bool michael@0: nsClipboard :: FindURLFromLocalFile ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) michael@0: { michael@0: bool dataFound = false; michael@0: michael@0: nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, GetFormat(kFileMime), nullptr, outData, outDataLen); michael@0: if ( NS_SUCCEEDED(loadResult) && *outData ) { michael@0: // we have a file path in |data|. Is it an internet shortcut or a normal file? michael@0: const nsDependentString filepath(static_cast(*outData)); michael@0: nsCOMPtr file; michael@0: nsresult rv = NS_NewLocalFile(filepath, true, getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) { michael@0: nsMemory::Free(*outData); michael@0: return dataFound; michael@0: } michael@0: michael@0: if ( IsInternetShortcut(filepath) ) { michael@0: nsMemory::Free(*outData); michael@0: nsAutoCString url; michael@0: ResolveShortcut( file, url ); michael@0: if ( !url.IsEmpty() ) { michael@0: // convert it to unicode and pass it out michael@0: nsDependentString urlString(UTF8ToNewUnicode(url)); michael@0: // the internal mozilla URL format, text/x-moz-url, contains michael@0: // URL\ntitle. We can guess the title from the file's name. michael@0: nsAutoString title; michael@0: file->GetLeafName(title); michael@0: // We rely on IsInternetShortcut check that file has a .url extension. michael@0: title.SetLength(title.Length() - 4); michael@0: if (title.IsEmpty()) michael@0: title = urlString; michael@0: *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + title); michael@0: *outDataLen = NS_strlen(static_cast(*outData)) * sizeof(char16_t); michael@0: michael@0: dataFound = true; michael@0: } michael@0: } michael@0: else { michael@0: // we have a normal file, use some Necko objects to get our file path michael@0: nsAutoCString urlSpec; michael@0: NS_GetURLSpecFromFile(file, urlSpec); michael@0: michael@0: // convert it to unicode and pass it out michael@0: nsMemory::Free(*outData); michael@0: *outData = UTF8ToNewUnicode(urlSpec); michael@0: *outDataLen = NS_strlen(static_cast(*outData)) * sizeof(char16_t); michael@0: dataFound = true; michael@0: } // else regular file michael@0: } michael@0: michael@0: return dataFound; michael@0: } // FindURLFromLocalFile michael@0: michael@0: // michael@0: // FindURLFromNativeURL michael@0: // michael@0: // we are looking for a URL and couldn't find it using our internal michael@0: // URL flavor, so look for it using the native URL flavor, michael@0: // CF_INETURLSTRW (We don't handle CF_INETURLSTRA currently) michael@0: // michael@0: bool michael@0: nsClipboard :: FindURLFromNativeURL ( IDataObject* inDataObject, UINT inIndex, void** outData, uint32_t* outDataLen ) michael@0: { michael@0: bool dataFound = false; michael@0: michael@0: void* tempOutData = nullptr; michael@0: uint32_t tempDataLen = 0; michael@0: michael@0: nsresult loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLW), nullptr, &tempOutData, &tempDataLen); michael@0: if ( NS_SUCCEEDED(loadResult) && tempOutData ) { michael@0: nsDependentString urlString(static_cast(tempOutData)); michael@0: // the internal mozilla URL format, text/x-moz-url, contains michael@0: // URL\ntitle. Since we don't actually have a title here, michael@0: // just repeat the URL to fake it. michael@0: *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString); michael@0: *outDataLen = NS_strlen(static_cast(*outData)) * sizeof(char16_t); michael@0: nsMemory::Free(tempOutData); michael@0: dataFound = true; michael@0: } michael@0: else { michael@0: loadResult = GetNativeDataOffClipboard(inDataObject, inIndex, ::RegisterClipboardFormat(CFSTR_INETURLA), nullptr, &tempOutData, &tempDataLen); michael@0: if ( NS_SUCCEEDED(loadResult) && tempOutData ) { michael@0: // CFSTR_INETURLA is (currently) equal to CFSTR_SHELLURL which is equal to CF_TEXT michael@0: // which is by definition ANSI encoded. michael@0: nsCString urlUnescapedA; michael@0: bool unescaped = NS_UnescapeURL(static_cast(tempOutData), tempDataLen, esc_OnlyNonASCII | esc_SkipControl, urlUnescapedA); michael@0: michael@0: nsString urlString; michael@0: if (unescaped) michael@0: NS_CopyNativeToUnicode(urlUnescapedA, urlString); michael@0: else michael@0: NS_CopyNativeToUnicode(nsDependentCString(static_cast(tempOutData), tempDataLen), urlString); michael@0: michael@0: // the internal mozilla URL format, text/x-moz-url, contains michael@0: // URL\ntitle. Since we don't actually have a title here, michael@0: // just repeat the URL to fake it. michael@0: *outData = ToNewUnicode(urlString + NS_LITERAL_STRING("\n") + urlString); michael@0: *outDataLen = NS_strlen(static_cast(*outData)) * sizeof(char16_t); michael@0: nsMemory::Free(tempOutData); michael@0: dataFound = true; michael@0: } michael@0: } michael@0: michael@0: return dataFound; michael@0: } // FindURLFromNativeURL michael@0: michael@0: // michael@0: // ResolveShortcut michael@0: // michael@0: void michael@0: nsClipboard :: ResolveShortcut ( nsIFile* aFile, nsACString& outURL ) michael@0: { michael@0: nsCOMPtr fph; michael@0: nsresult rv = NS_GetFileProtocolHandler(getter_AddRefs(fph)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: nsCOMPtr uri; michael@0: rv = fph->ReadURLFile(aFile, getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: uri->GetSpec(outURL); michael@0: } // ResolveShortcut michael@0: michael@0: michael@0: // michael@0: // IsInternetShortcut michael@0: // michael@0: // A file is an Internet Shortcut if it ends with .URL michael@0: // michael@0: bool michael@0: nsClipboard :: IsInternetShortcut ( const nsAString& inFileName ) michael@0: { michael@0: return StringEndsWith(inFileName, NS_LITERAL_STRING(".url"), nsCaseInsensitiveStringComparator()); michael@0: } // IsInternetShortcut michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: NS_IMETHODIMP michael@0: nsClipboard::GetNativeClipboardData ( nsITransferable * aTransferable, int32_t aWhichClipboard ) michael@0: { michael@0: // make sure we have a good transferable michael@0: if ( !aTransferable || aWhichClipboard != kGlobalClipboard ) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult res; michael@0: michael@0: // This makes sure we can use the OLE functionality for the clipboard michael@0: IDataObject * dataObj; michael@0: if (S_OK == ::OleGetClipboard(&dataObj)) { michael@0: // Use OLE IDataObject for clipboard operations michael@0: res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable); michael@0: dataObj->Release(); michael@0: } michael@0: else { michael@0: // do it the old manual way michael@0: res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable); michael@0: } michael@0: return res; michael@0: michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsClipboard::EmptyClipboard(int32_t aWhichClipboard) michael@0: { michael@0: // Some programs such as ZoneAlarm monitor clipboard usage and then open the michael@0: // clipboard to scan it. If we i) empty and then ii) set data, then the michael@0: // 'set data' can sometimes fail with access denied becacuse another program michael@0: // has the clipboard open. So to avoid this race condition for OpenClipboard michael@0: // we do not empty the clipboard when we're setting it. michael@0: if (aWhichClipboard == kGlobalClipboard && !mEmptyingForSetData) { michael@0: OleSetClipboard(nullptr); michael@0: } michael@0: return nsBaseClipboard::EmptyClipboard(aWhichClipboard); michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, michael@0: uint32_t aLength, michael@0: int32_t aWhichClipboard, michael@0: bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: if (aWhichClipboard != kGlobalClipboard || !aFlavorList) michael@0: return NS_OK; michael@0: michael@0: for (uint32_t i = 0;i < aLength; ++i) { michael@0: #ifdef DEBUG michael@0: if (strcmp(aFlavorList[i], kTextMime) == 0) michael@0: NS_WARNING ( "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD" ); michael@0: #endif michael@0: michael@0: UINT format = GetFormat(aFlavorList[i]); michael@0: if (IsClipboardFormatAvailable(format)) { michael@0: *_retval = true; michael@0: break; michael@0: } michael@0: else { michael@0: // We haven't found the exact flavor the client asked for, but maybe we can michael@0: // still find it from something else that's on the clipboard... michael@0: if (strcmp(aFlavorList[i], kUnicodeMime) == 0) { michael@0: // client asked for unicode and it wasn't present, check if we have CF_TEXT. michael@0: // We'll handle the actual data substitution in the data object. michael@0: if (IsClipboardFormatAvailable(GetFormat(kTextMime))) michael@0: *_retval = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: }