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: /* michael@0: Notes to self: michael@0: michael@0: - at some point, strings will be accessible from JS, so we won't have to wrap michael@0: flavors in an nsISupportsCString. Until then, we're kinda stuck with michael@0: this crappy API of nsISupportsArrays. michael@0: michael@0: */ michael@0: michael@0: michael@0: #include "nsTransferable.h" michael@0: #include "nsString.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsTArray.h" michael@0: #include "nsIFormatConverter.h" michael@0: #include "nsIComponentManager.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsXPCOM.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsMemory.h" michael@0: #include "nsPrimitiveHelpers.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsDirectoryService.h" michael@0: #include "nsCRT.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIOutputStream.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsIFile.h" michael@0: #include "nsILoadContext.h" michael@0: #include "nsAutoPtr.h" michael@0: michael@0: NS_IMPL_ISUPPORTS(nsTransferable, nsITransferable) michael@0: michael@0: uint32_t GetDataForFlavor (const nsTArray& aArray, michael@0: const char* aDataFlavor) michael@0: { michael@0: for (uint32_t i = 0 ; i < aArray.Length () ; ++i) { michael@0: if (aArray[i].GetFlavor().Equals (aDataFlavor)) michael@0: return i; michael@0: } michael@0: michael@0: return aArray.NoIndex; michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: DataStruct::~DataStruct() michael@0: { michael@0: if (mCacheFileName) free(mCacheFileName); michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@10: void michael@10: DataStruct::SetData ( nsISupports* aData, uint32_t aDataLen, bool aIsPrivBrowsing ) michael@10: { michael@10: // Now, check to see if we consider the data to be "too large" michael@10: // as well as ensuring that private browsing mode is disabled michael@10: if (aDataLen > kLargeDatasetSize && aIsPrivBrowsing == false) { michael@10: // if so, cache it to disk instead of memory michael@10: if ( NS_SUCCEEDED(WriteCache(aData, aDataLen)) ) michael@10: return; michael@10: else michael@10: NS_WARNING("Oh no, couldn't write data to the cache file"); michael@10: } michael@10: michael@10: mData = aData; michael@10: mDataLen = aDataLen; michael@10: } michael@10: michael@10: michael@10: //------------------------------------------------------------------------- michael@0: void michael@0: DataStruct::GetData ( nsISupports** aData, uint32_t *aDataLen ) michael@0: { michael@0: // check here to see if the data is cached on disk michael@0: if ( !mData && mCacheFileName ) { michael@0: // if so, read it in and pass it back michael@0: // ReadCache creates memory and copies the data into it. michael@0: if ( NS_SUCCEEDED(ReadCache(aData, aDataLen)) ) michael@0: return; michael@0: else { michael@0: // oh shit, something went horribly wrong here. michael@0: NS_WARNING("Oh no, couldn't read data in from the cache file"); michael@0: *aData = nullptr; michael@0: *aDataLen = 0; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: *aData = mData; michael@0: if ( mData ) michael@0: NS_ADDREF(*aData); michael@0: *aDataLen = mDataLen; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: already_AddRefed michael@0: DataStruct::GetFileSpec(const char* aFileName) michael@0: { michael@0: nsCOMPtr cacheFile; michael@0: NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(cacheFile)); michael@0: michael@0: if (!cacheFile) michael@0: return nullptr; michael@0: michael@0: // if the param aFileName contains a name we should use that michael@0: // because the file probably already exists michael@0: // otherwise create a unique name michael@0: if (!aFileName) { michael@0: cacheFile->AppendNative(NS_LITERAL_CSTRING("clipboardcache")); michael@0: cacheFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: } else { michael@0: cacheFile->AppendNative(nsDependentCString(aFileName)); michael@0: } michael@0: michael@0: return cacheFile.forget(); michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult michael@0: DataStruct::WriteCache(nsISupports* aData, uint32_t aDataLen) michael@0: { michael@0: // Get a new path and file to the temp directory michael@0: nsCOMPtr cacheFile = GetFileSpec(mCacheFileName); michael@0: if (cacheFile) { michael@0: // remember the file name michael@0: if (!mCacheFileName) { michael@0: nsXPIDLCString fName; michael@0: cacheFile->GetNativeLeafName(fName); michael@0: mCacheFileName = strdup(fName); michael@0: } michael@0: michael@0: // write out the contents of the clipboard michael@0: // to the file michael@0: //uint32_t bytes; michael@0: nsCOMPtr outStr; michael@0: michael@0: NS_NewLocalFileOutputStream(getter_AddRefs(outStr), michael@0: cacheFile); michael@0: michael@0: if (!outStr) return NS_ERROR_FAILURE; michael@0: michael@0: void* buff = nullptr; michael@0: nsPrimitiveHelpers::CreateDataFromPrimitive ( mFlavor.get(), aData, &buff, aDataLen ); michael@0: if ( buff ) { michael@0: uint32_t ignored; michael@0: outStr->Write(reinterpret_cast(buff), aDataLen, &ignored); michael@0: nsMemory::Free(buff); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: nsresult michael@0: DataStruct::ReadCache(nsISupports** aData, uint32_t* aDataLen) michael@0: { michael@0: // if we don't have a cache filename we are out of luck michael@0: if (!mCacheFileName) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // get the path and file name michael@0: nsCOMPtr cacheFile = GetFileSpec(mCacheFileName); michael@0: bool exists; michael@0: if ( cacheFile && NS_SUCCEEDED(cacheFile->Exists(&exists)) && exists ) { michael@0: // get the size of the file michael@0: int64_t fileSize; michael@0: int64_t max32 = 0xFFFFFFFF; michael@0: cacheFile->GetFileSize(&fileSize); michael@0: if (fileSize > max32) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: uint32_t size = uint32_t(fileSize); michael@0: // create new memory for the large clipboard data michael@0: nsAutoArrayPtr data(new char[size]); michael@0: if ( !data ) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // now read it all in michael@0: nsCOMPtr inStr; michael@0: NS_NewLocalFileInputStream( getter_AddRefs(inStr), michael@0: cacheFile); michael@0: michael@0: if (!cacheFile) return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = inStr->Read(data, fileSize, aDataLen); michael@0: michael@0: // make sure we got all the data ok michael@0: if (NS_SUCCEEDED(rv) && *aDataLen == size) { michael@0: nsPrimitiveHelpers::CreatePrimitiveForData ( mFlavor.get(), data, fileSize, aData ); michael@0: return *aData ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // zero the return params michael@0: *aData = nullptr; michael@0: *aDataLen = 0; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // michael@0: // Transferable constructor michael@0: // michael@0: //------------------------------------------------------------------------- michael@0: nsTransferable::nsTransferable() michael@0: : mPrivateData(false) michael@0: #ifdef DEBUG michael@0: , mInitialized(false) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: //------------------------------------------------------------------------- michael@0: // michael@0: // Transferable destructor michael@0: // michael@0: //------------------------------------------------------------------------- michael@0: nsTransferable::~nsTransferable() michael@0: { michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransferable::Init(nsILoadContext* aContext) michael@0: { michael@0: MOZ_ASSERT(!mInitialized); michael@0: michael@0: if (aContext) { michael@0: mPrivateData = aContext->UsePrivateBrowsing(); michael@0: } michael@0: #ifdef DEBUG michael@0: mInitialized = true; michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: // michael@0: // GetTransferDataFlavors michael@0: // michael@0: // Returns a copy of the internal list of flavors. This does NOT take into michael@0: // account any converter that may be registered. This list consists of michael@0: // nsISupportsCString objects so that the flavor list can be accessed from JS. michael@0: // michael@0: nsresult michael@0: nsTransferable::GetTransferDataFlavors(nsISupportsArray ** aDataFlavorList) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: nsresult rv = NS_NewISupportsArray ( aDataFlavorList ); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: for ( uint32_t i=0; i flavorWrapper = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); michael@0: if ( flavorWrapper ) { michael@0: flavorWrapper->SetData ( data.GetFlavor() ); michael@0: nsCOMPtr genericWrapper ( do_QueryInterface(flavorWrapper) ); michael@0: (*aDataFlavorList)->AppendElement( genericWrapper ); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // GetTransferData michael@0: // michael@0: // Returns the data of the requested flavor, obtained from either having the data on hand or michael@0: // using a converter to get it. The data is wrapped in a nsISupports primitive so that it is michael@0: // accessible from JS. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::GetTransferData(const char *aFlavor, nsISupports **aData, uint32_t *aDataLen) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr savedData; michael@0: michael@0: // first look and see if the data is present in one of the intrinsic flavors michael@0: uint32_t i; michael@0: for (i = 0; i < mDataArray.Length(); ++i ) { michael@0: DataStruct& data = mDataArray.ElementAt(i); michael@0: if ( data.GetFlavor().Equals(aFlavor) ) { michael@0: nsCOMPtr dataBytes; michael@0: uint32_t len; michael@0: data.GetData(getter_AddRefs(dataBytes), &len); michael@0: if (len == kFlavorHasDataProvider && dataBytes) { michael@0: // do we have a data provider? michael@0: nsCOMPtr dataProvider = do_QueryInterface(dataBytes); michael@0: if (dataProvider) { michael@0: rv = dataProvider->GetFlavorData(this, aFlavor, michael@0: getter_AddRefs(dataBytes), &len); michael@0: if (NS_FAILED(rv)) michael@0: break; // the provider failed. fall into the converter code below. michael@0: } michael@0: } michael@0: if (dataBytes && len > 0) { // XXXmats why is zero length not ok? michael@0: *aDataLen = len; michael@0: dataBytes.forget(aData); michael@0: return NS_OK; michael@0: } michael@0: savedData = dataBytes; // return this if format converter fails michael@0: break; michael@0: } michael@0: } michael@0: michael@0: bool found = false; michael@0: michael@0: // if not, try using a format converter to get the requested flavor michael@0: if ( mFormatConv ) { michael@0: for (i = 0; i < mDataArray.Length(); ++i) { michael@0: DataStruct& data = mDataArray.ElementAt(i); michael@0: bool canConvert = false; michael@0: mFormatConv->CanConvert(data.GetFlavor().get(), aFlavor, &canConvert); michael@0: if ( canConvert ) { michael@0: nsCOMPtr dataBytes; michael@0: uint32_t len; michael@0: data.GetData(getter_AddRefs(dataBytes), &len); michael@0: if (len == kFlavorHasDataProvider && dataBytes) { michael@0: // do we have a data provider? michael@0: nsCOMPtr dataProvider = do_QueryInterface(dataBytes); michael@0: if (dataProvider) { michael@0: rv = dataProvider->GetFlavorData(this, aFlavor, michael@0: getter_AddRefs(dataBytes), &len); michael@0: if (NS_FAILED(rv)) michael@0: break; // give up michael@0: } michael@0: } michael@0: mFormatConv->Convert(data.GetFlavor().get(), dataBytes, len, aFlavor, aData, aDataLen); michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // for backward compatibility michael@0: if (!found) { michael@0: savedData.forget(aData); michael@0: *aDataLen = 0; michael@0: } michael@0: michael@0: return found ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: // michael@0: // GetAnyTransferData michael@0: // michael@0: // Returns the data of the first flavor found. Caller is responsible for deleting the michael@0: // flavor string. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::GetAnyTransferData(char **aFlavor, nsISupports **aData, uint32_t *aDataLen) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aFlavor && aData && aDataLen); michael@0: michael@0: for ( uint32_t i=0; i < mDataArray.Length(); ++i ) { michael@0: DataStruct& data = mDataArray.ElementAt(i); michael@0: if (data.IsDataAvailable()) { michael@0: *aFlavor = ToNewCString(data.GetFlavor()); michael@0: data.GetData(aData, aDataLen); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: // michael@0: // SetTransferData michael@0: // michael@0: // michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::SetTransferData(const char *aFlavor, nsISupports *aData, uint32_t aDataLen) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG(aFlavor); michael@0: michael@0: // first check our intrinsic flavors to see if one has been registered. michael@0: uint32_t i = 0; michael@0: for (i = 0; i < mDataArray.Length(); ++i) { michael@0: DataStruct& data = mDataArray.ElementAt(i); michael@0: if ( data.GetFlavor().Equals(aFlavor) ) { michael@13: data.SetData ( aData, aDataLen, mPrivateData); michael@13: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // if not, try using a format converter to find a flavor to put the data in michael@0: if ( mFormatConv ) { michael@0: for (i = 0; i < mDataArray.Length(); ++i) { michael@0: DataStruct& data = mDataArray.ElementAt(i); michael@0: bool canConvert = false; michael@0: mFormatConv->CanConvert(aFlavor, data.GetFlavor().get(), &canConvert); michael@0: michael@0: if ( canConvert ) { michael@0: nsCOMPtr ConvertedData; michael@0: uint32_t ConvertedLen; michael@0: mFormatConv->Convert(aFlavor, aData, aDataLen, data.GetFlavor().get(), getter_AddRefs(ConvertedData), &ConvertedLen); michael@13: data.SetData(ConvertedData, ConvertedLen, mPrivateData); michael@13: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Can't set data neither directly nor through converter. Just add this flavor and try again michael@0: nsresult result = NS_ERROR_FAILURE; michael@0: if ( NS_SUCCEEDED(AddDataFlavor(aFlavor)) ) michael@0: result = SetTransferData (aFlavor, aData, aDataLen); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: michael@0: // michael@0: // AddDataFlavor michael@0: // michael@0: // Adds a data flavor to our list with no data. Error if it already exists. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::AddDataFlavor(const char *aDataFlavor) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: if (GetDataForFlavor (mDataArray, aDataFlavor) != mDataArray.NoIndex) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Create a new "slot" for the data michael@0: mDataArray.AppendElement(DataStruct ( aDataFlavor )); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // RemoveDataFlavor michael@0: // michael@0: // Removes a data flavor (and causes the data to be destroyed). Error if michael@0: // the requested flavor is not present. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::RemoveDataFlavor(const char *aDataFlavor) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: uint32_t idx; michael@0: if ((idx = GetDataForFlavor(mDataArray, aDataFlavor)) != mDataArray.NoIndex) { michael@0: mDataArray.RemoveElementAt (idx); michael@0: return NS_OK; michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * michael@0: * michael@0: */ michael@0: NS_IMETHODIMP michael@0: nsTransferable::IsLargeDataSet(bool *_retval) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * michael@0: * michael@0: */ michael@0: NS_IMETHODIMP nsTransferable::SetConverter(nsIFormatConverter * aConverter) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: mFormatConv = aConverter; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * michael@0: * michael@0: */ michael@0: NS_IMETHODIMP nsTransferable::GetConverter(nsIFormatConverter * *aConverter) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aConverter); michael@0: *aConverter = mFormatConv; michael@0: NS_IF_ADDREF(*aConverter); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // FlavorsTransferableCanImport michael@0: // michael@0: // Computes a list of flavors that the transferable can accept into it, either through michael@0: // intrinsic knowledge or input data converters. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::FlavorsTransferableCanImport(nsISupportsArray **_retval) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: michael@0: // Get the flavor list, and on to the end of it, append the list of flavors we michael@0: // can also get to through a converter. This is so that we can just walk the list michael@0: // in one go, looking for the desired flavor. michael@0: GetTransferDataFlavors(_retval); // addrefs michael@0: nsCOMPtr converter; michael@0: GetConverter(getter_AddRefs(converter)); michael@0: if ( converter ) { michael@0: nsCOMPtr convertedList; michael@0: converter->GetInputDataFlavors(getter_AddRefs(convertedList)); michael@0: michael@0: if ( convertedList ) { michael@0: uint32_t importListLen; michael@0: convertedList->Count(&importListLen); michael@0: michael@0: for ( uint32_t i=0; i < importListLen; ++i ) { michael@0: nsCOMPtr genericFlavor; michael@0: convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); michael@0: michael@0: nsCOMPtr flavorWrapper ( do_QueryInterface (genericFlavor) ); michael@0: nsAutoCString flavorStr; michael@0: flavorWrapper->GetData( flavorStr ); michael@0: michael@0: if (GetDataForFlavor (mDataArray, flavorStr.get()) michael@0: == mDataArray.NoIndex) // Don't append if already in intrinsic list michael@0: (*_retval)->AppendElement (genericFlavor); michael@0: } // foreach flavor that can be converted to michael@0: } michael@0: } // if a converter exists michael@0: michael@0: return NS_OK; michael@0: } // FlavorsTransferableCanImport michael@0: michael@0: michael@0: // michael@0: // FlavorsTransferableCanExport michael@0: // michael@0: // Computes a list of flavors that the transferable can export, either through michael@0: // intrinsic knowledge or output data converters. michael@0: // michael@0: NS_IMETHODIMP michael@0: nsTransferable::FlavorsTransferableCanExport(nsISupportsArray **_retval) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: michael@0: // Get the flavor list, and on to the end of it, append the list of flavors we michael@0: // can also get to through a converter. This is so that we can just walk the list michael@0: // in one go, looking for the desired flavor. michael@0: GetTransferDataFlavors(_retval); // addrefs michael@0: nsCOMPtr converter; michael@0: GetConverter(getter_AddRefs(converter)); michael@0: if ( converter ) { michael@0: nsCOMPtr convertedList; michael@0: converter->GetOutputDataFlavors(getter_AddRefs(convertedList)); michael@0: michael@0: if ( convertedList ) { michael@0: uint32_t importListLen; michael@0: convertedList->Count(&importListLen); michael@0: michael@0: for ( uint32_t i=0; i < importListLen; ++i ) { michael@0: nsCOMPtr genericFlavor; michael@0: convertedList->GetElementAt ( i, getter_AddRefs(genericFlavor) ); michael@0: michael@0: nsCOMPtr flavorWrapper ( do_QueryInterface (genericFlavor) ); michael@0: nsAutoCString flavorStr; michael@0: flavorWrapper->GetData( flavorStr ); michael@0: michael@0: if (GetDataForFlavor (mDataArray, flavorStr.get()) michael@0: == mDataArray.NoIndex) // Don't append if already in intrinsic list michael@0: (*_retval)->AppendElement (genericFlavor); michael@0: } // foreach flavor that can be converted to michael@0: } michael@0: } // if a converter exists michael@0: michael@0: return NS_OK; michael@0: } // FlavorsTransferableCanExport michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransferable::GetIsPrivateData(bool *aIsPrivateData) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: NS_ENSURE_ARG_POINTER(aIsPrivateData); michael@0: michael@0: *aIsPrivateData = mPrivateData; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTransferable::SetIsPrivateData(bool aIsPrivateData) michael@0: { michael@0: MOZ_ASSERT(mInitialized); michael@0: michael@0: mPrivateData = aIsPrivateData; michael@0: michael@0: return NS_OK; michael@0: } michael@0: