From 7cf059332715a0dc6c593e18e7cd729df293d915 Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Thu, 10 Dec 2009 00:15:56 -0500 Subject: [PATCH] Bug 338478. Add CF_HDROP support to Mozilla for file drag and drop. r=roc Bug 338478. Add CF_HDROP support to Mozilla for file drag and drop. r=roc --- widget/src/windows/Makefile.in | 4 + widget/src/windows/nsDataObj.cpp | 73 ++++- widget/src/windows/nsDataObj.h | 5 +- widget/src/windows/tests/Makefile.in | 63 ++++ widget/src/windows/tests/TestWinDND.cpp | 404 ++++++++++++++++++++++++ 5 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 widget/src/windows/tests/Makefile.in create mode 100644 widget/src/windows/tests/TestWinDND.cpp diff --git a/widget/src/windows/Makefile.in b/widget/src/windows/Makefile.in index d6fc33a77c6..a7b58faf5a1 100644 --- a/widget/src/windows/Makefile.in +++ b/widget/src/windows/Makefile.in @@ -132,6 +132,10 @@ ifndef WINCE ENABLE_CXX_EXCEPTIONS = 1 endif +ifdef ENABLE_TESTS +TOOL_DIRS += tests +endif + include $(topsrcdir)/config/rules.mk CXXFLAGS += $(MOZ_CAIRO_CFLAGS) diff --git a/widget/src/windows/nsDataObj.cpp b/widget/src/windows/nsDataObj.cpp index a20bbfa99d2..bb61007edc4 100644 --- a/widget/src/windows/nsDataObj.cpp +++ b/widget/src/windows/nsDataObj.cpp @@ -25,6 +25,7 @@ * Brodie Thiesfield * Masayuki Nakano * David Gardiner + * Kyle Huey * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -1367,7 +1368,8 @@ HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) PRBool found = PR_FALSE; while (NOERROR == m_enumFE->Next(1, &fe, &count) && dfInx < mDataFlavors.Length()) { - if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime)) { + if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime) || + mDataFlavors[dfInx].EqualsLiteral(kFileMime)) { found = PR_TRUE; break; } @@ -1377,6 +1379,75 @@ HRESULT nsDataObj::GetFile(FORMATETC& aFE, STGMEDIUM& aSTG) if (!found) return E_FAIL; + if (mDataFlavors[dfInx].EqualsLiteral(kNativeImageMime)) + return DropImage(aFE, aSTG); + return DropFile(aFE, aSTG); +} + +HRESULT nsDataObj::DropFile(FORMATETC& aFE, STGMEDIUM& aSTG) +{ + nsresult rv; + PRUint32 len = 0; + nsCOMPtr genericDataWrapper; + + mTransferable->GetTransferData(kFileMime, getter_AddRefs(genericDataWrapper), + &len); + nsCOMPtr file ( do_QueryInterface(genericDataWrapper) ); + + if (!file) + { + nsCOMPtr ptr(do_QueryInterface(genericDataWrapper)); + if (ptr) + ptr->GetData(getter_AddRefs(file)); + } + + if (!file) + return E_FAIL; + + aSTG.tymed = TYMED_HGLOBAL; + aSTG.pUnkForRelease = NULL; + + nsAutoString path; + rv = file->GetPath(path); + if (NS_FAILED(rv)) + return E_FAIL; + + PRUint32 allocLen = path.Length() + 2; + HGLOBAL hGlobalMemory = NULL; + PRUnichar *dest, *dest2; + + hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(DROPFILES) + + allocLen * sizeof(PRUnichar)); + if (!hGlobalMemory) + return E_FAIL; + + DROPFILES* pDropFile = (DROPFILES*)GlobalLock(hGlobalMemory); + + // First, populate the drop file structure + pDropFile->pFiles = sizeof(DROPFILES); //Offset to start of file name string + pDropFile->fNC = 0; + pDropFile->pt.x = 0; + pDropFile->pt.y = 0; + pDropFile->fWide = TRUE; + + // Copy the filename right after the DROPFILES structure + dest = (PRUnichar*)(((char*)pDropFile) + pDropFile->pFiles); + memcpy(dest, path.get(), (allocLen - 1) * sizeof(PRUnichar)); + + // Two null characters are needed at the end of the file name. + // Lookup the CF_HDROP shell clipboard format for more info. + // Add the second null character right after the first one. + dest[allocLen - 1] = L'\0'; + + GlobalUnlock(hGlobalMemory); + + aSTG.hGlobal = hGlobalMemory; + + return S_OK; +} + +HRESULT nsDataObj::DropImage(FORMATETC& aFE, STGMEDIUM& aSTG) +{ nsresult rv; PRUint32 len = 0; nsCOMPtr genericDataWrapper; diff --git a/widget/src/windows/nsDataObj.h b/widget/src/windows/nsDataObj.h index 46b786bfbf2..c69b0edcd64 100644 --- a/widget/src/windows/nsDataObj.h +++ b/widget/src/windows/nsDataObj.h @@ -56,7 +56,7 @@ // XXX for older version of PSDK where IAsyncOperation and related stuff is not available // but thisdefine should be removed when parocles config is updated #ifndef __IAsyncOperation_INTERFACE_DEFINED__ -// IAsyncOperation inerface definition +// IAsyncOperation interface definition EXTERN_C const IID IID_IAsyncOperation; MIDL_INTERFACE("3D8B0590-F691-11d2-8EA9-006097DF5BD4") @@ -228,6 +228,9 @@ class nsDataObj : public IDataObject, virtual HRESULT GetBitmap ( const nsACString& inFlavor, FORMATETC& FE, STGMEDIUM& STM); virtual HRESULT GetDib ( const nsACString& inFlavor, FORMATETC &, STGMEDIUM & aSTG ); virtual HRESULT GetMetafilePict(FORMATETC& FE, STGMEDIUM& STM); + + virtual HRESULT DropImage( FORMATETC& aFE, STGMEDIUM& aSTG ); + virtual HRESULT DropFile( FORMATETC& aFE, STGMEDIUM& aSTG ); virtual HRESULT GetUniformResourceLocator ( FORMATETC& aFE, STGMEDIUM& aSTG, PRBool aIsUnicode ) ; virtual HRESULT ExtractUniformResourceLocatorA ( FORMATETC& aFE, STGMEDIUM& aSTG ) ; diff --git a/widget/src/windows/tests/Makefile.in b/widget/src/windows/tests/Makefile.in new file mode 100644 index 00000000000..cbba2565c60 --- /dev/null +++ b/widget/src/windows/tests/Makefile.in @@ -0,0 +1,63 @@ +# +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Mozilla Windows Drag and Drop Tests. +# +# The Initial Developer of the Original Code is +# Kyle Huey +# Portions created by the Initial Developer are Copyright (C) 2009 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +ifndef MOZ_ENABLE_LIBXUL +LOCAL_INCLUDES = -I$(srcdir)/../ \ + -I$(srcdir)/../../xpwidgets \ + $(NULL) + +LIBS = ../$(LIB_PREFIX)widget_windows.$(LIB_SUFFIX) \ + ../../xpwidgets/$(LIB_PREFIX)xpwidgets_s.$(LIB_SUFFIX) \ + $(DIST)/lib/$(LIB_PREFIX)thebes.$(LIB_SUFFIX) \ + $(XPCOM_LIBS) \ + $(MOZ_UNICHARUTIL_LIBS) \ + $(NULL) + +OS_LIBS += $(call EXPAND_LIBNAME,ole32 shell32) + +CPP_UNIT_TESTS = TestWinDND.cpp \ + $(NULL) +endif + +include $(topsrcdir)/config/rules.mk diff --git a/widget/src/windows/tests/TestWinDND.cpp b/widget/src/windows/tests/TestWinDND.cpp new file mode 100644 index 00000000000..cbf64170c58 --- /dev/null +++ b/widget/src/windows/tests/TestWinDND.cpp @@ -0,0 +1,404 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Kyle Huey + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#define MOZILLA_INTERNAL_API + +#include +#include + +#include "TestHarness.h" +#include "nsIArray.h" +#include "nsILocalFile.h" +#include "nsNetUtil.h" +#include "nsISupportsPrimitives.h" +#include "nsIFileURL.h" +#include "nsITransferable.h" +#include "nsClipboard.h" +#include "nsDataObjCollection.h" + +nsIFile* xferFile; + +nsresult CheckValidHDROP(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + DROPFILES* pDropFiles; + pDropFiles = (DROPFILES*)GlobalLock(hGlobal); + if (!pDropFiles) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + if (pDropFiles->pFiles != sizeof(DROPFILES)) + fail("DROPFILES struct has wrong size"); + + if (pDropFiles->fWide != true) { + fail("Received data is not Unicode"); + return NS_ERROR_UNEXPECTED; + } + + nsString s; + s = (PRUnichar*)((char*)pDropFiles + pDropFiles->pFiles); + nsresult rv; + nsCOMPtr localFile( + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + rv = localFile->InitWithPath(s); + if (NS_FAILED(rv)) { + fail("File could not be opened"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult CheckValidTEXT(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + char* pText; + pText = (char*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsCString string; + string = pText; + + if (!string.Equals(NS_LITERAL_CSTRING("Mozilla can drag and drop"))) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult CheckValidUNICODE(STGMEDIUM* pSTG) +{ + if (pSTG->tymed != TYMED_HGLOBAL) { + fail("Received data is not in an HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + HGLOBAL hGlobal = pSTG->hGlobal; + PRUnichar* pText; + pText = (PRUnichar*)GlobalLock(hGlobal); + if (!pText) { + fail("There is no data at the given HGLOBAL"); + return NS_ERROR_UNEXPECTED; + } + + nsString string; + string = pText; + + if (!string.Equals(NS_LITERAL_STRING("Mozilla can drag and drop"))) { + fail("Text passed through drop object wrong"); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult GetTransferableFile(nsCOMPtr& pTransferable) +{ + nsresult rv; + + nsCOMPtr genericWrapper = do_QueryInterface(xferFile); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + rv = pTransferable->SetTransferData("application/x-moz-file", genericWrapper, + 0); + return rv; +} + +nsresult GetTransferableText(nsCOMPtr& pTransferable) +{ + nsresult rv; + NS_NAMED_LITERAL_STRING(mozString, "Mozilla can drag and drop"); + nsCOMPtr xferString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID); + rv = xferString->SetData(mozString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr genericWrapper = do_QueryInterface(xferString); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + rv = pTransferable->SetTransferData("text/unicode", genericWrapper, + mozString.Length() * sizeof(PRUnichar)); + return rv; +} + +nsresult GetTransferableURI(nsCOMPtr& pTransferable) +{ + nsresult rv; + + nsCOMPtr xferURI; + + rv = NS_NewURI(getter_AddRefs(xferURI), "http://www.mozilla.org"); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr genericWrapper = do_QueryInterface(xferURI); + + pTransferable = do_CreateInstance("@mozilla.org/widget/transferable;1"); + rv = pTransferable->SetTransferData("text/x-moz-url", genericWrapper, 0); + return rv; +} + +nsresult MakeDataObject(nsISupportsArray* transferableArray, + nsRefPtr& itemToDrag) +{ + nsresult rv; + PRUint32 itemCount = 0; + + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), "http://www.mozilla.org"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = transferableArray->Count(&itemCount); + NS_ENSURE_SUCCESS(rv, rv); + + // Copied more or less exactly from nsDragService::InvokeDragSession + // This is what lets us play fake Drag Service for the test + if (itemCount > 1) { + nsDataObjCollection * dataObjCollection = new nsDataObjCollection(); + if (!dataObjCollection) + return NS_ERROR_OUT_OF_MEMORY; + itemToDrag = dataObjCollection; + for (PRUint32 i=0; i supports; + transferableArray->GetElementAt(i, getter_AddRefs(supports)); + nsCOMPtr trans(do_QueryInterface(supports)); + if (trans) { + nsRefPtr dataObj; + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(dataObj), uri); + NS_ENSURE_SUCCESS(rv, rv); + // Add the flavors to the collection object too + rv = nsClipboard::SetupNativeDataObject(trans, dataObjCollection); + NS_ENSURE_SUCCESS(rv, rv); + + dataObjCollection->AddDataObject(dataObj); + } + } + } // if dragging multiple items + else { + nsCOMPtr supports; + transferableArray->GetElementAt(0, getter_AddRefs(supports)); + nsCOMPtr trans(do_QueryInterface(supports)); + if (trans) { + rv = nsClipboard::CreateNativeDataObject(trans, + getter_AddRefs(itemToDrag), + uri); + NS_ENSURE_SUCCESS(rv, rv); + } + } // else dragging a single object + return rv; +} + +nsresult Do_CheckOneFile() +{ + nsresult rv; + nsCOMPtr transferable; + nsCOMPtr transferableArray; + nsCOMPtr genericWrapper; + nsRefPtr dataObj; + rv = NS_NewISupportsArray(getter_AddRefs(transferableArray)); + if (NS_FAILED(rv)) { + fail("Could not create the necessary nsISupportsArray"); + return rv; + } + + rv = GetTransferableFile(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("File data object does not support the file data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("File data object did not provide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidHDROP(stg); + if (NS_FAILED(rv)) { + fail("HDROP was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + return S_OK; +} + +nsresult Do_CheckOneString() +{ + nsresult rv; + nsCOMPtr transferable; + nsCOMPtr transferableArray; + nsCOMPtr genericWrapper; + nsRefPtr dataObj; + rv = NS_NewISupportsArray(getter_AddRefs(transferableArray)); + if (NS_FAILED(rv)) { + fail("Could not create the necessary nsISupportsArray"); + return rv; + } + + rv = GetTransferableText(transferable); + if (NS_FAILED(rv)) { + fail("Could not create the proper nsITransferable!"); + return rv; + } + genericWrapper = do_QueryInterface(transferable); + rv = transferableArray->AppendElement(genericWrapper); + if (NS_FAILED(rv)) { + fail("Could not append element to transferable array"); + return rv; + } + + rv = MakeDataObject(transferableArray, dataObj); + if (NS_FAILED(rv)) { + fail("Could not create data object"); + return rv; + } + + FORMATETC fe; + SET_FORMATETC(fe, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the ASCII text data type!"); + return NS_ERROR_UNEXPECTED; + } + + STGMEDIUM* stg; + stg = (STGMEDIUM*)CoTaskMemAlloc(sizeof(STGMEDIUM)); + HRESULT hr; + if ((hr = dataObj->GetData(&fe, stg)) != S_OK) { + fail("String data object did not provide ASCII data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidTEXT(stg); + if (NS_FAILED(rv)) { + fail("TEXT was invalid"); + return rv; + } + + ReleaseStgMedium(stg); + + SET_FORMATETC(fe, CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL); + if (dataObj->QueryGetData(&fe) != S_OK) { + fail("String data object does not support the wide text data type!"); + return NS_ERROR_UNEXPECTED; + } + + if (dataObj->GetData(&fe, stg) != S_OK) { + fail("String data object did not provide wide data on request"); + return NS_ERROR_UNEXPECTED; + } + + rv = CheckValidUNICODE(stg); + if (NS_FAILED(rv)) { + fail("UNICODE was invalid"); + return rv; + } + + return S_OK; +} + +// This function performs basic drop tests, testing a data object consisting +// of one transferable +nsresult Do_Test1() +{ + nsresult rv = NS_OK; + nsresult workingrv; + + workingrv = Do_CheckOneFile(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on a single file"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working file drag object!"); + } + + workingrv = Do_CheckOneString(); + if (NS_FAILED(workingrv)) { + fail("Drag object tests failed on a single string"); + rv = NS_ERROR_UNEXPECTED; + } else { + passed("Successfully created a working string drag object!"); + } + + return rv; +} + +int main(int argc, char** argv) +{ + ScopedXPCOM xpcom("Test Windows Drag and Drop"); + + nsCOMPtr file; + file = xpcom.GetProfileDirectory(); + xferFile = file; + + if (NS_SUCCEEDED(Do_Test1())) + passed("Basic Drag and Drop data type tests succeeded!"); + + return gFailCount; +}