blob: f370565643c7c38faf387629c6678e637408f458 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/modules/clipboard/clipboard_reader.h"
#include "third_party/blink/public/mojom/clipboard/clipboard.mojom-blink.h"
#include "third_party/blink/renderer/core/clipboard/clipboard_mime_types.h"
#include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_promise.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_writer.h"
#include "third_party/blink/renderer/platform/image-encoders/image_encoder.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/scheduler/public/worker_pool.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink {
namespace { // anonymous namespace for ClipboardReader's derived classes.
// Reads an image from the System Clipboard as a Blob with image/png content.
class ClipboardImageReader final : public ClipboardReader {
public:
explicit ClipboardImageReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardImageReader() override = default;
ClipboardImageReader(const ClipboardImageReader&) = delete;
ClipboardImageReader& operator=(const ClipboardImageReader&) = delete;
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SkBitmap bitmap =
system_clipboard()->ReadImage(mojom::ClipboardBuffer::kStandard);
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
if (!image) {
NextRead(Vector<uint8_t>());
return;
}
worker_pool::PostTask(
FROM_HERE,
CrossThreadBindOnce(&ClipboardImageReader::EncodeOnBackgroundThread,
std::move(image), WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
private:
static void EncodeOnBackgroundThread(
sk_sp<SkImage> image,
ClipboardImageReader* reader,
scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) {
DCHECK(!IsMainThread());
SkPixmap pixmap;
image->peekPixels(&pixmap);
// Set encoding options to favor speed over size.
SkPngEncoder::Options options;
options.fZLibLevel = 1;
options.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
Vector<uint8_t> png_data;
if (!ImageEncoder::Encode(&png_data, pixmap, options))
png_data.clear();
// Now return to the kUserInteraction thread.
PostCrossThreadTask(*clipboard_task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardImageReader::NextRead,
WrapCrossThreadPersistent(reader),
std::move(png_data)));
}
void NextRead(Vector<uint8_t> utf8_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Blob* blob = nullptr;
if (utf8_bytes.size()) {
blob =
Blob::Create(utf8_bytes.data(), utf8_bytes.size(), kMimeTypeImagePng);
}
promise_->OnRead(blob);
}
};
// Reads an image from the System Clipboard as a Blob with text/plain content.
class ClipboardTextReader final : public ClipboardReader {
public:
explicit ClipboardTextReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardTextReader() override = default;
ClipboardTextReader(const ClipboardTextReader&) = delete;
ClipboardTextReader& operator=(const ClipboardTextReader&) = delete;
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
String plain_text =
system_clipboard()->ReadPlainText(mojom::ClipboardBuffer::kStandard);
if (plain_text.IsEmpty()) {
NextRead(Vector<uint8_t>());
return;
}
worker_pool::PostTask(
FROM_HERE, CrossThreadBindOnce(
&ClipboardTextReader::EncodeOnBackgroundThread,
std::move(plain_text), WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
private:
static void EncodeOnBackgroundThread(
String plain_text,
ClipboardTextReader* reader,
scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) {
DCHECK(!IsMainThread());
// Encode WTF String to UTF-8, the standard text format for Blobs.
StringUTF8Adaptor utf8_text(plain_text);
Vector<uint8_t> utf8_bytes;
utf8_bytes.ReserveInitialCapacity(utf8_text.size());
utf8_bytes.Append(utf8_text.data(), utf8_text.size());
PostCrossThreadTask(*clipboard_task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardTextReader::NextRead,
WrapCrossThreadPersistent(reader),
std::move(utf8_bytes)));
}
void NextRead(Vector<uint8_t> utf8_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Blob* blob = nullptr;
if (utf8_bytes.size()) {
blob = Blob::Create(utf8_bytes.data(), utf8_bytes.size(),
kMimeTypeTextPlain);
}
promise_->OnRead(blob);
}
};
// Reads HTML from the System Clipboard as a Blob with text/html content.
class ClipboardHtmlReader final : public ClipboardReader {
public:
explicit ClipboardHtmlReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardHtmlReader() override = default;
// This must be called on the main thread because HTML DOM nodes can
// only be used on the main thread.
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
promise_->GetExecutionContext()->CountUse(
WebFeature::kHtmlClipboardApiRead);
KURL url;
unsigned fragment_start = 0;
unsigned fragment_end = 0;
String html_string =
system_clipboard()->ReadHTML(url, fragment_start, fragment_end);
// Now sanitize the HTML string.
LocalFrame* frame = promise_->GetLocalFrame();
DocumentFragment* fragment = CreateSanitizedFragmentFromMarkupWithContext(
*frame->GetDocument(), html_string, fragment_start,
html_string.length(), url);
String sanitized_html =
CreateMarkup(fragment, kIncludeNode, kResolveAllURLs);
if (sanitized_html.IsEmpty()) {
NextRead(Vector<uint8_t>());
return;
}
worker_pool::PostTask(
FROM_HERE,
CrossThreadBindOnce(&ClipboardHtmlReader::EncodeOnBackgroundThread,
std::move(sanitized_html),
WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
private:
static void EncodeOnBackgroundThread(
String plain_text,
ClipboardHtmlReader* reader,
scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) {
DCHECK(!IsMainThread());
// Encode WTF String to UTF-8, the standard text format for blobs.
StringUTF8Adaptor utf8_text(plain_text);
Vector<uint8_t> utf8_bytes;
utf8_bytes.ReserveInitialCapacity(utf8_text.size());
utf8_bytes.Append(utf8_text.data(), utf8_text.size());
PostCrossThreadTask(*clipboard_task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardHtmlReader::NextRead,
WrapCrossThreadPersistent(reader),
std::move(utf8_bytes)));
}
void NextRead(Vector<uint8_t> utf8_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Blob* blob = nullptr;
if (utf8_bytes.size()) {
blob =
Blob::Create(utf8_bytes.data(), utf8_bytes.size(), kMimeTypeTextHTML);
}
promise_->OnRead(blob);
}
};
// Reads SVG from the System Clipboard as a Blob with image/svg+xml content.
class ClipboardSvgReader final : public ClipboardReader {
public:
ClipboardSvgReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardSvgReader() override = default;
// This must be called on the main thread because XML DOM nodes can
// only be used on the main thread.
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
system_clipboard()->ReadSvg(
WTF::Bind(&ClipboardSvgReader::OnRead, WrapPersistent(this)));
}
private:
void OnRead(const String& svg_string) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LocalFrame* frame = promise_->GetLocalFrame();
if (!frame) {
NextRead(Vector<uint8_t>());
return;
}
// Now sanitize the SVG string.
KURL url;
unsigned fragment_start = 0;
DocumentFragment* fragment = CreateSanitizedFragmentFromMarkupWithContext(
*frame->GetDocument(), svg_string, fragment_start, svg_string.length(),
url);
String sanitized_svg =
CreateMarkup(fragment, kIncludeNode, kResolveAllURLs);
if (sanitized_svg.IsEmpty()) {
NextRead(Vector<uint8_t>());
return;
}
worker_pool::PostTask(
FROM_HERE,
CrossThreadBindOnce(&ClipboardSvgReader::EncodeOnBackgroundThread,
std::move(sanitized_svg),
WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
static void EncodeOnBackgroundThread(
String plain_text,
ClipboardSvgReader* reader,
scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) {
DCHECK(!IsMainThread());
// Encode WTF String to UTF-8, the standard text format for Blobs.
StringUTF8Adaptor utf8_text(plain_text);
Vector<uint8_t> utf8_bytes;
utf8_bytes.ReserveInitialCapacity(utf8_text.size());
utf8_bytes.Append(utf8_text.data(), utf8_text.size());
PostCrossThreadTask(*clipboard_task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardSvgReader::NextRead,
WrapCrossThreadPersistent(reader),
std::move(utf8_bytes)));
}
void NextRead(Vector<uint8_t> utf8_bytes) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Blob* blob = nullptr;
if (utf8_bytes.size()) {
blob =
Blob::Create(utf8_bytes.data(), utf8_bytes.size(), kMimeTypeImageSvg);
}
promise_->OnRead(blob);
}
};
} // anonymous namespace
// ClipboardReader functions.
// static
ClipboardReader* ClipboardReader::Create(SystemClipboard* system_clipboard,
const String& mime_type,
ClipboardPromise* promise) {
DCHECK(ClipboardWriter::IsValidType(mime_type, /*is_raw=*/false));
if (mime_type == kMimeTypeImagePng)
return MakeGarbageCollected<ClipboardImageReader>(system_clipboard,
promise);
if (mime_type == kMimeTypeTextPlain)
return MakeGarbageCollected<ClipboardTextReader>(system_clipboard, promise);
if (mime_type == kMimeTypeTextHTML)
return MakeGarbageCollected<ClipboardHtmlReader>(system_clipboard, promise);
if (mime_type == kMimeTypeImageSvg &&
RuntimeEnabledFeatures::ClipboardSvgEnabled())
return MakeGarbageCollected<ClipboardSvgReader>(system_clipboard, promise);
NOTREACHED()
<< "IsValidType() and Create() have inconsistent implementations.";
return nullptr;
}
ClipboardReader::ClipboardReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: clipboard_task_runner_(promise->GetExecutionContext()->GetTaskRunner(
TaskType::kUserInteraction)),
promise_(promise),
system_clipboard_(system_clipboard) {}
ClipboardReader::~ClipboardReader() = default;
void ClipboardReader::Trace(Visitor* visitor) const {
visitor->Trace(system_clipboard_);
visitor->Trace(promise_);
}
} // namespace blink