summaryrefslogtreecommitdiffstats
path: root/widget/windows/nsClipboard.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/windows/nsClipboard.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows/nsClipboard.cpp')
-rw-r--r--widget/windows/nsClipboard.cpp1471
1 files changed, 1471 insertions, 0 deletions
diff --git a/widget/windows/nsClipboard.cpp b/widget/windows/nsClipboard.cpp
new file mode 100644
index 0000000000..f2c16343ee
--- /dev/null
+++ b/widget/windows/nsClipboard.cpp
@@ -0,0 +1,1471 @@
+/* -*- 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 <shlobj.h>
+#include <intshcut.h>
+
+// shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
+#include <shellapi.h>
+
+#include <functional>
+#include <thread>
+#include <chrono>
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Compatibility.h"
+#endif
+#include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_clipboard.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WindowsVersion.h"
+#include "SpecialSystemDirectory.h"
+
+#include "nsArrayUtils.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDataObj.h"
+#include "nsString.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIInputStream.h"
+#include "nsITransferable.h"
+#include "nsXPCOM.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsIWidget.h"
+#include "nsWidgetsCID.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsEscape.h"
+#include "nsIObserverService.h"
+#include "nsMimeTypes.h"
+#include "imgITools.h"
+#include "imgIContainer.h"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule gWin32ClipboardLog("nsClipboard");
+
+/* static */
+UINT nsClipboard::GetClipboardFileDescriptorFormatA() {
+ static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORA);
+ MOZ_ASSERT(format);
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetClipboardFileDescriptorFormatW() {
+ static UINT format = ::RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW);
+ MOZ_ASSERT(format);
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetHtmlClipboardFormat() {
+ static UINT format = ::RegisterClipboardFormatW(L"HTML Format");
+ return format;
+}
+
+/* static */
+UINT nsClipboard::GetCustomClipboardFormat() {
+ static UINT format =
+ ::RegisterClipboardFormatW(L"application/x-moz-custom-clipdata");
+ return format;
+}
+
+//-------------------------------------------------------------------------
+//
+// nsClipboard constructor
+//
+//-------------------------------------------------------------------------
+nsClipboard::nsClipboard()
+ : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
+ false /* supportsSelectionClipboard */,
+ false /* supportsFindClipboard */,
+ false /* supportsSelectionCache */)) {
+ 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_WILL_SHUTDOWN_OBSERVER_ID,
+ 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_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 = GetHtmlClipboardFormat();
+ } else if (strcmp(aMimeStr, kCustomTypesMime) == 0) {
+ format = GetCustomClipboardFormat();
+ } else {
+ format = ::RegisterClipboardFormatW(NS_ConvertASCIItoUTF16(aMimeStr).get());
+ }
+
+ return format;
+}
+
+//-------------------------------------------------------------------------
+// static
+nsresult nsClipboard::CreateNativeDataObject(
+ nsITransferable* aTransferable, IDataObject** aDataObj, nsIURI* aUri,
+ MightNeedToFlush* aMightNeedToFlush) {
+ MOZ_ASSERT(aTransferable);
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Create our native DataObject that implements the OLE IDataObject interface
+ RefPtr<nsDataObj> dataObj = new nsDataObj(aUri);
+
+ // Now set it up with all the right data flavors & enums
+ nsresult res =
+ SetupNativeDataObject(aTransferable, dataObj, aMightNeedToFlush);
+ if (NS_SUCCEEDED(res)) {
+ dataObj.forget(aDataObj);
+ }
+ return res;
+}
+
+static nsresult StoreValueInDataObject(nsDataObj* aObj,
+ LPCWSTR aClipboardFormat, DWORD value) {
+ HGLOBAL hGlobalMemory = ::GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD));
+ if (!hGlobalMemory) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ DWORD* pdw = (DWORD*)::GlobalLock(hGlobalMemory);
+ *pdw = value;
+ ::GlobalUnlock(hGlobalMemory);
+
+ STGMEDIUM stg;
+ stg.tymed = TYMED_HGLOBAL;
+ stg.pUnkForRelease = nullptr;
+ stg.hGlobal = hGlobalMemory;
+
+ FORMATETC fe;
+ SET_FORMATETC(fe, ::RegisterClipboardFormat(aClipboardFormat), 0,
+ DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ aObj->SetData(&fe, &stg, TRUE);
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SetupNativeDataObject(
+ nsITransferable* aTransferable, IDataObject* aDataObj,
+ MightNeedToFlush* aMightNeedToFlush) {
+ MOZ_ASSERT(aTransferable);
+ MOZ_ASSERT(aDataObj);
+ if (!aTransferable || !aDataObj) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto* dObj = static_cast<nsDataObj*>(aDataObj);
+ if (aMightNeedToFlush) {
+ *aMightNeedToFlush = MightNeedToFlush::No;
+ }
+
+ // Now give the Transferable to the DataObject
+ // for getting the data out of it
+ dObj->SetTransferable(aTransferable);
+
+ // Get the transferable list of data flavors
+ nsTArray<nsCString> flavors;
+ aTransferable->FlavorsTransferableCanExport(flavors);
+
+ // Walk through flavors that contain data and register them
+ // into the DataObj as supported flavors
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ // 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.get(), 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.get(), &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 (flavorStr.EqualsLiteral(kTextMime)) {
+ // if we find text/plain, also add CF_TEXT, but we can add it for
+ // text/plain as well.
+ FORMATETC textFE;
+ SET_FORMATETC(textFE, CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kTextMime, &textFE);
+ if (aMightNeedToFlush) {
+ *aMightNeedToFlush = MightNeedToFlush::Yes;
+ }
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ // 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, GetHtmlClipboardFormat(), 0, DVASPECT_CONTENT, -1,
+ TYMED_HGLOBAL);
+ dObj->AddDataFlavor(kHTMLMime, &htmlFE);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // 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 (flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime) ||
+ flavorStr.EqualsLiteral(kNativeImageMime)) {
+ // 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.get(), &imageFE);
+ // Add DIBv3
+ SET_FORMATETC(imageFE, CF_DIB, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL)
+ dObj->AddDataFlavor(flavorStr.get(), &imageFE);
+ } else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ // 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);
+ }
+ }
+
+ if (!mozilla::StaticPrefs::
+ clipboard_copyPrivateDataToClipboardCloudOrHistory()) {
+ // Let Clipboard know that data is sensitive and must not be copied to
+ // the Cloud Clipboard, Clipboard History and similar.
+ // https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats
+ if (aTransferable->GetIsPrivateData()) {
+ nsresult rv =
+ StoreValueInDataObject(dObj, TEXT("CanUploadToCloudClipboard"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ StoreValueInDataObject(dObj, TEXT("CanIncludeInClipboardHistory"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreValueInDataObject(
+ dObj, TEXT("ExcludeClipboardContentFromMonitorProcessing"), 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+// See methods listed at
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
+static void IDataObjectMethodResultToString(const HRESULT aHres,
+ nsACString& aResult) {
+ switch (aHres) {
+ case E_INVALIDARG:
+ aResult = "E_INVALIDARG";
+ break;
+ case E_UNEXPECTED:
+ aResult = "E_UNEXPECTED";
+ break;
+ case E_OUTOFMEMORY:
+ aResult = "E_OUTOFMEMORY";
+ break;
+ case DV_E_LINDEX:
+ aResult = "DV_E_LINDEX";
+ break;
+ case DV_E_FORMATETC:
+ aResult = "DV_E_FORMATETC";
+ break;
+ case DV_E_TYMED:
+ aResult = "DV_E_TYMED";
+ break;
+ case DV_E_DVASPECT:
+ aResult = "DV_E_DVASPECT";
+ break;
+ case OLE_E_NOTRUNNING:
+ aResult = "OLE_E_NOTRUNNING";
+ break;
+ case STG_E_MEDIUMFULL:
+ aResult = "STG_E_MEDIUMFULL";
+ break;
+ case DV_E_CLIPFORMAT:
+ aResult = "DV_E_CLIPFORMAT";
+ break;
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
+static void OleGetClipboardResultToString(const HRESULT aHres,
+ nsACString& aResult) {
+ switch (aHres) {
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ case CLIPBRD_E_CANT_OPEN:
+ aResult = "CLIPBRD_E_CANT_OPEN";
+ break;
+ case CLIPBRD_E_CANT_CLOSE:
+ aResult = "CLIPBRD_E_CANT_CLOSE";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
+static void LogOleGetClipboardResult(const HRESULT aHres) {
+ if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
+ nsAutoCString hresString;
+ OleGetClipboardResultToString(aHres, hresString);
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
+ ("OleGetClipboard result: %s", hresString.get()));
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
+static void OleSetClipboardResultToString(HRESULT aHres, nsACString& aResult) {
+ switch (aHres) {
+ case S_OK:
+ aResult = "S_OK";
+ break;
+ case CLIPBRD_E_CANT_OPEN:
+ aResult = "CLIPBRD_E_CANT_OPEN";
+ break;
+ case CLIPBRD_E_CANT_EMPTY:
+ aResult = "CLIPBRD_E_CANT_EMPTY";
+ break;
+ case CLIPBRD_E_CANT_CLOSE:
+ aResult = "CLIPBRD_E_CANT_CLOSE";
+ break;
+ case CLIPBRD_E_CANT_SET:
+ aResult = "CLIPBRD_E_CANT_SET";
+ break;
+ default:
+ // Explicit template instantiaton, because otherwise the call is
+ // ambiguous.
+ constexpr int kRadix = 16;
+ aResult = IntToCString<int32_t>(aHres, kRadix);
+ break;
+ }
+}
+
+// See
+// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olesetclipboard>.
+static void LogOleSetClipboardResult(const HRESULT aHres) {
+ if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
+ nsAutoCString hresString;
+ OleSetClipboardResultToString(aHres, hresString);
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
+ ("OleSetClipboard result: %s", hresString.get()));
+ }
+}
+
+template <typename Function, typename LogFunction, typename... Args>
+static HRESULT RepeatedlyTry(Function aFunction, LogFunction aLogFunction,
+ Args... aArgs) {
+ // These are magic values based on local testing. They are chosen not higher
+ // to avoid jank (<https://developer.mozilla.org/en-US/docs/Glossary/Jank>).
+ // When changing them, be careful.
+ static constexpr int kNumberOfTries = 3;
+ static constexpr int kDelayInMs = 3;
+
+ HRESULT hres;
+ for (int i = 0; i < kNumberOfTries; ++i) {
+ hres = aFunction(aArgs...);
+ aLogFunction(hres);
+
+ if (hres == S_OK) {
+ break;
+ }
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(kDelayInMs));
+ }
+
+ return hres;
+}
+
+// Other apps can block access to the clipboard. This repeatedly
+// calls `::OleSetClipboard` for a fixed number of times and should be called
+// instead of `::OleSetClipboard`.
+static void RepeatedlyTryOleSetClipboard(IDataObject* aDataObj) {
+ RepeatedlyTry(::OleSetClipboard, LogOleSetClipboardResult, aDataObj);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::SetNativeClipboardData(
+ nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
+ int32_t aWhichClipboard) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
+
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // make sure we have a good transferable
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+
+#ifdef ACCESSIBILITY
+ mozilla::a11y::Compatibility::SuppressA11yForClipboardCopy();
+#endif
+
+ RefPtr<IDataObject> dataObj;
+ auto mightNeedToFlush = MightNeedToFlush::No;
+ if (NS_SUCCEEDED(CreateNativeDataObject(aTransferable,
+ getter_AddRefs(dataObj), nullptr,
+ &mightNeedToFlush))) {
+ RepeatedlyTryOleSetClipboard(dataObj);
+
+ const bool doFlush = [&] {
+ switch (mozilla::StaticPrefs::widget_windows_sync_clipboard_flush()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ // Bug 1774285: Windows Suggested Actions (introduced in Windows 11
+ // 22H2) walks the entire a11y tree using UIA if something is placed
+ // on the clipboard using delayed rendering. (The OLE clipboard always
+ // uses delayed rendering.) This a11y tree walk causes an unacceptable
+ // hang, particularly when the a11y cache is disabled. We choose the
+ // lesser of the two performance/memory evils here and force immediate
+ // rendering.
+ return mightNeedToFlush == MightNeedToFlush::Yes &&
+ mozilla::NeedsWindows11SuggestedActionsWorkaround();
+ }
+ }();
+ if (doFlush) {
+ RepeatedlyTry(::OleFlushClipboard, [](HRESULT) {});
+ }
+ } else {
+ // Clear the native clipboard
+ RepeatedlyTryOleSetClipboard(nullptr);
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetGlobalData(HGLOBAL aHGBL, void** aData,
+ uint32_t* aLen) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose, ("%s", __FUNCTION__));
+
+ // 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 3 bytes (but don't report them in |aLen|!)
+ // and null them out to ensure that all of our NS_strlen calls will succeed.
+ // NS_strlen operates on char16_t, so we need 3 NUL bytes to ensure it finds
+ // a full NUL char16_t when |*aLen| is odd.
+ nsresult result = NS_ERROR_FAILURE;
+ if (aHGBL != nullptr) {
+ LPSTR lpStr = (LPSTR)GlobalLock(aHGBL);
+ mozilla::CheckedInt<uint32_t> allocSize =
+ mozilla::CheckedInt<uint32_t>(GlobalSize(aHGBL)) + 3;
+ if (!allocSize.isValid()) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ char* data = static_cast<char*>(malloc(allocSize.value()));
+ if (data) {
+ uint32_t size = allocSize.value() - 3;
+ memcpy(data, lpStr, size);
+ // null terminate for safety
+ data[size] = data[size + 1] = data[size + 2] = '\0';
+
+ GlobalUnlock(aHGBL);
+ *aData = data;
+ *aLen = size;
+
+ 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) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
+ ("%s: overload taking nsIWidget*.", __FUNCTION__));
+
+ HGLOBAL hglb;
+ nsresult result = NS_ERROR_FAILURE;
+
+ HWND nativeWin = nullptr;
+ if (::OpenClipboard(nativeWin)) {
+ hglb = ::GetClipboardData(aFormat);
+ result = GetGlobalData(hglb, aData, aLen);
+ ::CloseClipboard();
+ }
+ return result;
+}
+
+// See methods listed at
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nn-objidl-idataobject#methods>.
+static void LogIDataObjectMethodResult(const HRESULT aHres,
+ const nsCString& aMethodName) {
+ if (MOZ_LOG_TEST(gWin32ClipboardLog, LogLevel::Debug)) {
+ nsAutoCString hresString;
+ IDataObjectMethodResultToString(aHres, hresString);
+ MOZ_LOG(
+ gWin32ClipboardLog, LogLevel::Debug,
+ ("IDataObject::%s result: %s", aMethodName.get(), hresString.get()));
+ }
+}
+
+// Other apps can block access to the clipboard. This repeatedly calls
+// `GetData` for a fixed number of times and should be called instead of
+// `GetData`. See
+// <https://docs.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-idataobject-getdata>.
+// While Microsoft's documentation doesn't include `CLIPBRD_E_CANT_OPEN`
+// explicitly, it allows it implicitly and in local experiments it was indeed
+// returned.
+static HRESULT RepeatedlyTryGetData(IDataObject& aDataObject, LPFORMATETC pFE,
+ LPSTGMEDIUM pSTM) {
+ return RepeatedlyTry(
+ [&aDataObject, &pFE, &pSTM]() { return aDataObject.GetData(pFE, pSTM); },
+ std::bind(LogIDataObjectMethodResult, std::placeholders::_1,
+ "GetData"_ns));
+}
+
+//-------------------------------------------------------------------------
+// static
+HRESULT nsClipboard::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);
+ LogIDataObjectMethodResult(hres, "QueryGetData"_ns);
+ if (S_OK == hres) {
+ hres = RepeatedlyTryGetData(*aDataObject, pFE, pSTM);
+ }
+ 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) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
+ ("%s: overload taking IDataObject*.", __FUNCTION__));
+
+ 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);
+
+ // If the format is CF_HDROP and we haven't found any files we can try looking
+ // for virtual files with FILEDESCRIPTOR.
+ if (FAILED(hres) && format == CF_HDROP) {
+ hres = FillSTGMedium(aDataObject,
+ nsClipboard::GetClipboardFileDescriptorFormatW(), &fe,
+ &stm, TYMED_HGLOBAL);
+ if (FAILED(hres)) {
+ hres = FillSTGMedium(aDataObject,
+ nsClipboard::GetClipboardFileDescriptorFormatA(),
+ &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;
+ const char* clipboardData;
+ if (NS_SUCCEEDED(GetGlobalData(
+ stm.hGlobal, (void**)&clipboardData, &allocLen))) {
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<imgITools> imgTools =
+ do_CreateInstance("@mozilla.org/image/tools;1");
+ result = imgTools->DecodeImageFromBuffer(
+ clipboardData, allocLen,
+ nsLiteralCString(IMAGE_BMP_MS_CLIPBOARD),
+ getter_AddRefs(container));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ nsAutoCString mimeType;
+ if (strcmp(aMIMEImageFormat, kJPGImageMime) == 0) {
+ mimeType.Assign(IMAGE_JPEG);
+ } else {
+ mimeType.Assign(aMIMEImageFormat);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ result = imgTools->EncodeImage(container, mimeType, u""_ns,
+ getter_AddRefs(inputStream));
+ if (NS_FAILED(result)) {
+ break;
+ }
+
+ if (!inputStream) {
+ result = NS_ERROR_FAILURE;
+ break;
+ }
+
+ *aData = inputStream.forget().take();
+ *aLen = sizeof(nsIInputStream*);
+ }
+ }
+ 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)));
+ ::DragQueryFileW(dropFiles, aIndex, buffer, fileNameLen + 1);
+ *aData = buffer;
+ *aLen = fileNameLen * sizeof(char16_t);
+ result = NS_OK;
+ }
+ GlobalUnlock(stm.hGlobal);
+
+ } break;
+
+ default: {
+ if (fe.cfFormat == fileDescriptorFlavorA ||
+ fe.cfFormat == fileDescriptorFlavorW) {
+ nsAutoString tempPath;
+
+ LPFILEGROUPDESCRIPTOR fgdesc =
+ static_cast<LPFILEGROUPDESCRIPTOR>(GlobalLock(stm.hGlobal));
+ if (fgdesc) {
+ result = GetTempFilePath(
+ nsDependentString((fgdesc->fgd)[aIndex].cFileName),
+ tempPath);
+ GlobalUnlock(stm.hGlobal);
+ }
+ if (NS_FAILED(result)) {
+ break;
+ }
+ result = SaveStorageOrStream(aDataObject, aIndex, tempPath);
+ if (NS_FAILED(result)) {
+ break;
+ }
+ wchar_t* buffer = reinterpret_cast<wchar_t*>(
+ moz_xmalloc((tempPath.Length() + 1) * sizeof(wchar_t)));
+ wcscpy(buffer, tempPath.get());
+ *aData = buffer;
+ *aLen = tempPath.Length() * sizeof(wchar_t);
+ result = NS_OK;
+ } else if (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 == GetHtmlClipboardFormat()) {
+ // 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 == GetCustomClipboardFormat()) {
+ // 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) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
+
+ // 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)
+ nsTArray<nsCString> flavors;
+ res = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(res)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Walk through flavors and see which flavor is on the clipboard them on the
+ // native clipboard,
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+ UINT format = GetFormat(flavorStr.get());
+
+ // 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.get(), &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 (flavorStr.EqualsLiteral(kTextMime)) {
+ dataFound =
+ FindUnicodeFromPlainText(aDataObject, anIndex, &data, &dataLen);
+ } else if (flavorStr.EqualsLiteral(kURLMime)) {
+ // 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 (flavorStr.EqualsLiteral(kFileMime)) {
+ // 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 (flavorStr.EqualsLiteral(kNativeHTMLMime)) {
+ 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 (flavorStr.EqualsLiteral(kHTMLMime)) {
+ 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 (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime)) {
+ nsIInputStream* imageStream = reinterpret_cast<nsIInputStream*>(data);
+ genericDataWrapper = do_QueryInterface(imageStream);
+ NS_IF_RELEASE(imageStream);
+ } else {
+ // Treat custom types as a string of bytes.
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ bool isRTF = flavorStr.EqualsLiteral(kRTFMime);
+ // 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(isRTF, &data,
+ &signedLen);
+ dataLen = signedLen;
+
+ if (isRTF) {
+ // 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.get(), genericDataWrapper);
+ 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
+//
+// Looks for CF_TEXT on the clipboard and converts it into an UTF-16 string
+// if present. Returns this string in outData, and its length in outDataLen.
+// XXXndeakin Windows converts between CF_UNICODE and CF_TEXT automatically
+// so it doesn't seem like this is actually needed.
+//
+bool nsClipboard ::FindUnicodeFromPlainText(IDataObject* inDataObject,
+ UINT inIndex, void** outData,
+ uint32_t* outDataLen) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
+
+ // We are looking for text/plain and we failed to find it on the clipboard
+ // first, so try again with CF_TEXT. If that is present, convert it to
+ // unicode.
+ nsresult rv = GetNativeDataOffClipboard(inDataObject, inIndex, CF_TEXT,
+ 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) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
+
+ 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 + u"\n"_ns + 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) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug, ("%s", __FUNCTION__));
+
+ 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 + u"\n"_ns + 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 + u"\n"_ns + urlString);
+ *outDataLen =
+ NS_strlen(static_cast<char16_t*>(*outData)) * sizeof(char16_t);
+ free(tempOutData);
+ dataFound = true;
+ }
+ }
+
+ return dataFound;
+} // FindURLFromNativeURL
+
+// Other apps can block access to the clipboard. This repeatedly
+// calls `::OleGetClipboard` for a fixed number of times and should be called
+// instead of `::OleGetClipboard`.
+static HRESULT RepeatedlyTryOleGetClipboard(IDataObject** aDataObj) {
+ return RepeatedlyTry(::OleGetClipboard, LogOleGetClipboardResult, aDataObj);
+}
+
+//
+// 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, u".url"_ns,
+ nsCaseInsensitiveStringComparator);
+} // IsInternetShortcut
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Debug,
+ ("%s aWhichClipboard=%i", __FUNCTION__, 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 == RepeatedlyTryOleGetClipboard(&dataObj)) {
+ // Use OLE IDataObject for clipboard operations
+ MOZ_LOG(gWin32ClipboardLog, LogLevel::Verbose,
+ ("%s: use OLE IDataObject.", __FUNCTION__));
+ 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) {
+ RepeatedlyTryOleSetClipboard(nullptr);
+ }
+ return nsBaseClipboard::EmptyClipboard(aWhichClipboard);
+}
+
+//-------------------------------------------------------------------------
+NS_IMETHODIMP nsClipboard::HasDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ bool* _retval) {
+ *_retval = false;
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_OK;
+ }
+
+ for (auto& flavor : aFlavorList) {
+ UINT format = GetFormat(flavor.get());
+ if (IsClipboardFormatAvailable(format)) {
+ *_retval = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::GetTempFilePath(const nsAString& aFileName,
+ nsAString& aFilePath) {
+ nsresult result = NS_OK;
+
+ nsCOMPtr<nsIFile> tmpFile;
+ result =
+ GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(tmpFile));
+ NS_ENSURE_SUCCESS(result, result);
+
+ result = tmpFile->Append(aFileName);
+ NS_ENSURE_SUCCESS(result, result);
+
+ result = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0660);
+ NS_ENSURE_SUCCESS(result, result);
+ result = tmpFile->GetPath(aFilePath);
+
+ return result;
+}
+
+//-------------------------------------------------------------------------
+nsresult nsClipboard::SaveStorageOrStream(IDataObject* aDataObject, UINT aIndex,
+ const nsAString& aFileName) {
+ NS_ENSURE_ARG_POINTER(aDataObject);
+
+ FORMATETC fe = {0};
+ SET_FORMATETC(fe, RegisterClipboardFormat(CFSTR_FILECONTENTS), 0,
+ DVASPECT_CONTENT, aIndex, TYMED_ISTORAGE | TYMED_ISTREAM);
+
+ STGMEDIUM stm = {0};
+ HRESULT hres = aDataObject->GetData(&fe, &stm);
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto releaseMediumGuard =
+ mozilla::MakeScopeExit([&] { ReleaseStgMedium(&stm); });
+
+ // We do this check because, even though we *asked* for IStorage or IStream,
+ // it seems that IDataObject providers can just hand us back whatever they
+ // feel like. See Bug 1824644 for a fun example of that!
+ if (stm.tymed != TYMED_ISTORAGE && stm.tymed != TYMED_ISTREAM) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (stm.tymed == TYMED_ISTORAGE) {
+ RefPtr<IStorage> file;
+ hres = StgCreateStorageEx(
+ aFileName.Data(), STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
+ STGFMT_STORAGE, 0, NULL, NULL, IID_IStorage, getter_AddRefs(file));
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ hres = stm.pstg->CopyTo(0, NULL, NULL, file);
+ if (FAILED(hres)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ file->Commit(STGC_DEFAULT);
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(stm.tymed == TYMED_ISTREAM);
+
+ HANDLE handle = CreateFile(aFileName.Data(), GENERIC_WRITE, FILE_SHARE_READ,
+ NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto fileCloseGuard = mozilla::MakeScopeExit([&] { CloseHandle(handle); });
+
+ const ULONG bufferSize = 4096;
+ char buffer[bufferSize] = {0};
+ ULONG bytesRead = 0;
+ DWORD bytesWritten = 0;
+ while (true) {
+ HRESULT result = stm.pstm->Read(buffer, bufferSize, &bytesRead);
+ if (FAILED(result)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (bytesRead == 0) {
+ break;
+ }
+ if (!WriteFile(handle, buffer, static_cast<DWORD>(bytesRead), &bytesWritten,
+ NULL)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}