blob: ec20fc6602ff6e301ccdacbc9604c7b69d789184 [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 <memory>
#include <utility>
#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/pickle.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/child_process_host.h"
#include "ipc/ipc_message.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/clipboard_constants.h"
#include "ui/base/clipboard/clipboard_data_endpoint.h"
#include "ui/base/clipboard/clipboard_format_type.h"
#include "ui/base/clipboard/custom_data_helper.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "url/gurl.h"
namespace content {
// 5 mins is based on the timeout in BinaryUploadService. This scanning timeout
// of 5 mins means no paste will be held back longer before being allowed or
// blocked, so matching this timeout with the threshold for a paste being too
// old ensures scans that:
// - Scans that timeout can be retried without waiting
// - Scans that succeed will apply their verdicts without the risk that their
// associated IsPasteAllowedRequest is already too old.
const base::TimeDelta ClipboardHostImpl::kIsPasteAllowedRequestTooOld =
base::TimeDelta::FromMinutes(5);
ClipboardHostImpl::IsPasteAllowedRequest::IsPasteAllowedRequest() = default;
ClipboardHostImpl::IsPasteAllowedRequest::~IsPasteAllowedRequest() = default;
bool ClipboardHostImpl::IsPasteAllowedRequest::AddCallback(
IsClipboardPasteAllowedCallback callback) {
// If this request has already completed, invoke the callback immediately
// and return.
if (allowed_.has_value()) {
std::move(callback).Run(allowed_.value());
return false;
}
callbacks_.push_back(std::move(callback));
// If this is the first callback registered tell the caller to start the scan.
return callbacks_.size() == 1;
}
void ClipboardHostImpl::IsPasteAllowedRequest::Complete(
ClipboardPasteAllowed allowed) {
allowed_ = allowed;
InvokeCallbacks();
}
bool ClipboardHostImpl::IsPasteAllowedRequest::IsObsolete(base::Time now) {
// If the request is old and no longer has any registered callbacks it is
// obsolete.
return (now - time_) > kIsPasteAllowedRequestTooOld && callbacks_.empty();
}
void ClipboardHostImpl::IsPasteAllowedRequest::InvokeCallbacks() {
DCHECK(allowed_);
auto callbacks = std::move(callbacks_);
for (auto& callback : callbacks) {
if (!callback.is_null())
std::move(callback).Run(allowed_.value());
}
}
ClipboardHostImpl::ClipboardHostImpl(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::ClipboardHost> receiver)
: receiver_(this, std::move(receiver)),
clipboard_(ui::Clipboard::GetForCurrentThread()) {
// |render_frame_host| may be null in unit tests.
if (render_frame_host) {
render_frame_routing_id_ =
GlobalFrameRoutingId(render_frame_host->GetProcess()->GetID(),
render_frame_host->GetRoutingID());
clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>(
ui::ClipboardBuffer::kCopyPaste,
std::make_unique<ui::ClipboardDataEndpoint>(
render_frame_host->GetLastCommittedOrigin()));
} else {
render_frame_routing_id_ = GlobalFrameRoutingId(
ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE);
clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>(
ui::ClipboardBuffer::kCopyPaste);
}
}
void ClipboardHostImpl::Create(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::ClipboardHost> receiver) {
// Clipboard implementations do interesting things, like run nested message
// loops. Use manual memory management instead of SelfOwnedReceiver<T> which
// synchronously destroys on failure and can result in some unfortunate
// use-after-frees after the nested message loops exit.
auto* host = new ClipboardHostImpl(
static_cast<RenderFrameHostImpl*>(render_frame_host),
std::move(receiver));
host->receiver_.set_disconnect_handler(base::BindOnce(
[](ClipboardHostImpl* host) {
base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, host);
},
host));
}
ClipboardHostImpl::~ClipboardHostImpl() {
clipboard_writer_->Reset();
}
void ClipboardHostImpl::GetSequenceNumber(ui::ClipboardBuffer clipboard_buffer,
GetSequenceNumberCallback callback) {
std::move(callback).Run(clipboard_->GetSequenceNumber(clipboard_buffer));
}
void ClipboardHostImpl::ReadAvailableTypes(
ui::ClipboardBuffer clipboard_buffer,
ReadAvailableTypesCallback callback) {
std::vector<base::string16> types;
clipboard_->ReadAvailableTypes(clipboard_buffer, CreateDataEndpoint().get(),
&types);
std::move(callback).Run(types);
}
void ClipboardHostImpl::IsFormatAvailable(blink::mojom::ClipboardFormat format,
ui::ClipboardBuffer clipboard_buffer,
IsFormatAvailableCallback callback) {
bool result = false;
auto data_endpoint = CreateDataEndpoint();
switch (format) {
case blink::mojom::ClipboardFormat::kPlaintext:
result = clipboard_->IsFormatAvailable(
ui::ClipboardFormatType::GetPlainTextType(), clipboard_buffer,
data_endpoint.get());
#if defined(OS_WIN)
result |= clipboard_->IsFormatAvailable(
ui::ClipboardFormatType::GetPlainTextAType(), clipboard_buffer,
data_endpoint.get());
#endif
break;
case blink::mojom::ClipboardFormat::kHtml:
result =
clipboard_->IsFormatAvailable(ui::ClipboardFormatType::GetHtmlType(),
clipboard_buffer, data_endpoint.get());
break;
case blink::mojom::ClipboardFormat::kSmartPaste:
result = clipboard_->IsFormatAvailable(
ui::ClipboardFormatType::GetWebKitSmartPasteType(), clipboard_buffer,
data_endpoint.get());
break;
case blink::mojom::ClipboardFormat::kBookmark:
#if defined(OS_WIN) || defined(OS_MAC)
result =
clipboard_->IsFormatAvailable(ui::ClipboardFormatType::GetUrlType(),
clipboard_buffer, data_endpoint.get());
#else
result = false;
#endif
break;
}
std::move(callback).Run(result);
}
void ClipboardHostImpl::ReadText(ui::ClipboardBuffer clipboard_buffer,
ReadTextCallback callback) {
base::string16 result;
auto data_dst = CreateDataEndpoint();
if (clipboard_->IsFormatAvailable(ui::ClipboardFormatType::GetPlainTextType(),
clipboard_buffer, data_dst.get())) {
clipboard_->ReadText(clipboard_buffer, data_dst.get(), &result);
} else {
#if defined(OS_WIN)
if (clipboard_->IsFormatAvailable(
ui::ClipboardFormatType::GetPlainTextAType(), clipboard_buffer,
data_dst.get())) {
std::string ascii;
clipboard_->ReadAsciiText(clipboard_buffer, data_dst.get(), &ascii);
result = base::ASCIIToUTF16(ascii);
}
#endif
}
std::string data = base::UTF16ToUTF8(result);
PerformPasteIfAllowed(clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetPlainTextType(),
std::move(data),
base::BindOnce(
[](base::string16 result, ReadTextCallback callback,
ClipboardPasteAllowed allowed) {
if (!allowed)
result.clear();
std::move(callback).Run(result);
},
std::move(result), std::move(callback)));
}
void ClipboardHostImpl::ReadHtml(ui::ClipboardBuffer clipboard_buffer,
ReadHtmlCallback callback) {
base::string16 markup;
std::string src_url_str;
uint32_t fragment_start = 0;
uint32_t fragment_end = 0;
auto data_dst = CreateDataEndpoint();
clipboard_->ReadHTML(clipboard_buffer, data_dst.get(), &markup, &src_url_str,
&fragment_start, &fragment_end);
std::string data = base::UTF16ToUTF8(markup);
PerformPasteIfAllowed(
clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetHtmlType(), std::move(data),
base::BindOnce(
[](base::string16 markup, std::string src_url_str,
uint32_t fragment_start, uint32_t fragment_end,
ReadHtmlCallback callback, ClipboardPasteAllowed allowed) {
if (!allowed)
markup.clear();
std::move(callback).Run(std::move(markup), GURL(src_url_str),
fragment_start, fragment_end);
},
std::move(markup), std::move(src_url_str), fragment_start,
fragment_end, std::move(callback)));
}
void ClipboardHostImpl::ReadSvg(ui::ClipboardBuffer clipboard_buffer,
ReadSvgCallback callback) {
base::string16 markup;
clipboard_->ReadSvg(clipboard_buffer, /*data_dst=*/nullptr, &markup);
std::string data = base::UTF16ToUTF8(markup);
PerformPasteIfAllowed(clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetSvgType(), std::move(data),
base::BindOnce(
[](base::string16 markup, ReadSvgCallback callback,
ClipboardPasteAllowed allowed) {
if (!allowed)
markup.clear();
std::move(callback).Run(std::move(markup));
},
std::move(markup), std::move(callback)));
}
void ClipboardHostImpl::ReadRtf(ui::ClipboardBuffer clipboard_buffer,
ReadRtfCallback callback) {
std::string result;
auto data_dst = CreateDataEndpoint();
clipboard_->ReadRTF(clipboard_buffer, data_dst.get(), &result);
std::string data = result;
PerformPasteIfAllowed(clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetRtfType(), std::move(data),
base::BindOnce(
[](std::string result, ReadRtfCallback callback,
ClipboardPasteAllowed allowed) {
if (!allowed)
result.clear();
std::move(callback).Run(result);
},
std::move(result), std::move(callback)));
}
void ClipboardHostImpl::ReadImage(ui::ClipboardBuffer clipboard_buffer,
ReadImageCallback callback) {
auto data_dst = CreateDataEndpoint();
clipboard_->ReadImage(clipboard_buffer, data_dst.get(),
base::BindOnce(&ClipboardHostImpl::OnReadImage,
weak_ptr_factory_.GetWeakPtr(),
clipboard_buffer, std::move(callback)));
}
void ClipboardHostImpl::OnReadImage(ui::ClipboardBuffer clipboard_buffer,
ReadImageCallback callback,
const SkBitmap& bitmap) {
std::string data =
std::string(reinterpret_cast<const char*>(bitmap.getPixels()),
bitmap.computeByteSize());
PerformPasteIfAllowed(clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetBitmapType(),
std::move(data),
base::BindOnce(
[](SkBitmap bitmap, ReadImageCallback callback,
ClipboardPasteAllowed allowed) {
if (!allowed)
bitmap.reset();
std::move(callback).Run(bitmap);
},
std::move(bitmap), std::move(callback)));
}
void ClipboardHostImpl::ReadCustomData(ui::ClipboardBuffer clipboard_buffer,
const base::string16& type,
ReadCustomDataCallback callback) {
base::string16 result;
auto data_dst = CreateDataEndpoint();
clipboard_->ReadCustomData(clipboard_buffer, type, data_dst.get(), &result);
std::string data = base::UTF16ToUTF8(result);
PerformPasteIfAllowed(
clipboard_->GetSequenceNumber(clipboard_buffer),
ui::ClipboardFormatType::GetWebCustomDataType(), std::move(data),
base::BindOnce(
[](base::string16 result, ReadCustomDataCallback callback,
ClipboardPasteAllowed allowed) {
if (!allowed)
result.clear();
std::move(callback).Run(result);
},
std::move(result), std::move(callback)));
}
void ClipboardHostImpl::WriteText(const base::string16& text) {
clipboard_writer_->WriteText(text);
}
void ClipboardHostImpl::WriteHtml(const base::string16& markup,
const GURL& url) {
clipboard_writer_->WriteHTML(markup, url.spec());
}
void ClipboardHostImpl::WriteSvg(const base::string16& markup) {
clipboard_writer_->WriteSvg(markup);
}
void ClipboardHostImpl::WriteSmartPasteMarker() {
clipboard_writer_->WriteWebSmartPaste();
}
void ClipboardHostImpl::WriteCustomData(
const base::flat_map<base::string16, base::string16>& data) {
base::Pickle pickle;
ui::WriteCustomDataToPickle(data, &pickle);
clipboard_writer_->WritePickledData(
pickle, ui::ClipboardFormatType::GetWebCustomDataType());
}
void ClipboardHostImpl::WriteBookmark(const std::string& url,
const base::string16& title) {
clipboard_writer_->WriteBookmark(title, url);
}
void ClipboardHostImpl::WriteImage(const SkBitmap& bitmap) {
clipboard_writer_->WriteImage(bitmap);
}
void ClipboardHostImpl::CommitWrite() {
clipboard_writer_ = std::make_unique<ui::ScopedClipboardWriter>(
ui::ClipboardBuffer::kCopyPaste, CreateDataEndpoint());
}
void ClipboardHostImpl::PerformPasteIfAllowed(
uint64_t seqno,
const ui::ClipboardFormatType& data_type,
std::string data,
IsClipboardPasteAllowedCallback callback) {
CleanupObsoleteRequests();
if (data.empty()) {
std::move(callback).Run(ClipboardPasteAllowed(true));
return;
}
// Add |callback| to the callbacks associated to the sequence number, adding
// an entry to the map if one does not exist.
auto& request = is_allowed_requests_[seqno];
if (request.AddCallback(std::move(callback)))
StartIsPasteAllowedRequest(seqno, data_type, std::move(data));
}
void ClipboardHostImpl::StartIsPasteAllowedRequest(
uint64_t seqno,
const ui::ClipboardFormatType& data_type,
std::string data) {
// May not have a RenderFrameHost in tests.
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_frame_routing_id_);
if (render_frame_host) {
render_frame_host->IsClipboardPasteAllowed(
data_type, data,
base::BindOnce(&ClipboardHostImpl::FinishPasteIfAllowed,
weak_ptr_factory_.GetWeakPtr(), seqno));
} else {
FinishPasteIfAllowed(seqno, ClipboardPasteAllowed(true));
}
}
void ClipboardHostImpl::FinishPasteIfAllowed(uint64_t seqno,
ClipboardPasteAllowed allowed) {
if (is_allowed_requests_.count(seqno) == 0)
return;
auto& request = is_allowed_requests_[seqno];
request.Complete(allowed);
}
void ClipboardHostImpl::CleanupObsoleteRequests() {
for (auto it = is_allowed_requests_.begin();
it != is_allowed_requests_.end();) {
it = it->second.IsObsolete(base::Time::Now())
? is_allowed_requests_.erase(it)
: std::next(it);
}
}
std::unique_ptr<ui::ClipboardDataEndpoint>
ClipboardHostImpl::CreateDataEndpoint() {
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(render_frame_routing_id_);
if (render_frame_host) {
return std::make_unique<ui::ClipboardDataEndpoint>(
render_frame_host->GetLastCommittedOrigin());
}
return nullptr;
}
} // namespace content