blob: c798ea0dea0e7cd0ea5897d16fddbe9456e58ae0 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Many of these functions are based on those found in
// webkit/port/platform/PasteboardWin.cpp
#include "ui/base/clipboard/clipboard_win.h"
#include <objidl.h>
#include <shellapi.h>
#include <shlobj.h>
#include <cstdint>
#include <string_view>
#include <vector>
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_offset_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/win/message_window.h"
#include "base/win/scoped_gdi_object.h"
#include "base/win/scoped_hdc.h"
#include "base/win/scoped_hglobal.h"
#include "clipboard_util.h"
#include "net/base/filename_util.h"
#include "skia/ext/skia_utils_base.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_metrics.h"
#include "ui/base/clipboard/clipboard_util.h"
#include "ui/base/clipboard/clipboard_util_win.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
namespace ui {
namespace {
// A scoper to impersonate the anonymous token and revert when leaving scope
class AnonymousImpersonator {
public:
AnonymousImpersonator() {
must_revert_ = ::ImpersonateAnonymousToken(::GetCurrentThread());
}
AnonymousImpersonator(const AnonymousImpersonator&) = delete;
AnonymousImpersonator& operator=(const AnonymousImpersonator&) = delete;
~AnonymousImpersonator() {
if (must_revert_)
::RevertToSelf();
}
private:
BOOL must_revert_;
};
// A scoper to manage acquiring and automatically releasing the clipboard.
class ScopedClipboard {
public:
ScopedClipboard() : opened_(false) { }
~ScopedClipboard() {
if (opened_)
Release();
}
bool Acquire(HWND owner) {
const int kMaxAttemptsToOpenClipboard = 5;
if (opened_) {
NOTREACHED();
return false;
}
// Attempt to open the clipboard, which will acquire the Windows clipboard
// lock. This may fail if another process currently holds this lock.
// We're willing to try a few times in the hopes of acquiring it.
//
// This turns out to be an issue when using remote desktop because the
// rdpclip.exe process likes to read what we've written to the clipboard and
// send it to the RDP client. If we open and close the clipboard in quick
// succession, we might be trying to open it while rdpclip.exe has it open,
// See Bug 815425.
//
// In fact, we believe we'll only spin this loop over remote desktop. In
// normal situations, the user is initiating clipboard operations and there
// shouldn't be contention.
for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) {
if (::OpenClipboard(owner)) {
opened_ = true;
return true;
}
// If we didn't manage to open the clipboard, sleep a bit and be hopeful.
::Sleep(5);
}
// We failed to acquire the clipboard.
return false;
}
void Release() {
if (opened_) {
// Impersonate the anonymous token during the call to CloseClipboard
// This prevents Windows 8+ capturing the broker's access token which
// could be accessed by lower-privileges chrome processes leading to
// a risk of EoP
AnonymousImpersonator impersonator;
::CloseClipboard();
opened_ = false;
} else {
NOTREACHED();
}
}
private:
bool opened_;
};
bool ClipboardOwnerWndProc(UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
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.
// We always set data, so there isn't a need to actually do anything here.
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.
// We always set data, so there isn't a need to actually do anything here.
break;
case WM_DRAWCLIPBOARD:
break;
case WM_DESTROY:
break;
case WM_CHANGECBCHAIN:
break;
default:
return false;
}
*result = 0;
return true;
}
template <typename charT>
HGLOBAL CreateGlobalData(const std::basic_string<charT>& str) {
HGLOBAL data =
::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT)));
if (data) {
charT* raw_data = static_cast<charT*>(::GlobalLock(data));
memcpy(raw_data, str.data(), str.size() * sizeof(charT));
raw_data[str.size()] = '\0';
::GlobalUnlock(data);
}
return data;
}
bool BitmapHasInvalidPremultipliedColors(const SkPixmap& pixmap) {
for (int x = 0; x < pixmap.width(); ++x) {
for (int y = 0; y < pixmap.height(); ++y) {
uint32_t pixel = *pixmap.addr32(x, y);
if (SkColorGetR(pixel) > SkColorGetA(pixel) ||
SkColorGetG(pixel) > SkColorGetA(pixel) ||
SkColorGetB(pixel) > SkColorGetA(pixel))
return true;
}
}
return false;
}
void MakeBitmapOpaque(SkPixmap* pixmap) {
for (int x = 0; x < pixmap->width(); ++x) {
for (int y = 0; y < pixmap->height(); ++y) {
*pixmap->writable_addr32(x, y) =
SkColorSetA(*pixmap->addr32(x, y), SK_AlphaOPAQUE);
}
}
}
template <typename StringType>
void TrimAfterNull(StringType* result) {
// Text copied to the clipboard may explicitly contain null characters that
// should be ignored, depending on the application that does the copying.
constexpr typename StringType::value_type kNull = 0;
size_t pos = result->find_first_of(kNull);
if (pos != StringType::npos)
result->resize(pos);
}
bool ReadFilenamesAvailable() {
return ::IsClipboardFormatAvailable(
ClipboardFormatType::CFHDropType().ToFormatEtc().cfFormat) ||
::IsClipboardFormatAvailable(
ClipboardFormatType::FilenameType().ToFormatEtc().cfFormat) ||
::IsClipboardFormatAvailable(
ClipboardFormatType::FilenameAType().ToFormatEtc().cfFormat);
}
} // namespace
// Clipboard factory method.
// static
Clipboard* Clipboard::Create() {
return new ClipboardWin;
}
// ClipboardWin implementation.
ClipboardWin::ClipboardWin() {
if (base::CurrentUIThread::IsSet())
clipboard_owner_ = std::make_unique<base::win::MessageWindow>();
}
ClipboardWin::~ClipboardWin() {
}
void ClipboardWin::OnPreShutdown() {}
std::optional<DataTransferEndpoint> ClipboardWin::GetSource(
ClipboardBuffer buffer) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow())) {
return std::nullopt;
}
HANDLE data = ::GetClipboardData(
ClipboardFormatType::InternalSourceUrlType().ToFormatEtc().cfFormat);
if (!data) {
return std::nullopt;
}
std::string source_string;
source_string.assign(static_cast<const char*>(::GlobalLock(data)),
::GlobalSize(data));
::GlobalUnlock(data);
TrimAfterNull(&source_string);
GURL source_url(source_string);
if (!source_url.is_valid()) {
return std::nullopt;
}
return DataTransferEndpoint(std::move(source_url));
}
const ClipboardSequenceNumberToken& ClipboardWin::GetSequenceNumber(
ClipboardBuffer buffer) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
DWORD sequence_number = ::GetClipboardSequenceNumber();
if (sequence_number != clipboard_sequence_.sequence_number) {
// Generate a unique token associated with the current sequence number.
clipboard_sequence_ = {sequence_number, ClipboardSequenceNumberToken()};
}
return clipboard_sequence_.token;
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
bool ClipboardWin::IsFormatAvailable(
const ClipboardFormatType& format,
ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
if (format == ClipboardFormatType::FilenameType())
return ReadFilenamesAvailable();
// Chrome can retrieve an image from the clipboard as either a bitmap or PNG.
if (format == ClipboardFormatType::PngType() ||
format == ClipboardFormatType::BitmapType()) {
return ::IsClipboardFormatAvailable(
ClipboardFormatType::PngType().ToFormatEtc().cfFormat) !=
FALSE ||
::IsClipboardFormatAvailable(
ClipboardFormatType::BitmapType().ToFormatEtc().cfFormat) !=
FALSE;
}
return ::IsClipboardFormatAvailable(format.ToFormatEtc().cfFormat) != FALSE;
}
void ClipboardWin::Clear(ClipboardBuffer buffer) {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
::EmptyClipboard();
}
std::vector<std::u16string> ClipboardWin::GetStandardFormats(
ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst) const {
std::vector<std::u16string> types;
if (::IsClipboardFormatAvailable(
ClipboardFormatType::PlainTextAType().ToFormatEtc().cfFormat))
types.push_back(base::UTF8ToUTF16(kMimeTypeText));
if (::IsClipboardFormatAvailable(
ClipboardFormatType::HtmlType().ToFormatEtc().cfFormat))
types.push_back(base::UTF8ToUTF16(kMimeTypeHTML));
if (::IsClipboardFormatAvailable(
ClipboardFormatType::SvgType().ToFormatEtc().cfFormat))
types.push_back(base::UTF8ToUTF16(kMimeTypeSvg));
if (::IsClipboardFormatAvailable(
ClipboardFormatType::RtfType().ToFormatEtc().cfFormat))
types.push_back(base::UTF8ToUTF16(kMimeTypeRTF));
if (::IsClipboardFormatAvailable(CF_DIB))
types.push_back(base::UTF8ToUTF16(kMimeTypePNG));
if (ReadFilenamesAvailable())
types.push_back(base::UTF8ToUTF16(kMimeTypeURIList));
return types;
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadAvailableTypes(
ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::vector<std::u16string>* types) const {
DCHECK(types);
types->clear();
*types = GetStandardFormats(buffer, data_dst);
// Read the custom type only if it's present on the clipboard.
// See crbug.com/1477344 for details.
if (!IsFormatAvailable(ClipboardFormatType::WebCustomDataType(), buffer,
data_dst)) {
return;
}
// Acquire the clipboard to read WebCustomDataType types.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE hdata = ::GetClipboardData(
ClipboardFormatType::WebCustomDataType().ToFormatEtc().cfFormat);
if (!hdata)
return;
base::win::ScopedHGlobal<const uint8_t*> locked_data(hdata);
ReadCustomDataTypes(locked_data, types);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadText(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::u16string* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kText);
if (!result) {
NOTREACHED();
return;
}
result->clear();
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE data = ::GetClipboardData(CF_UNICODETEXT);
if (!data)
return;
result->assign(static_cast<const char16_t*>(::GlobalLock(data)),
::GlobalSize(data) / sizeof(char16_t));
::GlobalUnlock(data);
TrimAfterNull(result);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadAsciiText(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::string* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kText);
if (!result) {
NOTREACHED();
return;
}
result->clear();
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE data = ::GetClipboardData(CF_TEXT);
if (!data)
return;
result->assign(static_cast<const char*>(::GlobalLock(data)),
::GlobalSize(data));
::GlobalUnlock(data);
TrimAfterNull(result);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadHTML(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::u16string* markup,
std::string* src_url,
uint32_t* fragment_start,
uint32_t* fragment_end) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kHtml);
markup->clear();
// TODO(dcheng): Remove these checks, I don't think they should be optional.
DCHECK(src_url);
if (src_url)
src_url->clear();
*fragment_start = 0;
*fragment_end = 0;
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE data = ::GetClipboardData(
ClipboardFormatType::HtmlType().ToFormatEtc().cfFormat);
if (!data)
return;
std::string cf_html(static_cast<const char*>(::GlobalLock(data)),
::GlobalSize(data));
::GlobalUnlock(data);
TrimAfterNull(&cf_html);
size_t html_start = std::string::npos;
size_t start_index = std::string::npos;
size_t end_index = std::string::npos;
clipboard_util::CFHtmlExtractMetadata(cf_html, src_url, &html_start,
&start_index, &end_index);
// This might happen if the contents of the clipboard changed and CF_HTML is
// no longer available.
if (start_index == std::string::npos ||
end_index == std::string::npos ||
html_start == std::string::npos)
return;
if (start_index < html_start || end_index < start_index)
return;
std::vector<size_t> offsets = {start_index - html_start,
end_index - html_start};
markup->assign(base::UTF8ToUTF16AndAdjustOffsets(cf_html.data() + html_start,
&offsets));
// Ensure the Fragment points within the string; see https://crbug.com/607181.
size_t end = std::min(offsets[1], markup->length());
*fragment_start = base::checked_cast<uint32_t>(std::min(offsets[0], end));
*fragment_end = base::checked_cast<uint32_t>(end);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadSvg(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::u16string* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kSvg);
std::string data;
ReadData(ClipboardFormatType::SvgType(), data_dst, &data);
result->assign(reinterpret_cast<const char16_t*>(data.data()),
data.size() / sizeof(char16_t));
TrimAfterNull(result);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadRTF(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::string* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kRtf);
ReadData(ClipboardFormatType::RtfType(), data_dst, result);
TrimAfterNull(result);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadPng(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
ReadPngCallback callback) const {
RecordRead(ClipboardFormatMetric::kPng);
std::vector<uint8_t> data = ReadPngInternal(buffer);
// On Windows, PNG and bitmap are separate formats. Read PNG if possible,
// otherwise fall back to reading as a bitmap.
if (!data.empty()) {
std::move(callback).Run(data);
return;
}
SkBitmap bitmap = ReadBitmapInternal(buffer);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(&clipboard_util::EncodeBitmapToPng, bitmap),
std::move(callback));
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadCustomData(ClipboardBuffer buffer,
const std::u16string& type,
const DataTransferEndpoint* data_dst,
std::u16string* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
RecordRead(ClipboardFormatMetric::kCustomData);
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE hdata = ::GetClipboardData(
ClipboardFormatType::WebCustomDataType().ToFormatEtc().cfFormat);
if (!hdata)
return;
base::win::ScopedHGlobal<const uint8_t*> locked_data(hdata);
if (std::optional<std::u16string> maybe_result =
ReadCustomDataForType(locked_data, type);
maybe_result) {
*result = std::move(*maybe_result);
}
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadFilenames(ClipboardBuffer buffer,
const DataTransferEndpoint* data_dst,
std::vector<ui::FileInfo>* result) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
DCHECK(result);
RecordRead(ClipboardFormatMetric::kFilenames);
result->clear();
if (!ReadFilenamesAvailable())
return;
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
// TODO(crbug.com/40749279): Refactor similar code in clipboard_utils_win:
// clipboard_util::GetFilenames() and reuse rather than duplicate.
HANDLE data = ::GetClipboardData(
ClipboardFormatType::CFHDropType().ToFormatEtc().cfFormat);
if (data) {
{
base::win::ScopedHGlobal<HDROP> hdrop(data);
if (!hdrop.data()) {
return;
}
const int kMaxFilenameLen = 4096;
const unsigned num_files = DragQueryFileW(hdrop.data(), 0xffffffff, 0, 0);
for (unsigned int i = 0; i < num_files; ++i) {
wchar_t filename[kMaxFilenameLen];
if (!DragQueryFileW(hdrop.data(), i, filename, kMaxFilenameLen)) {
continue;
}
base::FilePath path(filename);
result->push_back(ui::FileInfo(path, base::FilePath()));
}
}
return;
}
data = ::GetClipboardData(
ClipboardFormatType::FilenameType().ToFormatEtc().cfFormat);
if (data) {
{
// filename using Unicode
base::win::ScopedHGlobal<wchar_t*> filename(data);
if (filename.data() && filename.data()[0]) {
base::FilePath path(filename.data());
result->push_back(ui::FileInfo(path, base::FilePath()));
}
}
return;
}
data = ::GetClipboardData(
ClipboardFormatType::FilenameAType().ToFormatEtc().cfFormat);
if (data) {
{
// filename using ASCII
base::win::ScopedHGlobal<char*> filename(data);
if (filename.data() && filename.data()[0]) {
base::FilePath path(base::SysNativeMBToWide(filename.data()));
result->push_back(ui::FileInfo(path, base::FilePath()));
}
}
}
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadBookmark(const DataTransferEndpoint* data_dst,
std::u16string* title,
std::string* url) const {
RecordRead(ClipboardFormatMetric::kBookmark);
if (title)
title->clear();
if (url)
url->clear();
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE data =
::GetClipboardData(ClipboardFormatType::UrlType().ToFormatEtc().cfFormat);
if (!data)
return;
std::u16string bookmark(static_cast<const char16_t*>(::GlobalLock(data)),
::GlobalSize(data) / sizeof(char16_t));
::GlobalUnlock(data);
TrimAfterNull(&bookmark);
*url = base::UTF16ToUTF8(bookmark);
}
// |data_dst| is not used. It's only passed to be consistent with other
// platforms.
void ClipboardWin::ReadData(const ClipboardFormatType& format,
const DataTransferEndpoint* data_dst,
std::string* result) const {
RecordRead(ClipboardFormatMetric::kData);
if (!result) {
NOTREACHED();
return;
}
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
HANDLE data = ::GetClipboardData(format.ToFormatEtc().cfFormat);
if (!data)
return;
result->assign(static_cast<const char*>(::GlobalLock(data)),
::GlobalSize(data));
::GlobalUnlock(data);
}
void ClipboardWin::WritePortableAndPlatformRepresentations(
ClipboardBuffer buffer,
const ObjectMap& objects,
std::vector<Clipboard::PlatformRepresentation> platform_representations,
std::unique_ptr<DataTransferEndpoint> data_src,
uint32_t privacy_types) {
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return;
::EmptyClipboard();
DispatchPlatformRepresentations(std::move(platform_representations));
for (const auto& object : objects)
DispatchPortableRepresentation(object.second);
if (data_src && data_src->IsUrlType()) {
HGLOBAL glob = CreateGlobalData(data_src->GetURL()->spec());
WriteToClipboard(ClipboardFormatType::InternalSourceUrlType(), glob);
}
// Write privacy data if there is any.
// On Windows, there is no special format to conceal passwords, but
// don't save it in the history or cloud clipboard for privacy reasons.
if (privacy_types & Clipboard::PrivacyTypes::kNoDisplay) {
WriteConfidentialDataForPassword();
} else {
if (privacy_types & Clipboard::PrivacyTypes::kNoLocalClipboardHistory) {
WriteClipboardHistory();
}
if (privacy_types & Clipboard::PrivacyTypes::kNoCloudClipboard) {
WriteUploadCloudClipboard();
}
}
}
void ClipboardWin::WriteText(std::string_view text) {
HGLOBAL glob = CreateGlobalData(base::UTF8ToUTF16(text));
WriteToClipboard(ClipboardFormatType::PlainTextType(), glob);
}
void ClipboardWin::WriteHTML(std::string_view markup,
std::optional<std::string_view> source_url) {
// Add Windows specific headers to the HTML payload before writing to the
// clipboard.
std::string html_fragment =
clipboard_util::HtmlToCFHtml(markup, source_url.value_or(""));
HGLOBAL glob = CreateGlobalData(html_fragment);
WriteToClipboard(ClipboardFormatType::HtmlType(), glob);
}
void ClipboardWin::WriteSvg(std::string_view markup) {
HGLOBAL glob = CreateGlobalData(base::UTF8ToUTF16(markup));
WriteToClipboard(ClipboardFormatType::SvgType(), glob);
}
void ClipboardWin::WriteRTF(std::string_view rtf) {
WriteData(ClipboardFormatType::RtfType(),
base::as_bytes(base::make_span(rtf)));
}
void ClipboardWin::WriteFilenames(std::vector<ui::FileInfo> filenames) {
STGMEDIUM storage = clipboard_util::CreateStorageForFileNames(filenames);
if (storage.tymed == TYMED_NULL)
return;
WriteToClipboard(ClipboardFormatType::CFHDropType(), storage.hGlobal);
}
void ClipboardWin::WriteBookmark(std::string_view title, std::string_view url) {
// On Windows, CFSTR_INETURLW is expected to only contain the URL & not the
// title separated by a newline.
// https://docs.microsoft.com/en-us/windows/win32/shell/clipboard#cfstr_ineturl.
HGLOBAL glob = CreateGlobalData(base::UTF8ToUTF16(url));
WriteToClipboard(ClipboardFormatType::UrlType(), glob);
}
void ClipboardWin::WriteWebSmartPaste() {
DCHECK_NE(clipboard_owner_->hwnd(), nullptr);
::SetClipboardData(
ClipboardFormatType::WebKitSmartPasteType().ToFormatEtc().cfFormat,
nullptr);
}
void ClipboardWin::WriteBitmap(const SkBitmap& bitmap) {
CHECK_EQ(bitmap.colorType(), kN32_SkColorType);
// On Windows there are 2 ways of writing a transparent image to the
// clipboard: Using the DIBV5 format with correct header and format or using
// the PNG format. Some programs only support one or the other. In particular,
// Word support for DIBV5 is buggy and PNG format is needed for it. Writing
// order is also important as some programs will use the first compatible
// format that is available on the clipboard, and we want Word to choose the
// PNG format.
//
// Encode the bitmap to a PNG from the UI thread. Ideally this CPU-intensive
// encoding operation would be performed on a background thread, but
// ui::base::Clipboard writes are (unfortunately) synchronous.
// We could consider making writes async, then moving this image encoding to a
// background sequence.
std::vector<uint8_t> png_encoded_bitmap =
clipboard_util::EncodeBitmapToPngAcceptJank(bitmap);
if (!png_encoded_bitmap.empty()) {
HGLOBAL png_hglobal = skia::CreateHGlobalForByteArray(png_encoded_bitmap);
if (png_hglobal)
WriteToClipboard(ClipboardFormatType::PngType(), png_hglobal);
}
HGLOBAL dibv5_hglobal = skia::CreateDIBV5ImageDataFromN32SkBitmap(bitmap);
if (dibv5_hglobal)
WriteToClipboard(ClipboardFormatType::BitmapType(), dibv5_hglobal);
}
void ClipboardWin::WriteData(const ClipboardFormatType& format,
base::span<const uint8_t> data) {
HGLOBAL hdata = ::GlobalAlloc(GMEM_MOVEABLE, data.size());
if (!hdata)
return;
char* hdata_ptr = static_cast<char*>(::GlobalLock(hdata));
memcpy(hdata_ptr, data.data(), data.size());
::GlobalUnlock(hdata);
WriteToClipboard(format, hdata);
}
void ClipboardWin::WriteClipboardHistory() {
// Write a zero value to the clipboard to indicate that the clipboard history
// is not available.
DWORD value = 0;
WriteData(
ClipboardFormatType::ClipboardHistoryType(),
base::make_span(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
}
void ClipboardWin::WriteUploadCloudClipboard() {
// Write a zero value to the clipboard to indicate that the cloud clipboard
// is not available.
DWORD value = 0;
WriteData(
ClipboardFormatType::UploadCloudClipboardType(),
base::make_span(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
}
void ClipboardWin::WriteConfidentialDataForPassword() {
// Write a zero value to the clipboard to indicate that the clipboard history
// and cloud clipboard are not available.
DWORD value = 0;
WriteData(
ClipboardFormatType::ClipboardHistoryType(),
base::make_span(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
WriteData(
ClipboardFormatType::UploadCloudClipboardType(),
base::make_span(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
}
std::vector<uint8_t> ClipboardWin::ReadPngInternal(
ClipboardBuffer buffer) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return std::vector<uint8_t>();
HANDLE data =
::GetClipboardData(ClipboardFormatType::PngType().ToFormatEtc().cfFormat);
if (!data)
return std::vector<uint8_t>();
std::string result(static_cast<const char*>(::GlobalLock(data)),
::GlobalSize(data));
::GlobalUnlock(data);
return std::vector<uint8_t>(result.begin(), result.end());
}
SkBitmap ClipboardWin::ReadBitmapInternal(ClipboardBuffer buffer) const {
DCHECK_EQ(buffer, ClipboardBuffer::kCopyPaste);
// Acquire the clipboard.
ScopedClipboard clipboard;
if (!clipboard.Acquire(GetClipboardWindow()))
return SkBitmap();
// We use a DIB rather than a DDB here since ::GetObject() with the
// HBITMAP returned from ::GetClipboardData(CF_BITMAP) always reports a color
// depth of 32bpp.
BITMAPINFO* bitmap = static_cast<BITMAPINFO*>(::GetClipboardData(CF_DIB));
if (!bitmap)
return SkBitmap();
int color_table_length = 0;
// Image is too large, and may cause an allocation failure.
// See https://crbug.com/1164680.
constexpr size_t kMaxImageSizeBytes = 1 << 27; // 128 MiB
size_t image_size_bytes;
// Estimate the number of bytes per pixel. For images with fewer than one byte
// pixel we will over-estimate the size. For compressed images we will
// over-estimate the size, but some overestimating of the storage size is okay
// and the calculation will be a good estimate of the decompressed size. The
// reported bug was for uncompressed 32-bit images where this code will give
// correct results.
size_t bytes_per_pixel = bitmap->bmiHeader.biBitCount / 8;
if (bytes_per_pixel == 0)
bytes_per_pixel = 1;
// Calculate the size of the bitmap. This is not an exact calculation but that
// doesn't matter for this purpose. If the calculation overflows then the
// image is too big. Return an empty image.
if (!base::CheckMul(
bitmap->bmiHeader.biWidth,
base::CheckMul(bitmap->bmiHeader.biHeight, bytes_per_pixel))
.AssignIfValid(&image_size_bytes))
return SkBitmap();
// If the image size is too big then return an empty image.
if (image_size_bytes > kMaxImageSizeBytes)
return SkBitmap();
// For more information on BITMAPINFOHEADER and biBitCount definition,
// see https://docs.microsoft.com/en-us/windows/win32/wmdm/-bitmapinfoheader
switch (bitmap->bmiHeader.biBitCount) {
case 1:
case 4:
case 8:
color_table_length = bitmap->bmiHeader.biClrUsed
? bitmap->bmiHeader.biClrUsed
: 1 << bitmap->bmiHeader.biBitCount;
break;
case 16:
case 32:
if (bitmap->bmiHeader.biCompression == BI_BITFIELDS)
color_table_length = 3;
break;
case 24:
break;
default:
NOTREACHED();
}
const void* bitmap_bits = reinterpret_cast<const char*>(bitmap) +
bitmap->bmiHeader.biSize +
color_table_length * sizeof(RGBQUAD);
void* dst_bits;
// dst_hbitmap is freed by the release_proc in skia_bitmap (below)
base::win::ScopedBitmap dst_hbitmap = skia::CreateHBitmapXRGB8888(
bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, 0, &dst_bits);
{
base::win::ScopedCreateDC hdc(CreateCompatibleDC(nullptr));
HBITMAP old_hbitmap =
static_cast<HBITMAP>(SelectObject(hdc.Get(), dst_hbitmap.get()));
::SetDIBitsToDevice(hdc.Get(), 0, 0, bitmap->bmiHeader.biWidth,
bitmap->bmiHeader.biHeight, 0, 0, 0,
bitmap->bmiHeader.biHeight, bitmap_bits, bitmap,
DIB_RGB_COLORS);
SelectObject(hdc.Get(), old_hbitmap);
}
// Windows doesn't really handle alpha channels well in many situations. When
// the source image is < 32 bpp, we force the bitmap to be opaque. When the
// source image is 32 bpp, the alpha channel might still contain garbage data.
// Since Windows uses premultiplied alpha, we scan for instances where
// (R, G, B) > A. If there are any invalid premultiplied colors in the image,
// we assume the alpha channel contains garbage and force the bitmap to be
// opaque as well. This heuristic will fail on a transparent bitmap
// containing only black pixels...
SkPixmap device_pixels(SkImageInfo::MakeN32Premul(bitmap->bmiHeader.biWidth,
bitmap->bmiHeader.biHeight),
dst_bits, bitmap->bmiHeader.biWidth * 4);
{
bool has_invalid_alpha_channel =
bitmap->bmiHeader.biBitCount < 32 ||
BitmapHasInvalidPremultipliedColors(device_pixels);
if (has_invalid_alpha_channel) {
MakeBitmapOpaque(&device_pixels);
}
}
SkBitmap skia_bitmap;
skia_bitmap.installPixels(
device_pixels.info(), device_pixels.writable_addr(),
device_pixels.rowBytes(),
[](void* pixels, void* hbitmap) {
DeleteObject(static_cast<HBITMAP>(hbitmap));
},
dst_hbitmap.release());
return skia_bitmap;
}
void ClipboardWin::WriteToClipboard(ClipboardFormatType format, HANDLE handle) {
UINT cf_format = format.ToFormatEtc().cfFormat;
DCHECK_NE(clipboard_owner_->hwnd(), nullptr);
if (handle && !::SetClipboardData(cf_format, handle)) {
DCHECK_NE(GetLastError(),
static_cast<unsigned long>(ERROR_CLIPBOARD_NOT_OPEN));
::GlobalFree(handle);
}
}
HWND ClipboardWin::GetClipboardWindow() const {
if (!clipboard_owner_)
return nullptr;
if (clipboard_owner_->hwnd() == nullptr)
clipboard_owner_->Create(base::BindRepeating(&ClipboardOwnerWndProc));
return clipboard_owner_->hwnd();
}
} // namespace ui