diff options
Diffstat (limited to 'Source/WebCore/platform/win/PasteboardWin.cpp')
-rw-r--r-- | Source/WebCore/platform/win/PasteboardWin.cpp | 1060 |
1 files changed, 1060 insertions, 0 deletions
diff --git a/Source/WebCore/platform/win/PasteboardWin.cpp b/Source/WebCore/platform/win/PasteboardWin.cpp new file mode 100644 index 000000000..031bfd249 --- /dev/null +++ b/Source/WebCore/platform/win/PasteboardWin.cpp @@ -0,0 +1,1060 @@ +/* + * 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 "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 "URL.h" +#include "NotImplemented.h" +#include "Page.h" +#include "Range.h" +#include "RenderImage.h" +#include "SharedBuffer.h" +#include "TextEncoding.h" +#include "WebCoreInstanceHandle.h" +#include "markup.h" +#include <wtf/WindowsExtras.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringView.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 = std::make_unique<Pasteboard>(); + COMPtr<IDataObject> clipboardData; + if (!SUCCEEDED(OleGetClipboard(&clipboardData))) + clipboardData = 0; + pasteboard->setExternalDataObject(clipboardData.get()); + return pasteboard; +} + +std::unique_ptr<Pasteboard> Pasteboard::createPrivate() +{ + // Windows has no "Private pasteboard" concept. + return createForCopyAndPaste(); +} + +#if ENABLE(DRAG_SUPPORT) +std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop() +{ + COMPtr<WCDataObject> dataObject; + WCDataObject::createInstance(&dataObject); + return std::make_unique<Pasteboard>(dataObject.get()); +} + +// static +std::unique_ptr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData) +{ + if (dragData.platformData()) + return std::make_unique<Pasteboard>(dragData.platformData()); + // FIXME: Should add a const overload of dragDataMap so we don't need a const_cast here. + return std::make_unique<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) +{ + String strippedType = type.stripWhiteSpace(); + + // two special cases for IE compatibility + if (equalLettersIgnoringASCIICase(strippedType, "text") || equalLettersIgnoringASCIICase(strippedType, "text/plain") || strippedType.startsWith("text/plain;", false)) + return ClipboardDataTypeText; + if (equalLettersIgnoringASCIICase(strippedType, "url") || equalLettersIgnoringASCIICase(strippedType, "text/uri-list")) + return ClipboardDataTypeURL; + if (equalLettersIgnoringASCIICase(strippedType, "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) +{ + // URL and Text are provided for compatibility with IE's model + if (format.cfFormat == urlFormat()->cfFormat || format.cfFormat == urlWFormat()->cfFormat) { + results.add("URL"); + results.add("text/uri-list"); + } + + if (format.cfFormat == plainTextWFormat()->cfFormat || format.cfFormat == plainTextFormat()->cfFormat) { + results.add("Text"); + results.add("text/plain"); + } +} + +Vector<String> Pasteboard::types() +{ + 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); + } + } + + Vector<String> vector; + copyToVector(results, vector); + return vector; +} + +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 ""; +} + +Vector<String> Pasteboard::readFilenames() +{ + Vector<String> fileNames; + +#if USE(CF) + if (m_dataObject) { + STGMEDIUM medium; + if (FAILED(m_dataObject->GetData(cfHDropFormat(), &medium))) + return fileNames; + + HDROP hdrop = reinterpret_cast<HDROP>(GlobalLock(medium.hGlobal)); + if (!hdrop) + return fileNames; + + 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; + fileNames.append(filename); + } + + GlobalUnlock(medium.hGlobal); + ReleaseStgMedium(&medium); + return fileNames; + } + if (!m_dragDataMap.contains(cfHDropFormat()->cfFormat)) + return fileNames; + return m_dragDataMap.get(cfHDropFormat()->cfFormat); +#else + notImplemented(); + return fileNames; +#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(); + } + + STGMEDIUM medium = {0}; + 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 = {0}; + 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(DragImageRef, const IntPoint&) +{ + // Do nothing in Windows. +} +#endif + +void Pasteboard::writeRangeToDataObject(Range& selectedRange, Frame& frame) +{ + if (!m_writableDataObject) + return; + + STGMEDIUM medium = {0}; + medium.tymed = TYMED_HGLOBAL; + + Vector<char> data; + markupToCFHTML(createMarkup(selectedRange, 0, AnnotateForInterchange), + 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; + markupToCFHTML(createMarkup(selectedRange, 0, AnnotateForInterchange), + 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 = {0}; + 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 UChar* extension, bool isLink) +{ + static const size_t fsPathMaxLengthExcludingNullTerminator = MAX_PATH - 1; + bool usedURL = false; + WCHAR fsPathBuffer[MAX_PATH]; + fsPathBuffer[0] = 0; + int extensionLen = extension ? lstrlen(extension) : 0; + int fsPathMaxLengthExcludingExtension = fsPathMaxLengthExcludingNullTerminator - extensionLen; + + if (!title.isEmpty()) { + size_t len = std::min<size_t>(title.length(), fsPathMaxLengthExcludingExtension); + StringView(title).substring(0, len).getCharactersWithUpconvert(fsPathBuffer); + fsPathBuffer[len] = 0; + pathRemoveBadFSCharacters(fsPathBuffer, len); + } + + if (!lstrlen(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(fsPathBuffer, len); + } + + if (!extension) + return String(static_cast<UChar*>(fsPathBuffer)); + + if (!isLink && usedURL) { + PathRenameExtension(fsPathBuffer, extension); + return String(static_cast<UChar*>(fsPathBuffer)); + } + + return makeString(static_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 = {0}; + 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.containsOnlyASCII()); // URL::string() is URL encoded. + + String fsPath = filesystemPathFromUrlOrTitle(url, titleStr, L".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(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(); + } + + // 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::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(); + } +} + +void Pasteboard::writePasteboard(const Pasteboard& sourcePasteboard) +{ + notImplemented(); +} + +bool Pasteboard::canSmartReplace() +{ + return ::IsClipboardFormatAvailable(WebSmartPasteFormat); +} + +void Pasteboard::read(PasteboardPlainText& text) +{ + 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(); + } +} + +PassRefPtr<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(); + + RefPtr<DocumentFragment> fragment = fragmentFromCFHTML(frame.document(), cfhtml); + if (fragment) + return fragment.release(); + } 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(); + RefPtr<DocumentFragment> fragment = createFragmentFromText(context, str); + if (fragment) + return fragment.release(); + } 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(); + RefPtr<DocumentFragment> fragment = createFragmentFromText(context, str); + if (fragment) + return fragment.release(); + } else + ::CloseClipboard(); + } + } + + return 0; +} + +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()); + + HRESULT hr = S_OK; + 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.charactersWithNullTermination().data(), 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(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); + const Vector<UChar>& localPathWide = localPath.charactersWithNullTermination(); + LPCWSTR localPathStr = localPathWide.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.charactersWithNullTermination().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((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.fastGetAttribute(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 = {0}; + medium.tymed = TYMED_HGLOBAL; + + medium.hGlobal = createGlobalData(data); + if (medium.hGlobal && FAILED(m_writableDataObject->SetData(htmlFormat(), &medium, TRUE))) + GlobalFree(medium.hGlobal); +} + +} // namespace WebCore |