blob: c8a6859e84d6b5ea292a898e73eee96cc5b66257 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// 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_writer.h"
#include "base/task/single_thread_task_runner.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/clipboard/clipboard.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_supported_type.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/fileapi/file_reader_loader.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
#include "third_party/blink/renderer/core/keywords.h"
#include "third_party/blink/renderer/core/typed_arrays/array_buffer/array_buffer_contents.h"
#include "third_party/blink/renderer/core/xml/dom_parser.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard.h"
#include "third_party/blink/renderer/platform/heap/cross_thread_handle.h"
#include "third_party/blink/renderer/platform/image-decoders/image_decoder.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_copier_base.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier_skia.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
#include "ui/base/clipboard/clipboard_constants.h"
namespace blink {
namespace { // anonymous namespace for ClipboardWriter's derived classes.
// Writes image/png content to the System Clipboard.
class ClipboardImageWriter final : public ClipboardWriter {
public:
ClipboardImageWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardWriter(system_clipboard, promise) {}
~ClipboardImageWriter() override = default;
private:
void StartWrite(
DOMArrayBuffer* raw_data,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// ArrayBufferContents is a thread-safe smart pointer around the backing
// store.
ArrayBufferContents contents = *raw_data->Content();
worker_pool::PostTask(
FROM_HERE,
CrossThreadBindOnce(&ClipboardImageWriter::DecodeOnBackgroundThread,
std::move(contents), MakeCrossThreadHandle(this),
task_runner));
}
static void DecodeOnBackgroundThread(
ArrayBufferContents png_data,
CrossThreadHandle<ClipboardImageWriter> writer,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(!IsMainThread());
std::unique_ptr<ImageDecoder> decoder = ImageDecoder::Create(
SegmentReader::CreateFromSkData(
SkData::MakeWithoutCopy(png_data.Data(), png_data.DataLength())),
/*data_complete=*/true, ImageDecoder::kAlphaPremultiplied,
ImageDecoder::kDefaultBitDepth, ColorBehavior::kTag,
cc::AuxImage::kDefault, Platform::GetMaxDecodedImageBytes());
sk_sp<SkImage> image = nullptr;
// `decoder` is nullptr if `png_data` doesn't begin with the PNG signature.
if (decoder) {
image = ImageBitmap::GetSkImageFromDecoder(std::move(decoder));
}
PostCrossThreadTask(
*task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardImageWriter::Write,
MakeUnwrappingCrossThreadHandle(std::move(writer)),
std::move(image)));
}
void Write(sk_sp<SkImage> image) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!image) {
promise_->RejectFromReadOrDecodeFailure();
return;
}
if (!promise_->GetLocalFrame()) {
return;
}
SkBitmap bitmap;
image->asLegacyBitmap(&bitmap);
system_clipboard()->WriteImage(std::move(bitmap));
promise_->CompleteWriteRepresentation();
}
};
// Writes text/plain content to the System Clipboard.
class ClipboardTextWriter final : public ClipboardWriter {
public:
ClipboardTextWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardWriter(system_clipboard, promise) {}
~ClipboardTextWriter() override = default;
private:
void StartWrite(
DOMArrayBuffer* raw_data,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// ArrayBufferContents is a thread-safe smart pointer around the backing
// store.
ArrayBufferContents contents = *raw_data->Content();
worker_pool::PostTask(
FROM_HERE,
CrossThreadBindOnce(&ClipboardTextWriter::DecodeOnBackgroundThread,
std::move(contents), MakeCrossThreadHandle(this),
task_runner));
}
static void DecodeOnBackgroundThread(
ArrayBufferContents raw_data,
CrossThreadHandle<ClipboardTextWriter> writer,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(!IsMainThread());
String wtf_string = String::FromUTF8(raw_data.ByteSpan());
PostCrossThreadTask(
*task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardTextWriter::Write,
MakeUnwrappingCrossThreadHandle(std::move(writer)),
std::move(wtf_string)));
}
void Write(const String& text) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!promise_->GetLocalFrame()) {
return;
}
system_clipboard()->WritePlainText(text);
promise_->CompleteWriteRepresentation();
}
};
// Writes text/html content to the System Clipboard.
class ClipboardHtmlWriter final : public ClipboardWriter {
public:
ClipboardHtmlWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardWriter(system_clipboard, promise) {}
~ClipboardHtmlWriter() override = default;
private:
void StartWrite(
DOMArrayBuffer* html_data,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LocalFrame* local_frame = promise_->GetLocalFrame();
auto* execution_context = promise_->GetExecutionContext();
if (!local_frame || !execution_context) {
return;
}
const KURL& url = local_frame->GetDocument()->Url();
DOMParser* dom_parser = DOMParser::Create(promise_->GetScriptState());
String html_string = String::FromUTF8(html_data->ByteSpan());
const Document* doc = dom_parser->ParseFromStringWithoutTrustedTypes(
html_string, V8SupportedType(V8SupportedType::Enum::kTextHtml));
DCHECK(doc);
String serialized_html = CreateMarkup(doc, kIncludeNode, kResolveAllURLs);
Write(serialized_html, url);
}
void Write(const String& serialized_html, const KURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
system_clipboard()->WriteHTML(serialized_html, url);
promise_->CompleteWriteRepresentation();
}
};
// Write image/svg+xml content to the System Clipboard.
class ClipboardSvgWriter final : public ClipboardWriter {
public:
ClipboardSvgWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardWriter(system_clipboard, promise) {}
~ClipboardSvgWriter() override = default;
private:
void StartWrite(
DOMArrayBuffer* svg_data,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LocalFrame* local_frame = promise_->GetLocalFrame();
if (!local_frame) {
return;
}
DOMParser* dom_parser = DOMParser::Create(promise_->GetScriptState());
String svg_string = String::FromUTF8(svg_data->ByteSpan());
const Document* doc = dom_parser->ParseFromStringWithoutTrustedTypes(
svg_string, V8SupportedType(V8SupportedType::Enum::kImageSvgXml));
promise_->GetExecutionContext()->CountUse(WebFeature::kClipboardSvgWrite);
Write(CreateMarkup(doc, kIncludeNode, kResolveAllURLs));
}
void Write(const String& svg_html) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
system_clipboard()->WriteSvg(svg_html);
promise_->CompleteWriteRepresentation();
}
};
// Writes arbitrary, unsanitized content to the System Clipboard.
class ClipboardCustomFormatWriter final : public ClipboardWriter {
public:
ClipboardCustomFormatWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise,
const String& mime_type)
: ClipboardWriter(system_clipboard, promise), mime_type_(mime_type) {}
~ClipboardCustomFormatWriter() override = default;
private:
void StartWrite(
DOMArrayBuffer* custom_format_data,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
promise_->GetExecutionContext()->CountUse(
WebFeature::kClipboardCustomFormatWrite);
Write(custom_format_data);
}
void Write(DOMArrayBuffer* custom_format_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!promise_->GetLocalFrame()) {
return;
}
if (custom_format_data->ByteLength() >=
mojom::blink::ClipboardHost::kMaxDataSize) {
promise_->RejectFromReadOrDecodeFailure();
return;
}
mojo_base::BigBuffer buffer(custom_format_data->ByteSpan());
system_clipboard()->WriteUnsanitizedCustomFormat(mime_type_,
std::move(buffer));
promise_->CompleteWriteRepresentation();
}
String mime_type_;
};
} // anonymous namespace
// ClipboardWriter functions.
// static
ClipboardWriter* ClipboardWriter::Create(SystemClipboard* system_clipboard,
const String& mime_type,
ClipboardPromise* promise) {
CHECK(ClipboardItem::supports(mime_type));
String web_custom_format = Clipboard::ParseWebCustomFormat(mime_type);
if (!web_custom_format.empty()) {
// We write the custom MIME type without the "web " prefix into the web
// custom format map so native applications don't have to add any string
// parsing logic to read format from clipboard.
return MakeGarbageCollected<ClipboardCustomFormatWriter>(
system_clipboard, promise, web_custom_format);
}
if (mime_type == ui::kMimeTypePng) {
return MakeGarbageCollected<ClipboardImageWriter>(system_clipboard,
promise);
}
if (mime_type == ui::kMimeTypePlainText) {
return MakeGarbageCollected<ClipboardTextWriter>(system_clipboard, promise);
}
if (mime_type == ui::kMimeTypeHtml) {
return MakeGarbageCollected<ClipboardHtmlWriter>(system_clipboard, promise);
}
if (mime_type == ui::kMimeTypeSvg) {
return MakeGarbageCollected<ClipboardSvgWriter>(system_clipboard, promise);
}
NOTREACHED()
<< "IsValidType() and Create() have inconsistent implementations.";
}
ClipboardWriter::ClipboardWriter(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: promise_(promise),
clipboard_task_runner_(promise->GetExecutionContext()->GetTaskRunner(
TaskType::kUserInteraction)),
file_reading_task_runner_(promise->GetExecutionContext()->GetTaskRunner(
TaskType::kFileReading)),
system_clipboard_(system_clipboard) {}
ClipboardWriter::~ClipboardWriter() = default;
void ClipboardWriter::WriteToSystem(V8UnionBlobOrString* clipboard_item_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (clipboard_item_data->IsBlob()) {
DCHECK(!file_reader_);
file_reader_ = MakeGarbageCollected<FileReaderLoader>(
this, std::move(file_reading_task_runner_));
file_reader_->Start(clipboard_item_data->GetAsBlob()->GetBlobDataHandle());
} else if (clipboard_item_data->IsString()) {
DCHECK(RuntimeEnabledFeatures::ClipboardItemWithDOMStringSupportEnabled());
std::string utf8_string = clipboard_item_data->GetAsString().Utf8();
StartWrite(DOMArrayBuffer::Create(base::as_byte_span(utf8_string)),
clipboard_task_runner_);
} else {
NOTREACHED();
}
}
// FileReaderClient implementation.
void ClipboardWriter::DidFinishLoading(FileReaderData contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DOMArrayBuffer* array_buffer = std::move(contents).AsDOMArrayBuffer();
DCHECK(array_buffer);
self_keep_alive_.Clear();
file_reader_ = nullptr;
StartWrite(array_buffer, clipboard_task_runner_);
}
void ClipboardWriter::DidFail(FileErrorCode error_code) {
FileReaderAccumulator::DidFail(error_code);
self_keep_alive_.Clear();
file_reader_ = nullptr;
promise_->RejectFromReadOrDecodeFailure();
}
void ClipboardWriter::Trace(Visitor* visitor) const {
FileReaderAccumulator::Trace(visitor);
visitor->Trace(promise_);
visitor->Trace(system_clipboard_);
visitor->Trace(file_reader_);
}
} // namespace blink