blob: 45a37cac83cb25ae3f1b94dc11c10f0e22603346 [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2013-2014 Apple Inc. All rights reserved.
* Copyright (C) 2013 Xueqing Huang <huangxueqing@baidu.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "Pasteboard.h"
#include "BitmapInfo.h"
#include "CachedImage.h"
#include "ClipboardUtilitiesWin.h"
#include "Color.h"
#include "Document.h"
#include "DocumentFragment.h"
#include "Editor.h"
#include "Element.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "HTMLParserIdioms.h"
#include "HWndDC.h"
#include "HitTestResult.h"
#include "Image.h"
#include "NotImplemented.h"
#include "Range.h"
#include "RenderImage.h"
#include "SharedBuffer.h"
#include "TextEncoding.h"
#include "WebCoreInstanceHandle.h"
#include "markup.h"
#include <wtf/Optional.h>
#include <wtf/URL.h>
#include <wtf/WindowsExtras.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringView.h>
#include <wtf/text/win/WCharStringExtras.h>
#include <wtf/win/GDIObject.h>
namespace WebCore {
// We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft
// see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
static UINT HTMLClipboardFormat = 0;
static UINT BookmarkClipboardFormat = 0;
static UINT WebSmartPasteFormat = 0;
static LRESULT CALLBACK PasteboardOwnerWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
LRESULT lresult = 0;
switch (message) {
case WM_RENDERFORMAT:
// This message comes when SetClipboardData was sent a null data handle
// and now it's come time to put the data on the clipboard.
break;
case WM_RENDERALLFORMATS:
// This message comes when SetClipboardData was sent a null data handle
// and now this application is about to quit, so it must put data on
// the clipboard before it exits.
break;
case WM_DESTROY:
break;
case WM_DRAWCLIPBOARD:
break;
case WM_CHANGECBCHAIN:
break;
default:
lresult = DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return lresult;
}
std::unique_ptr<Pasteboard> Pasteboard::createForCopyAndPaste()
{
auto pasteboard = makeUnique<Pasteboard>();
COMPtr<IDataObject> clipboardData;
if (!SUCCEEDED(OleGetClipboard(&clipboardData)))
clipboardData = 0;
pasteboard->setExternalDataObject(clipboardData.get());
return pasteboard;
}
#if ENABLE(DRAG_SUPPORT)
std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop()
{
COMPtr<WCDataObject> dataObject;
WCDataObject::createInstance(&dataObject);
return makeUnique<Pasteboard>(dataObject.get());
}
// static
std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
{
if (dragData.platformData())
return makeUnique<Pasteboard>(dragData.platformData());
// FIXME: Should add a const overload of dragDataMap so we don't need a const_cast here.
return makeUnique<Pasteboard>(const_cast<DragData&>(dragData).dragDataMap());
}
#endif
void Pasteboard::finishCreatingPasteboard()
{
WNDCLASS wc;
memset(&wc, 0, sizeof(WNDCLASS));
wc.lpfnWndProc = PasteboardOwnerWndProc;
wc.hInstance = WebCore::instanceHandle();
wc.lpszClassName = L"PasteboardOwnerWindowClass";
RegisterClass(&wc);
m_owner = ::CreateWindow(L"PasteboardOwnerWindowClass", L"PasteboardOwnerWindow", 0, 0, 0, 0, 0,
HWND_MESSAGE, 0, 0, 0);
HTMLClipboardFormat = ::RegisterClipboardFormat(L"HTML Format");
BookmarkClipboardFormat = ::RegisterClipboardFormat(L"UniformResourceLocatorW");
WebSmartPasteFormat = ::RegisterClipboardFormat(L"WebKit Smart Paste Format");
}
Pasteboard::Pasteboard()
: m_dataObject(0)
, m_writableDataObject(0)
{
finishCreatingPasteboard();
}
Pasteboard::Pasteboard(IDataObject* dataObject)
: m_dataObject(dataObject)
, m_writableDataObject(0)
{
finishCreatingPasteboard();
}
Pasteboard::Pasteboard(WCDataObject* dataObject)
: m_dataObject(dataObject)
, m_writableDataObject(dataObject)
{
finishCreatingPasteboard();
}
Pasteboard::Pasteboard(const DragDataMap& dataMap)
: m_dataObject(0)
, m_writableDataObject(0)
, m_dragDataMap(dataMap)
{
finishCreatingPasteboard();
}
void Pasteboard::clear()
{
if (::OpenClipboard(m_owner)) {
::EmptyClipboard();
::CloseClipboard();
}
}
enum ClipboardDataType { ClipboardDataTypeNone, ClipboardDataTypeURL, ClipboardDataTypeText, ClipboardDataTypeTextHTML };
static ClipboardDataType clipboardTypeFromMIMEType(const String& type)
{
// two special cases for IE compatibility
if (equalLettersIgnoringASCIICase(type, "text/plain"))
return ClipboardDataTypeText;
if (equalLettersIgnoringASCIICase(type, "text/uri-list"))
return ClipboardDataTypeURL;
if (equalLettersIgnoringASCIICase(type, "text/html"))
return ClipboardDataTypeTextHTML;
return ClipboardDataTypeNone;
}
void Pasteboard::clear(const String& type)
{
if (!m_writableDataObject)
return;
ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
if (dataType == ClipboardDataTypeURL) {
m_writableDataObject->clearData(urlWFormat()->cfFormat);
m_writableDataObject->clearData(urlFormat()->cfFormat);
}
if (dataType == ClipboardDataTypeText) {
m_writableDataObject->clearData(plainTextFormat()->cfFormat);
m_writableDataObject->clearData(plainTextWFormat()->cfFormat);
}
}
bool Pasteboard::hasData()
{
if (!m_dataObject && m_dragDataMap.isEmpty())
return false;
if (m_dataObject) {
COMPtr<IEnumFORMATETC> itr;
if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
return false;
if (!itr)
return false;
FORMATETC data;
// IEnumFORMATETC::Next returns S_FALSE if there are no more items.
if (itr->Next(1, &data, 0) == S_OK) {
// There is at least one item in the IDataObject
return true;
}
return false;
}
return !m_dragDataMap.isEmpty();
}
static void addMimeTypesForFormat(ListHashSet<String>& results, const FORMATETC& format)
{
if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat)
results.add("text/uri-list");
if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat)
results.add("text/plain");
}
Vector<String> Pasteboard::typesSafeForBindings(const String&)
{
notImplemented();
return { };
}
Vector<String> Pasteboard::typesForLegacyUnsafeBindings()
{
ListHashSet<String> results;
if (!m_dataObject && m_dragDataMap.isEmpty())
return Vector<String>();
if (m_dataObject) {
COMPtr<IEnumFORMATETC> itr;
if (FAILED(m_dataObject->EnumFormatEtc(DATADIR_GET, &itr)))
return Vector<String>();
if (!itr)
return Vector<String>();
FORMATETC data;
// IEnumFORMATETC::Next returns S_FALSE if there are no more items.
while (itr->Next(1, &data, 0) == S_OK)
addMimeTypesForFormat(results, data);
} else {
for (DragDataMap::const_iterator it = m_dragDataMap.begin(); it != m_dragDataMap.end(); ++it) {
FORMATETC data;
data.cfFormat = (*it).key;
addMimeTypesForFormat(results, data);
}
}
return copyToVector(results);
}
String Pasteboard::readOrigin()
{
notImplemented();
return { };
}
String Pasteboard::readString(const String& type)
{
if (!m_dataObject && m_dragDataMap.isEmpty())
return "";
ClipboardDataType dataType = clipboardTypeFromMIMEType(type);
if (dataType == ClipboardDataTypeText)
return m_dataObject ? getPlainText(m_dataObject.get()) : getPlainText(&m_dragDataMap);
if (dataType == ClipboardDataTypeURL)
return m_dataObject ? getURL(m_dataObject.get(), DragData::DoNotConvertFilenames) : getURL(&m_dragDataMap, DragData::DoNotConvertFilenames);
if (dataType == ClipboardDataTypeTextHTML) {
String data = m_dataObject ? getTextHTML(m_dataObject.get()) : getTextHTML(&m_dragDataMap);
if (!data.isEmpty())
return data;
return m_dataObject ? getCFHTML(m_dataObject.get()) : getCFHTML(&m_dragDataMap);
}
return "";
}
String Pasteboard::readStringInCustomData(const String&)
{
notImplemented();
return { };
}
struct PasteboardFileCounter final : PasteboardFileReader {
void readFilename(const String&) final { ++count; }
void readBuffer(const String&, const String&, Ref<SharedBuffer>&&) final { ++count; }
unsigned count { 0 };
};
Pasteboard::FileContentState Pasteboard::fileContentState()
{
// FIXME: This implementation can be slightly more efficient by avoiding calls to DragQueryFileW.
PasteboardFileCounter reader;
read(reader);
return reader.count ? FileContentState::MayContainFilePaths : FileContentState::NoFileOrImageData;
}
void Pasteboard::read(PasteboardFileReader& reader)
{
#if USE(CF)
if (m_dataObject) {
STGMEDIUM medium;
if (FAILED(m_dataObject->GetData(cfHDropFormat(), &medium)))
return;
HDROP hdrop = reinterpret_cast<HDROP>(GlobalLock(medium.hGlobal));
if (!hdrop)
return;
WCHAR filename[MAX_PATH];
UINT fileCount = DragQueryFileW(hdrop, 0xFFFFFFFF, 0, 0);
for (UINT i = 0; i < fileCount; i++) {
if (!DragQueryFileW(hdrop, i, filename, WTF_ARRAY_LENGTH(filename)))
continue;
reader.readFilename(filename);
}
GlobalUnlock(medium.hGlobal);
ReleaseStgMedium(&medium);
return;
}
auto list = m_dragDataMap.find(cfHDropFormat()->cfFormat);
if (list == m_dragDataMap.end())
return;
for (auto& filename : list->value)
reader.readFilename(filename);
#else
UNUSED_PARAM(reader);
notImplemented();
return;
#endif
}
static bool writeURL(WCDataObject *data, const URL& url, String title, bool withPlainText, bool withHTML)
{
ASSERT(data);
if (url.isEmpty())
return false;
if (title.isEmpty()) {
title = url.lastPathComponent();
if (title.isEmpty())
title = url.host().toString();
}
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = createGlobalData(url, title);
bool success = false;
if (medium.hGlobal && FAILED(data->SetData(urlWFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
else
success = true;
if (withHTML) {
Vector<char> cfhtmlData;
markupToCFHTML(urlToMarkup(url, title), "", cfhtmlData);
medium.hGlobal = createGlobalData(cfhtmlData);
if (medium.hGlobal && FAILED(data->SetData(htmlFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
else
success = true;
}
if (withPlainText) {
medium.hGlobal = createGlobalData(url.string());
if (medium.hGlobal && FAILED(data->SetData(plainTextWFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
else
success = true;
}
return success;
}
void Pasteboard::writeString(const String& type, const String& data)
{
if (!m_writableDataObject)
return;
ClipboardDataType winType = clipboardTypeFromMIMEType(type);
if (winType == ClipboardDataTypeURL) {
WebCore::writeURL(m_writableDataObject.get(), URL(URL(), data), String(), false, true);
return;
}
if (winType == ClipboardDataTypeText) {
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = createGlobalData(data);
if (!medium.hGlobal)
return;
if (FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
}
}
#if ENABLE(DRAG_SUPPORT)
void Pasteboard::setDragImage(DragImage, const IntPoint&)
{
// Do nothing in Windows.
}
#endif
void Pasteboard::writeRangeToDataObject(Range& selectedRange, Frame& frame)
{
if (!m_writableDataObject)
return;
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
Vector<char> data;
markupToCFHTML(serializePreservingVisualAppearance(selectedRange, nullptr, AnnotateForInterchange::Yes),
selectedRange.startContainer().document().url().string(), data);
medium.hGlobal = createGlobalData(data);
if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
String str = frame.editor().selectedTextForDataTransfer();
replaceNewlinesWithWindowsStyleNewlines(str);
replaceNBSPWithSpace(str);
medium.hGlobal = createGlobalData(str);
if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
medium.hGlobal = 0;
if (frame.editor().canSmartCopyOrDelete())
m_writableDataObject->SetData(smartPasteFormat(), &medium, TRUE);
}
void Pasteboard::writeSelection(Range& selectedRange, bool canSmartCopyOrDelete, Frame& frame, ShouldSerializeSelectedTextForDataTransfer shouldSerializeSelectedTextForDataTransfer)
{
clear();
// Put CF_HTML format on the pasteboard
if (::OpenClipboard(m_owner)) {
Vector<char> data;
// FIXME: Use ResolveURLs::YesExcludingLocalFileURLsForPrivacy.
markupToCFHTML(serializePreservingVisualAppearance(frame.selection().selection()),
selectedRange.startContainer().document().url().string(), data);
HGLOBAL cbData = createGlobalData(data);
if (!::SetClipboardData(HTMLClipboardFormat, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
// Put plain string on the pasteboard. CF_UNICODETEXT covers CF_TEXT as well
String str = shouldSerializeSelectedTextForDataTransfer == IncludeImageAltTextForDataTransfer ? frame.editor().selectedTextForDataTransfer() : frame.editor().selectedText();
replaceNewlinesWithWindowsStyleNewlines(str);
replaceNBSPWithSpace(str);
if (::OpenClipboard(m_owner)) {
HGLOBAL cbData = createGlobalData(str);
if (!::SetClipboardData(CF_UNICODETEXT, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
// enable smart-replacing later on by putting dummy data on the pasteboard
if (canSmartCopyOrDelete) {
if (::OpenClipboard(m_owner)) {
::SetClipboardData(WebSmartPasteFormat, 0);
::CloseClipboard();
}
}
writeRangeToDataObject(selectedRange, frame);
}
void Pasteboard::writePlainTextToDataObject(const String& text, SmartReplaceOption smartReplaceOption)
{
if (!m_writableDataObject)
return;
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
String str = text;
replaceNewlinesWithWindowsStyleNewlines(str);
replaceNBSPWithSpace(str);
medium.hGlobal = createGlobalData(str);
if (medium.hGlobal && FAILED(m_writableDataObject->SetData(plainTextWFormat(), &medium, TRUE)))
::GlobalFree(medium.hGlobal);
}
void Pasteboard::writePlainText(const String& text, SmartReplaceOption smartReplaceOption)
{
clear();
// Put plain string on the pasteboard. CF_UNICODETEXT covers CF_TEXT as well
String str = text;
replaceNewlinesWithWindowsStyleNewlines(str);
if (::OpenClipboard(m_owner)) {
HGLOBAL cbData = createGlobalData(str);
if (!::SetClipboardData(CF_UNICODETEXT, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
// enable smart-replacing later on by putting dummy data on the pasteboard
if (smartReplaceOption == CanSmartReplace) {
if (::OpenClipboard(m_owner)) {
::SetClipboardData(WebSmartPasteFormat, 0);
::CloseClipboard();
}
}
writePlainTextToDataObject(text, smartReplaceOption);
}
static inline void pathRemoveBadFSCharacters(PWSTR psz, size_t length)
{
size_t writeTo = 0;
size_t readFrom = 0;
while (readFrom < length) {
UINT type = PathGetCharType(psz[readFrom]);
if (!psz[readFrom] || type & (GCT_LFNCHAR | GCT_SHORTCHAR))
psz[writeTo++] = psz[readFrom];
readFrom++;
}
psz[writeTo] = 0;
}
static String filesystemPathFromUrlOrTitle(const String& url, const String& title, const String& extension, bool isLink)
{
static const size_t fsPathMaxLengthExcludingNullTerminator = MAX_PATH - 1;
bool usedURL = false;
UChar fsPathBuffer[MAX_PATH];
fsPathBuffer[0] = 0;
int fsPathMaxLengthExcludingExtension = fsPathMaxLengthExcludingNullTerminator - extension.length();
if (!title.isEmpty()) {
size_t len = std::min<size_t>(title.length(), fsPathMaxLengthExcludingExtension);
StringView(title).substring(0, len).getCharactersWithUpconvert(fsPathBuffer);
fsPathBuffer[len] = 0;
pathRemoveBadFSCharacters(wcharFrom(fsPathBuffer), len);
}
if (!wcslen(wcharFrom(fsPathBuffer))) {
URL kurl(URL(), url);
usedURL = true;
// The filename for any content based drag or file url should be the last element of
// the path. If we can't find it, or we're coming up with the name for a link
// we just use the entire url.
DWORD len = fsPathMaxLengthExcludingExtension;
String lastComponent = kurl.lastPathComponent();
if (kurl.isLocalFile() || (!isLink && !lastComponent.isEmpty())) {
len = std::min<DWORD>(fsPathMaxLengthExcludingExtension, lastComponent.length());
StringView(lastComponent).substring(0, len).getCharactersWithUpconvert(fsPathBuffer);
} else {
len = std::min<DWORD>(fsPathMaxLengthExcludingExtension, url.length());
StringView(url).substring(0, len).getCharactersWithUpconvert(fsPathBuffer);
}
fsPathBuffer[len] = 0;
pathRemoveBadFSCharacters(wcharFrom(fsPathBuffer), len);
}
if (extension.isEmpty())
return String(fsPathBuffer);
if (!isLink && usedURL) {
PathRenameExtension(wcharFrom(fsPathBuffer), extension.wideCharacters().data());
return String(fsPathBuffer);
}
return makeString(const_cast<const UChar*>(fsPathBuffer), extension);
}
// writeFileToDataObject takes ownership of fileDescriptor and fileContent
static HRESULT writeFileToDataObject(IDataObject* dataObject, HGLOBAL fileDescriptor, HGLOBAL fileContent, HGLOBAL hDropContent)
{
HRESULT hr = S_OK;
FORMATETC* fe;
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
if (!fileDescriptor || !fileContent)
goto exit;
// Descriptor
fe = fileDescriptorFormat();
medium.hGlobal = fileDescriptor;
if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
goto exit;
// Contents
fe = fileContentFormatZero();
medium.hGlobal = fileContent;
if (FAILED(hr = dataObject->SetData(fe, &medium, TRUE)))
goto exit;
#if USE(CF)
// HDROP
if (hDropContent) {
medium.hGlobal = hDropContent;
hr = dataObject->SetData(cfHDropFormat(), &medium, TRUE);
}
#endif
exit:
if (FAILED(hr)) {
if (fileDescriptor)
GlobalFree(fileDescriptor);
if (fileContent)
GlobalFree(fileContent);
if (hDropContent)
GlobalFree(hDropContent);
}
return hr;
}
void Pasteboard::writeURLToDataObject(const URL& kurl, const String& titleStr)
{
if (!m_writableDataObject)
return;
WebCore::writeURL(m_writableDataObject.get(), kurl, titleStr, true, true);
String url = kurl.string();
ASSERT(url.isAllASCII()); // URL::string() is URL encoded.
String fsPath = filesystemPathFromUrlOrTitle(url, titleStr, ".URL", true);
String contentString("[InternetShortcut]\r\nURL=" + url + "\r\n");
CString content = contentString.latin1();
if (fsPath.length() <= 0)
return;
HGLOBAL urlFileDescriptor = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
if (!urlFileDescriptor)
return;
HGLOBAL urlFileContent = GlobalAlloc(GPTR, content.length());
if (!urlFileContent) {
GlobalFree(urlFileDescriptor);
return;
}
FILEGROUPDESCRIPTOR* fgd = static_cast<FILEGROUPDESCRIPTOR*>(GlobalLock(urlFileDescriptor));
if (!fgd) {
GlobalFree(urlFileDescriptor);
return;
}
ZeroMemory(fgd, sizeof(FILEGROUPDESCRIPTOR));
fgd->cItems = 1;
fgd->fgd[0].dwFlags = FD_FILESIZE;
fgd->fgd[0].nFileSizeLow = content.length();
unsigned maxSize = std::min<unsigned>(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
StringView(fsPath).substring(0, maxSize).getCharactersWithUpconvert(ucharFrom(fgd->fgd[0].cFileName));
GlobalUnlock(urlFileDescriptor);
char* fileContents = static_cast<char*>(GlobalLock(urlFileContent));
if (!fileContents) {
GlobalFree(urlFileDescriptor);
return;
}
CopyMemory(fileContents, content.data(), content.length());
GlobalUnlock(urlFileContent);
writeFileToDataObject(m_writableDataObject.get(), urlFileDescriptor, urlFileContent, 0);
}
void Pasteboard::write(const PasteboardURL& pasteboardURL)
{
ASSERT(!pasteboardURL.url.isEmpty());
clear();
String title(pasteboardURL.title);
if (title.isEmpty()) {
title = pasteboardURL.url.lastPathComponent();
if (title.isEmpty())
title = pasteboardURL.url.host().toString();
}
// write to clipboard in format com.apple.safari.bookmarkdata to be able to paste into the bookmarks view with appropriate title
if (::OpenClipboard(m_owner)) {
HGLOBAL cbData = createGlobalData(pasteboardURL.url, title);
if (!::SetClipboardData(BookmarkClipboardFormat, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
// write to clipboard in format CF_HTML to be able to paste into contenteditable areas as a link
if (::OpenClipboard(m_owner)) {
Vector<char> data;
markupToCFHTML(urlToMarkup(pasteboardURL.url, title), "", data);
HGLOBAL cbData = createGlobalData(data);
if (!::SetClipboardData(HTMLClipboardFormat, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
// bare-bones CF_UNICODETEXT support
if (::OpenClipboard(m_owner)) {
HGLOBAL cbData = createGlobalData(pasteboardURL.url.string());
if (!::SetClipboardData(CF_UNICODETEXT, cbData))
::GlobalFree(cbData);
::CloseClipboard();
}
writeURLToDataObject(pasteboardURL.url, pasteboardURL.title);
}
void Pasteboard::writeTrustworthyWebURLsPboardType(const PasteboardURL&)
{
notImplemented();
}
void Pasteboard::writeImage(Element& element, const URL&, const String&)
{
if (!is<RenderImage>(element.renderer()))
return;
auto& renderer = downcast<RenderImage>(*element.renderer());
CachedImage* cachedImage = renderer.cachedImage();
if (!cachedImage || cachedImage->errorOccurred())
return;
Image* image = cachedImage->imageForRenderer(&renderer);
ASSERT(image);
clear();
HWndDC dc(0);
auto compatibleDC = adoptGDIObject(::CreateCompatibleDC(0));
auto sourceDC = adoptGDIObject(::CreateCompatibleDC(0));
auto resultBitmap = adoptGDIObject(::CreateCompatibleBitmap(dc, image->width(), image->height()));
HGDIOBJ oldBitmap = ::SelectObject(compatibleDC.get(), resultBitmap.get());
BitmapInfo bmInfo = BitmapInfo::create(IntSize(image->size()));
auto coreBitmap = adoptGDIObject(::CreateDIBSection(dc, &bmInfo, DIB_RGB_COLORS, 0, 0, 0));
HGDIOBJ oldSource = ::SelectObject(sourceDC.get(), coreBitmap.get());
image->getHBITMAP(coreBitmap.get());
::BitBlt(compatibleDC.get(), 0, 0, image->width(), image->height(), sourceDC.get(), 0, 0, SRCCOPY);
::SelectObject(sourceDC.get(), oldSource);
::SelectObject(compatibleDC.get(), oldBitmap);
if (::OpenClipboard(m_owner)) {
::SetClipboardData(CF_BITMAP, resultBitmap.leak());
::CloseClipboard();
}
}
bool Pasteboard::canSmartReplace()
{
return ::IsClipboardFormatAvailable(WebSmartPasteFormat);
}
void Pasteboard::read(PasteboardPlainText& text, Optional<size_t>)
{
if (::IsClipboardFormatAvailable(CF_UNICODETEXT) && ::OpenClipboard(m_owner)) {
if (HANDLE cbData = ::GetClipboardData(CF_UNICODETEXT)) {
text.text = static_cast<UChar*>(GlobalLock(cbData));
GlobalUnlock(cbData);
::CloseClipboard();
return;
}
::CloseClipboard();
}
if (::IsClipboardFormatAvailable(CF_TEXT) && ::OpenClipboard(m_owner)) {
if (HANDLE cbData = ::GetClipboardData(CF_TEXT)) {
// FIXME: This treats the characters as Latin-1, not UTF-8 or even Windows Latin-1. Is that the right encoding?
text.text = static_cast<char*>(GlobalLock(cbData));
GlobalUnlock(cbData);
::CloseClipboard();
return;
}
::CloseClipboard();
}
}
RefPtr<DocumentFragment> Pasteboard::documentFragment(Frame& frame, Range& context, bool allowPlainText, bool& chosePlainText)
{
chosePlainText = false;
if (::IsClipboardFormatAvailable(HTMLClipboardFormat) && ::OpenClipboard(m_owner)) {
// get data off of clipboard
HANDLE cbData = ::GetClipboardData(HTMLClipboardFormat);
if (cbData) {
SIZE_T dataSize = ::GlobalSize(cbData);
String cfhtml(UTF8Encoding().decode(static_cast<char*>(GlobalLock(cbData)), dataSize));
GlobalUnlock(cbData);
::CloseClipboard();
return fragmentFromCFHTML(frame.document(), cfhtml);
} else
::CloseClipboard();
}
if (allowPlainText && ::IsClipboardFormatAvailable(CF_UNICODETEXT)) {
chosePlainText = true;
if (::OpenClipboard(m_owner)) {
HANDLE cbData = ::GetClipboardData(CF_UNICODETEXT);
if (cbData) {
UChar* buffer = static_cast<UChar*>(GlobalLock(cbData));
String str(buffer);
GlobalUnlock(cbData);
::CloseClipboard();
return createFragmentFromText(context, str);
} else
::CloseClipboard();
}
}
if (allowPlainText && ::IsClipboardFormatAvailable(CF_TEXT)) {
chosePlainText = true;
if (::OpenClipboard(m_owner)) {
HANDLE cbData = ::GetClipboardData(CF_TEXT);
if (cbData) {
char* buffer = static_cast<char*>(GlobalLock(cbData));
String str(buffer);
GlobalUnlock(cbData);
::CloseClipboard();
return createFragmentFromText(context, str);
} else
::CloseClipboard();
}
}
return nullptr;
}
void Pasteboard::setExternalDataObject(IDataObject *dataObject)
{
m_writableDataObject = 0;
m_dataObject = dataObject;
}
static CachedImage* getCachedImage(Element& element)
{
// Attempt to pull CachedImage from element
RenderObject* renderer = element.renderer();
if (!is<RenderImage>(renderer))
return nullptr;
auto* image = downcast<RenderImage>(renderer);
if (image->cachedImage() && !image->cachedImage()->errorOccurred())
return image->cachedImage();
return nullptr;
}
static HGLOBAL createGlobalImageFileDescriptor(const String& url, const String& title, CachedImage* image)
{
ASSERT_ARG(image, image);
ASSERT(image->image()->data());
String fsPath;
HGLOBAL memObj = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR));
if (!memObj)
return 0;
FILEGROUPDESCRIPTOR* fgd = (FILEGROUPDESCRIPTOR*)GlobalLock(memObj);
if (!fgd) {
GlobalFree(memObj);
return 0;
}
memset(fgd, 0, sizeof(FILEGROUPDESCRIPTOR));
fgd->cItems = 1;
fgd->fgd[0].dwFlags = FD_FILESIZE;
fgd->fgd[0].nFileSizeLow = image->image()->data()->size();
const String& preferredTitle = title.isEmpty() ? image->response().suggestedFilename() : title;
String extension = image->image()->filenameExtension();
if (extension.isEmpty()) {
// Do not continue processing in the rare and unusual case where a decoded image is not able
// to provide a filename extension. Something tricky (like a bait-n-switch) is going on
GlobalUnlock(memObj);
GlobalFree(memObj);
return 0;
}
extension.insert(".", 0);
fsPath = filesystemPathFromUrlOrTitle(url, preferredTitle, extension, false);
if (fsPath.length() <= 0) {
GlobalUnlock(memObj);
GlobalFree(memObj);
return 0;
}
int maxSize = std::min<int>(fsPath.length(), WTF_ARRAY_LENGTH(fgd->fgd[0].cFileName));
StringView(fsPath).substring(0, maxSize).getCharactersWithUpconvert(ucharFrom(fgd->fgd[0].cFileName));
GlobalUnlock(memObj);
return memObj;
}
static HGLOBAL createGlobalImageFileContent(SharedBuffer* data)
{
HGLOBAL memObj = GlobalAlloc(GPTR, data->size());
if (!memObj)
return 0;
char* fileContents = (PSTR)GlobalLock(memObj);
if (!fileContents) {
GlobalFree(memObj);
return 0;
}
if (data->data())
CopyMemory(fileContents, data->data(), data->size());
GlobalUnlock(memObj);
return memObj;
}
static HGLOBAL createGlobalHDropContent(const URL& url, String& fileName, SharedBuffer* data)
{
if (fileName.isEmpty() || !data)
return 0;
WCHAR filePath[MAX_PATH];
if (url.isLocalFile()) {
String localPath = decodeURLEscapeSequences(url.path());
// windows does not enjoy a leading slash on paths
if (localPath[0] == '/')
localPath = localPath.substring(1);
LPCWSTR localPathStr = localPath.wideCharacters().data();
if (localPathStr && wcslen(localPathStr) + 1 < MAX_PATH)
wcscpy_s(filePath, MAX_PATH, localPathStr);
else
return 0;
} else {
WCHAR tempPath[MAX_PATH];
WCHAR extension[MAX_PATH];
if (!::GetTempPath(WTF_ARRAY_LENGTH(tempPath), tempPath))
return 0;
if (!::PathAppend(tempPath, fileName.wideCharacters().data()))
return 0;
LPCWSTR foundExtension = ::PathFindExtension(tempPath);
if (foundExtension) {
if (wcscpy_s(extension, MAX_PATH, foundExtension))
return 0;
} else
*extension = 0;
::PathRemoveExtension(tempPath);
for (int i = 1; i < 10000; i++) {
if (swprintf_s(filePath, MAX_PATH, TEXT("%s-%d%s"), tempPath, i, extension) == -1)
return 0;
if (!::PathFileExists(filePath))
break;
}
HANDLE tempFileHandle = CreateFile(filePath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (tempFileHandle == INVALID_HANDLE_VALUE)
return 0;
// Write the data to this temp file.
DWORD written;
BOOL tempWriteSucceeded = FALSE;
if (data->data())
tempWriteSucceeded = WriteFile(tempFileHandle, data->data(), data->size(), &written, 0);
CloseHandle(tempFileHandle);
if (!tempWriteSucceeded)
return 0;
}
SIZE_T dropFilesSize = sizeof(DROPFILES) + (sizeof(WCHAR) * (wcslen(filePath) + 2));
HGLOBAL memObj = GlobalAlloc(GHND | GMEM_SHARE, dropFilesSize);
if (!memObj)
return 0;
DROPFILES* dropFiles = (DROPFILES*) GlobalLock(memObj);
if (!dropFiles) {
GlobalFree(memObj);
return 0;
}
dropFiles->pFiles = sizeof(DROPFILES);
dropFiles->fWide = TRUE;
wcscpy(reinterpret_cast<LPWSTR>(dropFiles + 1), filePath);
GlobalUnlock(memObj);
return memObj;
}
void Pasteboard::writeImageToDataObject(Element& element, const URL& url)
{
// Shove image data into a DataObject for use as a file
CachedImage* cachedImage = getCachedImage(element);
if (!cachedImage || !cachedImage->imageForRenderer(element.renderer()) || !cachedImage->isLoaded())
return;
SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element.renderer())->data();
if (!imageBuffer || !imageBuffer->size())
return;
HGLOBAL imageFileDescriptor = createGlobalImageFileDescriptor(url.string(), element.attributeWithoutSynchronization(HTMLNames::altAttr), cachedImage);
if (!imageFileDescriptor)
return;
HGLOBAL imageFileContent = createGlobalImageFileContent(imageBuffer);
if (!imageFileContent) {
GlobalFree(imageFileDescriptor);
return;
}
String fileName = cachedImage->response().suggestedFilename();
HGLOBAL hDropContent = createGlobalHDropContent(url, fileName, imageBuffer);
if (!hDropContent) {
GlobalFree(imageFileDescriptor);
GlobalFree(imageFileContent);
return;
}
writeFileToDataObject(m_writableDataObject.get(), imageFileDescriptor, imageFileContent, hDropContent);
}
void Pasteboard::writeURLToWritableDataObject(const URL& url, const String& title)
{
WebCore::writeURL(m_writableDataObject.get(), url, title, true, false);
}
void Pasteboard::writeMarkup(const String& markup)
{
Vector<char> data;
markupToCFHTML(markup, "", data);
STGMEDIUM medium { };
medium.tymed = TYMED_HGLOBAL;
medium.hGlobal = createGlobalData(data);
if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE)))
GlobalFree(medium.hGlobal);
}
void Pasteboard::write(const PasteboardWebContent&)
{
}
void Pasteboard::read(PasteboardWebContentReader&, WebContentReadingPolicy, Optional<size_t>)
{
}
void Pasteboard::write(const PasteboardImage&)
{
}
void Pasteboard::writeCustomData(const Vector<PasteboardCustomData>&)
{
}
void Pasteboard::write(const Color&)
{
}
} // namespace WebCore