| /* |
| * 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/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) |
| { |
| 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) |
| { |
| } |
| |
| void Pasteboard::write(const PasteboardImage&) |
| { |
| } |
| |
| void Pasteboard::writeCustomData(const PasteboardCustomData&) |
| { |
| } |
| |
| void Pasteboard::write(const Color&) |
| { |
| } |
| |
| } // namespace WebCore |