blob: f34527be79871d4bbcef844687353471a75549e9 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/renderer_host/clipboard_host_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/pickle.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/browser_context.h"
#include "ipc/ipc_message_macros.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "url/gurl.h"
namespace content {
namespace {
void ReleaseSharedMemoryPixels(void* addr, void* context) {
MojoResult result = MojoUnmapBuffer(context);
DCHECK_EQ(MOJO_RESULT_OK, result);
}
// No-op helper for delayed cleanup of BlobHandles generated by reading
// clipboard images.
void CleanupReadImageBlob(std::unique_ptr<content::BlobHandle>) {}
} // namespace
ClipboardHostImpl::ClipboardHostImpl(
scoped_refptr<ChromeBlobStorageContext> blob_storage_context)
: clipboard_(ui::Clipboard::GetForCurrentThread()),
blob_storage_context_(std::move(blob_storage_context)),
clipboard_writer_(
new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void ClipboardHostImpl::Create(
scoped_refptr<ChromeBlobStorageContext> blob_storage_context,
mojom::ClipboardHostRequest request) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
mojo::MakeStrongBinding(
base::WrapUnique<ClipboardHostImpl>(
new ClipboardHostImpl(std::move(blob_storage_context))),
std::move(request));
}
ClipboardHostImpl::~ClipboardHostImpl() {
clipboard_writer_->Reset();
}
void ClipboardHostImpl::GetSequenceNumber(blink::mojom::ClipboardBuffer buffer,
GetSequenceNumberCallback callback) {
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run(clipboard_->GetSequenceNumber(clipboard_type));
}
void ClipboardHostImpl::ReadAvailableTypes(
blink::mojom::ClipboardBuffer buffer,
ReadAvailableTypesCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
std::vector<base::string16> types;
bool contains_filenames;
clipboard_->ReadAvailableTypes(clipboard_type, &types, &contains_filenames);
std::move(callback).Run(types, contains_filenames);
}
void ClipboardHostImpl::IsFormatAvailable(blink::mojom::ClipboardFormat format,
blink::mojom::ClipboardBuffer buffer,
IsFormatAvailableCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
bool result = false;
switch (format) {
case blink::mojom::ClipboardFormat::kPlaintext:
result = clipboard_->IsFormatAvailable(
ui::Clipboard::GetPlainTextWFormatType(), clipboard_type) ||
clipboard_->IsFormatAvailable(
ui::Clipboard::GetPlainTextFormatType(), clipboard_type);
break;
case blink::mojom::ClipboardFormat::kHtml:
result = clipboard_->IsFormatAvailable(ui::Clipboard::GetHtmlFormatType(),
clipboard_type);
break;
case blink::mojom::ClipboardFormat::kSmartPaste:
result = clipboard_->IsFormatAvailable(
ui::Clipboard::GetWebKitSmartPasteFormatType(), clipboard_type);
break;
case blink::mojom::ClipboardFormat::kBookmark:
#if defined(OS_WIN) || defined(OS_MACOSX)
result = clipboard_->IsFormatAvailable(ui::Clipboard::GetUrlWFormatType(),
clipboard_type);
#else
result = false;
#endif
break;
}
std::move(callback).Run(result);
}
void ClipboardHostImpl::ReadText(blink::mojom::ClipboardBuffer buffer,
ReadTextCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
base::string16 result;
if (clipboard_->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(),
clipboard_type)) {
clipboard_->ReadText(clipboard_type, &result);
} else if (clipboard_->IsFormatAvailable(
ui::Clipboard::GetPlainTextFormatType(), clipboard_type)) {
std::string ascii;
clipboard_->ReadAsciiText(clipboard_type, &ascii);
result = base::ASCIIToUTF16(ascii);
}
std::move(callback).Run(result);
}
void ClipboardHostImpl::ReadHtml(blink::mojom::ClipboardBuffer buffer,
ReadHtmlCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
base::string16 markup;
std::string src_url_str;
uint32_t fragment_start = 0;
uint32_t fragment_end = 0;
clipboard_->ReadHTML(clipboard_type, &markup, &src_url_str, &fragment_start,
&fragment_end);
std::move(callback).Run(std::move(markup), GURL(src_url_str), fragment_start,
fragment_end);
}
void ClipboardHostImpl::ReadRtf(blink::mojom::ClipboardBuffer buffer,
ReadRtfCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
std::string result;
clipboard_->ReadRTF(clipboard_type, &result);
std::move(callback).Run(result);
}
void ClipboardHostImpl::ReadImage(blink::mojom::ClipboardBuffer buffer,
ReadImageCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
SkBitmap bitmap = clipboard_->ReadImage(clipboard_type);
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ClipboardHostImpl::ReadAndEncodeImage,
base::Unretained(this), bitmap, std::move(callback)));
}
void ClipboardHostImpl::ReadAndEncodeImage(const SkBitmap& bitmap,
ReadImageCallback callback) {
if (!bitmap.isNull()) {
std::vector<uint8_t> png_data;
if (gfx::PNGCodec::FastEncodeBGRASkBitmap(bitmap, false, &png_data)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&ClipboardHostImpl::OnReadAndEncodeImageFinished,
base::Unretained(this), std::move(png_data),
std::move(callback)));
return;
}
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(
[](ReadImageCallback callback) {
std::move(callback).Run(std::string(),
std::string(), -1);
},
std::move(callback)));
}
void ClipboardHostImpl::OnReadAndEncodeImageFinished(
std::vector<uint8_t> png_data,
ReadImageCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (png_data.size() < std::numeric_limits<uint32_t>::max()) {
std::unique_ptr<content::BlobHandle> blob_handle =
blob_storage_context_->CreateMemoryBackedBlob(
reinterpret_cast<char*>(png_data.data()), png_data.size(), "");
if (blob_handle) {
std::move(callback).Run(blob_handle->GetUUID(),
ui::Clipboard::kMimeTypePNG,
static_cast<int64_t>(png_data.size()));
// Give the renderer a minute to pick up a reference to the blob before
// giving up.
// TODO(dmurph): There should be a better way of transferring ownership of
// a blob from the browser to the renderer, rather than relying on this
// timeout to clean up eventually. See https://crbug.com/604800.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&CleanupReadImageBlob, base::Passed(&blob_handle)),
base::TimeDelta::FromMinutes(1));
return;
}
}
std::move(callback).Run(std::string(), std::string(), -1);
}
void ClipboardHostImpl::ReadCustomData(blink::mojom::ClipboardBuffer buffer,
const base::string16& type,
ReadCustomDataCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
ui::ClipboardType clipboard_type;
ConvertBufferType(buffer, &clipboard_type);
base::string16 result;
clipboard_->ReadCustomData(clipboard_type, type, &result);
std::move(callback).Run(result);
}
void ClipboardHostImpl::WriteText(blink::mojom::ClipboardBuffer,
const base::string16& text) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
clipboard_writer_->WriteText(text);
}
void ClipboardHostImpl::WriteHtml(blink::mojom::ClipboardBuffer,
const base::string16& markup,
const GURL& url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
clipboard_writer_->WriteHTML(markup, url.spec());
}
void ClipboardHostImpl::WriteSmartPasteMarker(
blink::mojom::ClipboardBuffer buffer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
clipboard_writer_->WriteWebSmartPaste();
}
void ClipboardHostImpl::WriteCustomData(
blink::mojom::ClipboardBuffer,
const std::unordered_map<base::string16, base::string16>& data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::Pickle pickle;
ui::WriteCustomDataToPickle(data, &pickle);
clipboard_writer_->WritePickledData(
pickle, ui::Clipboard::GetWebCustomDataFormatType());
}
void ClipboardHostImpl::WriteBookmark(blink::mojom::ClipboardBuffer,
const std::string& url,
const base::string16& title) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
clipboard_writer_->WriteBookmark(title, url);
}
void ClipboardHostImpl::WriteImage(
blink::mojom::ClipboardBuffer,
const gfx::Size& size,
mojo::ScopedSharedBufferHandle shared_buffer_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
SkBitmap bitmap;
// Let Skia do some sanity checking for (no negative widths/heights, no
// overflows while calculating bytes per row, etc).
if (!bitmap.setInfo(
SkImageInfo::MakeN32Premul(size.width(), size.height()))) {
return;
}
auto mapped = shared_buffer_handle->Map(bitmap.computeByteSize());
if (!mapped) {
return;
}
if (!bitmap.installPixels(bitmap.info(), mapped.get(), bitmap.rowBytes(),
&ReleaseSharedMemoryPixels, mapped.get())) {
return;
}
// On success, SkBitmap now owns the SharedMemory.
mapped.release();
clipboard_writer_->WriteImage(bitmap);
}
void ClipboardHostImpl::CommitWrite(blink::mojom::ClipboardBuffer) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
clipboard_writer_.reset(
new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE));
}
#if !defined(OS_MACOSX)
void ClipboardHostImpl::WriteStringToFindPboard(const base::string16& text) {
mojo::ReportBadMessage("Unexpected call to WriteStringToFindPboard.");
}
#endif
void ClipboardHostImpl::ConvertBufferType(blink::mojom::ClipboardBuffer buffer,
ui::ClipboardType* result) {
*result = ui::CLIPBOARD_TYPE_COPY_PASTE;
switch (buffer) {
case blink::mojom::ClipboardBuffer::kStandard:
break;
case blink::mojom::ClipboardBuffer::kSelection:
#if defined(USE_X11)
*result = ui::CLIPBOARD_TYPE_SELECTION;
break;
#else
// Chrome OS and non-X11 unix builds do not support
// the X selection clipboad.
// TODO: remove the need for this case, see http://crbug.com/361753
mojo::ReportBadMessage("Cannot use kSelection on non X11 platforms.");
#endif
}
}
} // namespace content