diff options
Diffstat (limited to 'widget/nsTransferable.cpp')
-rw-r--r-- | widget/nsTransferable.cpp | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/widget/nsTransferable.cpp b/widget/nsTransferable.cpp new file mode 100644 index 0000000000..8f1b6f3977 --- /dev/null +++ b/widget/nsTransferable.cpp @@ -0,0 +1,535 @@ +/* -*- 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/. */ + +/* +Notes to self: + +- at some point, strings will be accessible from JS, so we won't have to wrap + flavors in an nsISupportsCString. Until then, we're kinda stuck with + this crappy API of nsIArrays. + +*/ + +#include "nsTransferable.h" +#include "nsAnonymousTemporaryFile.h" +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsTArray.h" +#include "nsIFormatConverter.h" +#include "nsIContentPolicy.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsPrimitiveHelpers.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryService.h" +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsILoadContext.h" +#include "nsXULAppAPI.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsTransferable, nsITransferable) + +DataStruct::DataStruct(DataStruct&& aRHS) + : mData(aRHS.mData.forget()), + mCacheFD(aRHS.mCacheFD), + mFlavor(aRHS.mFlavor) { + aRHS.mCacheFD = nullptr; +} + +//------------------------------------------------------------------------- +DataStruct::~DataStruct() { + if (mCacheFD) { + PR_Close(mCacheFD); + } +} + +//------------------------------------------------------------------------- + +void DataStruct::SetData(nsISupports* aData, bool aIsPrivateData) { + // Now, check to see if we consider the data to be "too large" + // as well as ensuring that private browsing mode is disabled. + // File IO is not allowed in content processes. + if (!aIsPrivateData && XRE_IsParentProcess()) { + void* data = nullptr; + uint32_t dataLen = 0; + nsPrimitiveHelpers::CreateDataFromPrimitive(mFlavor, aData, &data, + &dataLen); + + if (dataLen > kLargeDatasetSize) { + // Too large, cache it to disk instead of memory. + if (NS_SUCCEEDED(WriteCache(data, dataLen))) { + free(data); + // Clear previously set small data. + mData = nullptr; + return; + } + + NS_WARNING("Oh no, couldn't write data to the cache file"); + } + + free(data); + } + + if (mCacheFD) { + // Clear previously set big data. + PR_Close(mCacheFD); + mCacheFD = nullptr; + } + + mData = aData; +} + +//------------------------------------------------------------------------- +void DataStruct::GetData(nsISupports** aData) { + // check here to see if the data is cached on disk + if (mCacheFD) { + // if so, read it in and pass it back + // ReadCache creates memory and copies the data into it. + if (NS_SUCCEEDED(ReadCache(aData))) { + return; + } + + // oh shit, something went horribly wrong here. + NS_WARNING("Oh no, couldn't read data in from the cache file"); + *aData = nullptr; + PR_Close(mCacheFD); + mCacheFD = nullptr; + return; + } + + nsCOMPtr<nsISupports> data = mData; + data.forget(aData); +} + +//------------------------------------------------------------------------- +nsresult DataStruct::WriteCache(void* aData, uint32_t aDataLen) { + MOZ_ASSERT(aData && aDataLen); + MOZ_ASSERT(aDataLen <= uint32_t(std::numeric_limits<int32_t>::max()), + "too large size for PR_Write"); + + nsresult rv; + if (!mCacheFD) { + rv = NS_OpenAnonymousTemporaryFile(&mCacheFD); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } else if (PR_Seek64(mCacheFD, 0, PR_SEEK_SET) == -1) { + return NS_ERROR_FAILURE; + } + + // Write out the contents of the clipboard to the file. + int32_t written = PR_Write(mCacheFD, aData, aDataLen); + if (written == int32_t(aDataLen)) { + return NS_OK; + } + + PR_Close(mCacheFD); + mCacheFD = nullptr; + return NS_ERROR_FAILURE; +} + +//------------------------------------------------------------------------- +nsresult DataStruct::ReadCache(nsISupports** aData) { + if (!mCacheFD) { + return NS_ERROR_FAILURE; + } + + PRFileInfo fileInfo; + if (PR_GetOpenFileInfo(mCacheFD, &fileInfo) != PR_SUCCESS) { + return NS_ERROR_FAILURE; + } + if (PR_Seek64(mCacheFD, 0, PR_SEEK_SET) == -1) { + return NS_ERROR_FAILURE; + } + uint32_t fileSize = fileInfo.size; + + auto data = MakeUnique<char[]>(fileSize); + if (!data) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t actual = PR_Read(mCacheFD, data.get(), fileSize); + if (actual != fileSize) { + return NS_ERROR_FAILURE; + } + + nsPrimitiveHelpers::CreatePrimitiveForData(mFlavor, data.get(), fileSize, + aData); + return NS_OK; +} + +//------------------------------------------------------------------------- +// +// Transferable constructor +// +//------------------------------------------------------------------------- +nsTransferable::nsTransferable() + : mPrivateData(false), + mContentPolicyType(nsIContentPolicy::TYPE_OTHER) +#ifdef DEBUG + , + mInitialized(false) +#endif +{ +} + +//------------------------------------------------------------------------- +// +// Transferable destructor +// +//------------------------------------------------------------------------- +nsTransferable::~nsTransferable() = default; + +NS_IMETHODIMP +nsTransferable::Init(nsILoadContext* aContext) { + MOZ_ASSERT(!mInitialized); + + if (aContext) { + mPrivateData = aContext->UsePrivateBrowsing(); + } else { + // without aContext here to provide PrivateBrowsing information, we defer to + // the active configured setting + mPrivateData = StaticPrefs::browser_privatebrowsing_autostart(); + } +#ifdef DEBUG + mInitialized = true; +#endif + return NS_OK; +} + +// +// GetTransferDataFlavors +// +// Returns a copy of the internal list of flavors. This does NOT take into +// account any converter that may be registered. +// +void nsTransferable::GetTransferDataFlavors(nsTArray<nsCString>& aFlavors) { + MOZ_ASSERT(mInitialized); + + for (size_t i = 0; i < mDataArray.Length(); ++i) { + DataStruct& data = mDataArray.ElementAt(i); + aFlavors.AppendElement(data.GetFlavor()); + } +} + +Maybe<size_t> nsTransferable::FindDataFlavor(const char* aFlavor) { + nsDependentCString flavor(aFlavor); + + for (size_t i = 0; i < mDataArray.Length(); ++i) { + if (mDataArray[i].GetFlavor().Equals(flavor)) { + return Some(i); + } + } + + return Nothing(); +} + +// +// GetTransferData +// +// Returns the data of the requested flavor, obtained from either having the +// data on hand or using a converter to get it. The data is wrapped in a +// nsISupports primitive so that it is accessible from JS. +// +NS_IMETHODIMP +nsTransferable::GetTransferData(const char* aFlavor, nsISupports** aData) { + MOZ_ASSERT(mInitialized); + + *aData = nullptr; + + nsresult rv = NS_OK; + + // First look and see if the data is present in one of the intrinsic flavors. + if (Maybe<size_t> index = FindDataFlavor(aFlavor)) { + nsCOMPtr<nsISupports> dataBytes; + mDataArray[index.value()].GetData(getter_AddRefs(dataBytes)); + + // Do we have a (lazy) data provider? + if (nsCOMPtr<nsIFlavorDataProvider> dataProvider = + do_QueryInterface(dataBytes)) { + rv = + dataProvider->GetFlavorData(this, aFlavor, getter_AddRefs(dataBytes)); + if (NS_FAILED(rv)) { + dataBytes = nullptr; + // The provider failed, fall into the converter code below. + } + } + + if (dataBytes) { + dataBytes.forget(aData); + return NS_OK; + } + + // Empty data + } + + // If not, try using a format converter to get the requested flavor. + if (mFormatConv) { + for (size_t i = 0; i < mDataArray.Length(); ++i) { + DataStruct& data = mDataArray.ElementAt(i); + bool canConvert = false; + mFormatConv->CanConvert(data.GetFlavor().get(), aFlavor, &canConvert); + if (canConvert) { + nsCOMPtr<nsISupports> dataBytes; + data.GetData(getter_AddRefs(dataBytes)); + + // Do we have a (lazy) data provider? + if (nsCOMPtr<nsIFlavorDataProvider> dataProvider = + do_QueryInterface(dataBytes)) { + rv = dataProvider->GetFlavorData(this, aFlavor, + getter_AddRefs(dataBytes)); + if (NS_FAILED(rv)) { + // Give up. + return rv; + } + } + + return mFormatConv->Convert(data.GetFlavor().get(), dataBytes, aFlavor, + aData); + } + } + } + + return NS_ERROR_FAILURE; +} + +// +// GetAnyTransferData +// +// Returns the data of the first flavor found. Caller is responsible for +// deleting the flavor string. +// +NS_IMETHODIMP +nsTransferable::GetAnyTransferData(nsACString& aFlavor, nsISupports** aData) { + MOZ_ASSERT(mInitialized); + + for (size_t i = 0; i < mDataArray.Length(); ++i) { + DataStruct& data = mDataArray.ElementAt(i); + if (data.IsDataAvailable()) { + aFlavor.Assign(data.GetFlavor()); + data.GetData(aData); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +// +// SetTransferData +// +// +// +NS_IMETHODIMP +nsTransferable::SetTransferData(const char* aFlavor, nsISupports* aData) { + MOZ_ASSERT(mInitialized); + + // first check our intrinsic flavors to see if one has been registered. + if (Maybe<size_t> index = FindDataFlavor(aFlavor)) { + DataStruct& data = mDataArray.ElementAt(index.value()); + data.SetData(aData, mPrivateData); + return NS_OK; + } + + // if not, try using a format converter to find a flavor to put the data in + if (mFormatConv) { + for (size_t i = 0; i < mDataArray.Length(); ++i) { + DataStruct& data = mDataArray.ElementAt(i); + bool canConvert = false; + mFormatConv->CanConvert(aFlavor, data.GetFlavor().get(), &canConvert); + + if (canConvert) { + nsCOMPtr<nsISupports> ConvertedData; + mFormatConv->Convert(aFlavor, aData, data.GetFlavor().get(), + getter_AddRefs(ConvertedData)); + data.SetData(ConvertedData, mPrivateData); + return NS_OK; + } + } + } + + // Can't set data neither directly nor through converter. Just add this flavor + // and try again + if (NS_SUCCEEDED(AddDataFlavor(aFlavor))) { + return SetTransferData(aFlavor, aData); + } + + return NS_ERROR_FAILURE; +} + +// +// AddDataFlavor +// +// Adds a data flavor to our list with no data. Error if it already exists. +// +NS_IMETHODIMP +nsTransferable::AddDataFlavor(const char* aDataFlavor) { + MOZ_ASSERT(mInitialized); + + if (FindDataFlavor(aDataFlavor).isSome()) { + return NS_ERROR_FAILURE; + } + + // Create a new "slot" for the data + mDataArray.AppendElement(DataStruct(aDataFlavor)); + return NS_OK; +} + +// +// RemoveDataFlavor +// +// Removes a data flavor (and causes the data to be destroyed). Error if +// the requested flavor is not present. +// +NS_IMETHODIMP +nsTransferable::RemoveDataFlavor(const char* aDataFlavor) { + MOZ_ASSERT(mInitialized); + + if (Maybe<size_t> index = FindDataFlavor(aDataFlavor)) { + mDataArray.RemoveElementAt(index.value()); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTransferable::SetConverter(nsIFormatConverter* aConverter) { + MOZ_ASSERT(mInitialized); + + mFormatConv = aConverter; + return NS_OK; +} + +NS_IMETHODIMP +nsTransferable::GetConverter(nsIFormatConverter** aConverter) { + MOZ_ASSERT(mInitialized); + + nsCOMPtr<nsIFormatConverter> converter = mFormatConv; + converter.forget(aConverter); + return NS_OK; +} + +// +// FlavorsTransferableCanImport +// +// Computes a list of flavors that the transferable can accept into it, either +// through intrinsic knowledge or input data converters. +// +NS_IMETHODIMP +nsTransferable::FlavorsTransferableCanImport(nsTArray<nsCString>& aFlavors) { + MOZ_ASSERT(mInitialized); + + // Get the flavor list, and on to the end of it, append the list of flavors we + // can also get to through a converter. This is so that we can just walk the + // list in one go, looking for the desired flavor. + GetTransferDataFlavors(aFlavors); + + if (mFormatConv) { + nsTArray<nsCString> convertedList; + mFormatConv->GetInputDataFlavors(convertedList); + + for (uint32_t i = 0; i < convertedList.Length(); ++i) { + nsCString& flavorStr = convertedList[i]; + + // Don't append if already in intrinsic list + if (!aFlavors.Contains(flavorStr)) { + aFlavors.AppendElement(flavorStr); + } + } + } + + return NS_OK; +} + +// +// FlavorsTransferableCanExport +// +// Computes a list of flavors that the transferable can export, either through +// intrinsic knowledge or output data converters. +// +NS_IMETHODIMP +nsTransferable::FlavorsTransferableCanExport(nsTArray<nsCString>& aFlavors) { + MOZ_ASSERT(mInitialized); + + // Get the flavor list, and on to the end of it, append the list of flavors we + // can also get to through a converter. This is so that we can just walk the + // list in one go, looking for the desired flavor. + GetTransferDataFlavors(aFlavors); + + if (mFormatConv) { + nsTArray<nsCString> convertedList; + mFormatConv->GetOutputDataFlavors(convertedList); + + for (uint32_t i = 0; i < convertedList.Length(); ++i) { + nsCString& flavorStr = convertedList[i]; + + // Don't append if already in intrinsic list + if (!aFlavors.Contains(flavorStr)) { + aFlavors.AppendElement(flavorStr); + } + } + } + + return NS_OK; +} + +bool nsTransferable::GetIsPrivateData() { + MOZ_ASSERT(mInitialized); + + return mPrivateData; +} + +void nsTransferable::SetIsPrivateData(bool aIsPrivateData) { + MOZ_ASSERT(mInitialized); + + mPrivateData = aIsPrivateData; +} + +nsIPrincipal* nsTransferable::GetRequestingPrincipal() { + MOZ_ASSERT(mInitialized); + + return mRequestingPrincipal; +} + +void nsTransferable::SetRequestingPrincipal( + nsIPrincipal* aRequestingPrincipal) { + MOZ_ASSERT(mInitialized); + + mRequestingPrincipal = aRequestingPrincipal; +} + +nsContentPolicyType nsTransferable::GetContentPolicyType() { + MOZ_ASSERT(mInitialized); + + return mContentPolicyType; +} + +void nsTransferable::SetContentPolicyType( + nsContentPolicyType aContentPolicyType) { + MOZ_ASSERT(mInitialized); + + mContentPolicyType = aContentPolicyType; +} + +nsICookieJarSettings* nsTransferable::GetCookieJarSettings() { + MOZ_ASSERT(mInitialized); + + return mCookieJarSettings; +} + +void nsTransferable::SetCookieJarSettings( + nsICookieJarSettings* aCookieJarSettings) { + MOZ_ASSERT(mInitialized); + + mCookieJarSettings = aCookieJarSettings; +} |