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