| // 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 |