mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
ef73dc056f
When Windows keeps us from getting access to the clipboard because another application is using it, this will report the exe that currently holds the mutex. Differential Revision: https://phabricator.services.mozilla.com/D227742
1516 lines
54 KiB
C++
1516 lines
54 KiB
C++
/* -*- 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"
|
|
#include "WinUtils.h"
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
static void MaybeLogClipboardCurrentOwner(
|
|
const HRESULT aHres, const mozilla::StaticString& aMethodName) {
|
|
if (!MOZ_CLIPBOARD_LOG_ENABLED()) {
|
|
return;
|
|
}
|
|
|
|
if (aHres != CLIPBRD_E_CANT_OPEN) {
|
|
return;
|
|
}
|
|
auto hwnd = ::GetOpenClipboardWindow();
|
|
if (!hwnd) {
|
|
MOZ_CLIPBOARD_LOG(
|
|
"IDataObject::%s | Clipboard already opened by unknown process",
|
|
aMethodName.get());
|
|
return;
|
|
}
|
|
DWORD procId;
|
|
DWORD threadId = ::GetWindowThreadProcessId(hwnd, &procId);
|
|
NS_ENSURE_TRUE_VOID(threadId);
|
|
nsAutoString procName;
|
|
NS_ENSURE_SUCCESS_VOID(
|
|
mozilla::widget::WinUtils::GetProcessImageName(procId, procName));
|
|
MOZ_CLIPBOARD_LOG(
|
|
"IDataObject::%s | Clipboard already opened by HWND: %p | "
|
|
"Process ID: %lu | Thread ID: %lu | App name: %s",
|
|
aMethodName.get(), hwnd, procId, threadId,
|
|
NS_ConvertUTF16toUTF8(procName).get());
|
|
}
|
|
|
|
// See
|
|
// <https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-olegetclipboard>.
|
|
static void LogOleGetClipboardResult(const HRESULT aHres) {
|
|
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
|
|
nsAutoCString hresString;
|
|
OleGetClipboardResultToString(aHres, hresString);
|
|
MOZ_CLIPBOARD_LOG("OleGetClipboard result: %s", hresString.get());
|
|
MaybeLogClipboardCurrentOwner(aHres, "OleGetClipboard");
|
|
}
|
|
}
|
|
|
|
// 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_CLIPBOARD_LOG_ENABLED()) {
|
|
nsAutoCString hresString;
|
|
OleSetClipboardResultToString(aHres, hresString);
|
|
MOZ_CLIPBOARD_LOG("OleSetClipboard result: %s", hresString.get());
|
|
MaybeLogClipboardCurrentOwner(aHres, "OleSetClipboard");
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// TODO: This was formerly std::sleep_for, which wasn't actually sleeping
|
|
// in tests (bug 1927664).
|
|
::SleepEx(kDelayInMs, TRUE);
|
|
}
|
|
|
|
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, ClipboardType aWhichClipboard) {
|
|
MOZ_CLIPBOARD_LOG("%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 as part of our workaround.
|
|
return mightNeedToFlush == MightNeedToFlush::Yes &&
|
|
mozilla::IsWin1122H2OrLater();
|
|
}
|
|
}();
|
|
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_CLIPBOARD_LOG("%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_CLIPBOARD_LOG("%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,
|
|
mozilla::StaticString aMethodName) {
|
|
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
|
|
nsAutoCString hresString;
|
|
IDataObjectMethodResultToString(aHres, hresString);
|
|
MOZ_CLIPBOARD_LOG("IDataObject::%s result : %s", aMethodName.get(),
|
|
hresString.get());
|
|
MaybeLogClipboardCurrentOwner(aHres, aMethodName);
|
|
}
|
|
}
|
|
|
|
// 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); },
|
|
[](HRESULT hres) { LogIDataObjectMethodResult(hres, "GetData"); });
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// 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");
|
|
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_CLIPBOARD_LOG("%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_CLIPBOARD_LOG("*********************** TYMED_GDI");
|
|
#endif
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
} // switch
|
|
|
|
ReleaseStgMedium(&stm);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
nsresult nsClipboard::GetDataFromDataObject(IDataObject* aDataObject,
|
|
UINT anIndex, nsIWidget* aWindow,
|
|
nsITransferable* aTransferable) {
|
|
MOZ_CLIPBOARD_LOG("%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, 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_CLIPBOARD_LOG("%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_CLIPBOARD_LOG("%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, 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_CLIPBOARD_LOG("%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,
|
|
ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(aTransferable);
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
|
|
MOZ_CLIPBOARD_LOG("%s aWhichClipboard=%i", __FUNCTION__, aWhichClipboard);
|
|
|
|
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_CLIPBOARD_LOG(" use OLE IDataObject:");
|
|
if (MOZ_CLIPBOARD_LOG_ENABLED()) {
|
|
IEnumFORMATETC* pEnum = nullptr;
|
|
if (S_OK == dataObj->EnumFormatEtc(DATADIR_GET, &pEnum)) {
|
|
FORMATETC fEtc;
|
|
while (S_OK == pEnum->Next(1, &fEtc, nullptr)) {
|
|
nsAutoString format;
|
|
mozilla::widget::WinUtils::GetClipboardFormatAsString(fEtc.cfFormat,
|
|
format);
|
|
MOZ_CLIPBOARD_LOG(" FORMAT %s",
|
|
NS_ConvertUTF16toUTF8(format).get());
|
|
}
|
|
}
|
|
pEnum->Release();
|
|
}
|
|
|
|
res = GetDataFromDataObject(dataObj, 0, nullptr, aTransferable);
|
|
dataObj->Release();
|
|
} else {
|
|
// do it the old manual way
|
|
res = GetDataFromDataObject(nullptr, 0, mWindow, aTransferable);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
nsresult nsClipboard::EmptyNativeClipboardData(ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(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.
|
|
RepeatedlyTryOleSetClipboard(nullptr);
|
|
return NS_OK;
|
|
}
|
|
|
|
mozilla::Result<int32_t, nsresult>
|
|
nsClipboard::GetNativeClipboardSequenceNumber(ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(kGlobalClipboard == aWhichClipboard);
|
|
return (int32_t)::GetClipboardSequenceNumber();
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
mozilla::Result<bool, nsresult>
|
|
nsClipboard::HasNativeClipboardDataMatchingFlavors(
|
|
const nsTArray<nsCString>& aFlavorList, ClipboardType aWhichClipboard) {
|
|
MOZ_DIAGNOSTIC_ASSERT(
|
|
nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
|
|
for (const auto& flavor : aFlavorList) {
|
|
UINT format = GetFormat(flavor.get());
|
|
if (IsClipboardFormatAvailable(format)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
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) {
|
|
// should never happen -- but theoretically possible, given an ill-behaved
|
|
// data-source
|
|
if (stm.pstg == nullptr) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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);
|
|
// should never happen -- but possible given an ill-behaved data-source, and
|
|
// has been seen in the wild (bug 1895681)
|
|
if (stm.pstm == nullptr) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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;
|
|
}
|