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