Commit e7e2296b authored by Dylan Sleeper's avatar Dylan Sleeper Committed by Commit Bot

Clipboard API: Move encoding to the background thread.

In the current clipboard reader implementation, all of the encoding
was done on the main thread. This CL moves all of the encoding from
the image and text reader onto the background thread. The main benefit
is avoiding main thread jank when the user pastes a large payload from
the clipboard.

Bug: 1091833
Change-Id: I640f5818cfd81312f3adb59f4643c8e4dd34dc8d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2233982Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Reviewed-by: default avatarDarwin Huang <huangdarwin@chromium.org>
Commit-Queue: Victor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#785738}
parent f806b53a
...@@ -23,9 +23,12 @@ ...@@ -23,9 +23,12 @@
#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h" #include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_writer.h"
#include "third_party/blink/renderer/modules/permissions/permission_utils.h" #include "third_party/blink/renderer/modules/permissions/permission_utils.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h" #include "third_party/blink/renderer/platform/scheduler/public/thread.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/functional.h" #include "third_party/blink/renderer/platform/wtf/functional.h"
// There are 2 clipboard permissions defined in the spec: // There are 2 clipboard permissions defined in the spec:
...@@ -234,28 +237,19 @@ void ClipboardPromise::HandleReadWithPermission(PermissionStatus status) { ...@@ -234,28 +237,19 @@ void ClipboardPromise::HandleReadWithPermission(PermissionStatus status) {
if (is_raw_) { if (is_raw_) {
RawSystemClipboard* raw_system_clipboard = RawSystemClipboard* raw_system_clipboard =
GetLocalFrame()->GetRawSystemClipboard(); GetLocalFrame()->GetRawSystemClipboard();
raw_system_clipboard->ReadAvailableFormatNames( raw_system_clipboard->ReadAvailableFormatNames(WTF::Bind(
WTF::Bind(&ClipboardPromise::OnReadAvailableRawFormatNames, &ClipboardPromise::OnReadAvailableFormatNames, WrapPersistent(this)));
WrapPersistent(this)));
return; return;
} }
SystemClipboard* system_clipboard = GetLocalFrame()->GetSystemClipboard(); SystemClipboard* system_clipboard = GetLocalFrame()->GetSystemClipboard();
Vector<String> available_types = system_clipboard->ReadAvailableTypes(); Vector<String> available_types = system_clipboard->ReadAvailableTypes();
clipboard_item_data_.ReserveInitialCapacity(available_types.size()); OnReadAvailableFormatNames(available_types);
for (String& type_to_read : available_types) {
ClipboardReader* reader =
ClipboardReader::Create(system_clipboard, type_to_read);
if (reader) {
clipboard_item_data_.emplace_back(std::move(type_to_read),
reader->ReadFromSystem());
}
}
ResolveRead();
} }
void ClipboardPromise::ResolveRead() { void ClipboardPromise::ResolveRead() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetExecutionContext()); DCHECK(GetExecutionContext());
if (!clipboard_item_data_.size()) { if (!clipboard_item_data_.size()) {
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>( script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kDataError, "No valid data on clipboard.")); DOMExceptionCode::kDataError, "No valid data on clipboard."));
...@@ -270,7 +264,7 @@ void ClipboardPromise::ResolveRead() { ...@@ -270,7 +264,7 @@ void ClipboardPromise::ResolveRead() {
script_promise_resolver_->Resolve(clipboard_items); script_promise_resolver_->Resolve(clipboard_items);
} }
void ClipboardPromise::OnReadAvailableRawFormatNames( void ClipboardPromise::OnReadAvailableFormatNames(
const Vector<String>& format_names) { const Vector<String>& format_names) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!GetExecutionContext()) if (!GetExecutionContext())
...@@ -281,10 +275,10 @@ void ClipboardPromise::OnReadAvailableRawFormatNames( ...@@ -281,10 +275,10 @@ void ClipboardPromise::OnReadAvailableRawFormatNames(
clipboard_item_data_.emplace_back(format_name, clipboard_item_data_.emplace_back(format_name,
/* Placeholder value. */ nullptr); /* Placeholder value. */ nullptr);
} }
ReadNextRawRepresentation(); ReadNextRepresentation();
} }
void ClipboardPromise::ReadNextRawRepresentation() { void ClipboardPromise::ReadNextRepresentation() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!GetExecutionContext()) if (!GetExecutionContext())
return; return;
...@@ -295,9 +289,20 @@ void ClipboardPromise::ReadNextRawRepresentation() { ...@@ -295,9 +289,20 @@ void ClipboardPromise::ReadNextRawRepresentation() {
String format_name = String format_name =
clipboard_item_data_[clipboard_representation_index_].first; clipboard_item_data_[clipboard_representation_index_].first;
GetLocalFrame()->GetRawSystemClipboard()->Read( if (is_raw_) {
format_name, GetLocalFrame()->GetRawSystemClipboard()->Read(
WTF::Bind(&ClipboardPromise::OnRawRead, WrapPersistent(this))); format_name,
WTF::Bind(&ClipboardPromise::OnRawRead, WrapPersistent(this)));
return;
}
ClipboardReader* clipboard_reader = ClipboardReader::Create(
GetLocalFrame()->GetSystemClipboard(), format_name, this);
if (!clipboard_reader) {
OnRead(nullptr);
return;
}
clipboard_reader->Read();
} }
void ClipboardPromise::OnRawRead(mojo_base::BigBuffer data) { void ClipboardPromise::OnRawRead(mojo_base::BigBuffer data) {
...@@ -317,7 +322,14 @@ void ClipboardPromise::OnRawRead(mojo_base::BigBuffer data) { ...@@ -317,7 +322,14 @@ void ClipboardPromise::OnRawRead(mojo_base::BigBuffer data) {
} }
clipboard_item_data_[clipboard_representation_index_].second = blob; clipboard_item_data_[clipboard_representation_index_].second = blob;
++clipboard_representation_index_; ++clipboard_representation_index_;
ReadNextRawRepresentation(); ReadNextRepresentation();
}
void ClipboardPromise::OnRead(Blob* blob) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
clipboard_item_data_[clipboard_representation_index_].second = blob;
++clipboard_representation_index_;
ReadNextRepresentation();
} }
void ClipboardPromise::HandleReadTextWithPermission(PermissionStatus status) { void ClipboardPromise::HandleReadTextWithPermission(PermissionStatus status) {
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h" #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/core/fileapi/blob.h" #include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_item.h" #include "third_party/blink/renderer/modules/clipboard/clipboard_item.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h"
#include "third_party/blink/renderer/modules/clipboard/clipboard_writer.h" #include "third_party/blink/renderer/modules/clipboard/clipboard_writer.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h" #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
...@@ -50,6 +51,9 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>, ...@@ -50,6 +51,9 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
// For rejections originating from ClipboardWriter. // For rejections originating from ClipboardWriter.
void RejectFromReadOrDecodeFailure(); void RejectFromReadOrDecodeFailure();
// Adds the blob to the clipboard items.
void OnRead(Blob* blob);
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
private: private:
...@@ -68,8 +72,8 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>, ...@@ -68,8 +72,8 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
void HandleWriteWithPermission(mojom::blink::PermissionStatus); void HandleWriteWithPermission(mojom::blink::PermissionStatus);
void HandleWriteTextWithPermission(mojom::blink::PermissionStatus); void HandleWriteTextWithPermission(mojom::blink::PermissionStatus);
void OnReadAvailableRawFormatNames(const Vector<String>& format_names); void OnReadAvailableFormatNames(const Vector<String>& format_names);
void ReadNextRawRepresentation(); void ReadNextRepresentation();
void OnRawRead(mojo_base::BigBuffer data); void OnRawRead(mojo_base::BigBuffer data);
void ResolveRead(); void ResolveRead();
...@@ -87,6 +91,7 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>, ...@@ -87,6 +91,7 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
Member<ScriptPromiseResolver> script_promise_resolver_; Member<ScriptPromiseResolver> script_promise_resolver_;
Member<ClipboardWriter> clipboard_writer_; Member<ClipboardWriter> clipboard_writer_;
// Checks for Read and Write permission. // Checks for Read and Write permission.
HeapMojoRemote<mojom::blink::PermissionService, HeapMojoRemote<mojom::blink::PermissionService,
HeapMojoWrapperMode::kWithoutContextObserver> HeapMojoWrapperMode::kWithoutContextObserver>
......
// Copyright 2019 The Chromium Authors. All rights reserved. // Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "third_party/blink/renderer/modules/clipboard/clipboard_reader.h" #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/public/mojom/clipboard/clipboard.mojom-blink.h"
...@@ -9,7 +8,11 @@ ...@@ -9,7 +8,11 @@
#include "third_party/blink/renderer/core/clipboard/system_clipboard.h" #include "third_party/blink/renderer/core/clipboard/system_clipboard.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/imagebitmap/image_bitmap.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/platform/image-encoders/image_encoder.h" #include "third_party/blink/renderer/platform/image-encoders/image_encoder.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/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h" #include "third_party/blink/renderer/platform/wtf/wtf.h"
...@@ -20,18 +23,40 @@ namespace { // anonymous namespace for ClipboardReader's derived classes. ...@@ -20,18 +23,40 @@ namespace { // anonymous namespace for ClipboardReader's derived classes.
// Reads an image from the System Clipboard as a blob with image/png content. // Reads an image from the System Clipboard as a blob with image/png content.
class ClipboardImageReader final : public ClipboardReader { class ClipboardImageReader final : public ClipboardReader {
public: public:
explicit ClipboardImageReader(SystemClipboard* system_clipboard) explicit ClipboardImageReader(SystemClipboard* system_clipboard,
: ClipboardReader(system_clipboard) {} ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardImageReader() override = default; ~ClipboardImageReader() override = default;
Blob* ReadFromSystem() override { ClipboardImageReader(const ClipboardImageReader&) = delete;
ClipboardImageReader& operator=(const ClipboardImageReader&) = delete;
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SkBitmap bitmap = SkBitmap bitmap =
system_clipboard()->ReadImage(mojom::ClipboardBuffer::kStandard); 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::EncodeImageOnBackgroundThread,
std::move(image), WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
private:
static void EncodeImageOnBackgroundThread(
sk_sp<SkImage> image,
ClipboardImageReader* reader,
scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner) {
DCHECK(!IsMainThread());
// TODO(huangdarwin): Move encoding off the main thread.
// Encode bitmap to Vector<uint8_t> on the main thread.
SkPixmap pixmap; SkPixmap pixmap;
bitmap.peekPixels(&pixmap); image->peekPixels(&pixmap);
// Set encoding options to favor speed over size. // Set encoding options to favor speed over size.
SkPngEncoder::Options options; SkPngEncoder::Options options;
...@@ -40,56 +65,112 @@ class ClipboardImageReader final : public ClipboardReader { ...@@ -40,56 +65,112 @@ class ClipboardImageReader final : public ClipboardReader {
Vector<uint8_t> png_data; Vector<uint8_t> png_data;
if (!ImageEncoder::Encode(&png_data, pixmap, options)) if (!ImageEncoder::Encode(&png_data, pixmap, options))
return nullptr; png_data.clear();
// Now return to the kUserInteraction thread.
PostCrossThreadTask(*clipboard_task_runner, FROM_HERE,
CrossThreadBindOnce(&ClipboardImageReader::NextRead,
WrapCrossThreadPersistent(reader),
std::move(png_data)));
}
// An empty vector indicates that the encoding step failed.
void NextRead(Vector<uint8_t> png_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
Blob* blob = nullptr;
if (png_data.size())
blob = Blob::Create(png_data.data(), png_data.size(), kMimeTypeImagePng);
return Blob::Create(png_data.data(), png_data.size(), kMimeTypeImagePng); promise_->OnRead(blob);
} }
}; };
// Reads an image from the System Clipboard as a blob with text/plain content. // Reads an image from the System Clipboard as a blob with text/plain content.
class ClipboardTextReader final : public ClipboardReader { class ClipboardTextReader final : public ClipboardReader {
public: public:
explicit ClipboardTextReader(SystemClipboard* system_clipboard) explicit ClipboardTextReader(SystemClipboard* system_clipboard,
: ClipboardReader(system_clipboard) {} ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~ClipboardTextReader() override = default; ~ClipboardTextReader() override = default;
Blob* ReadFromSystem() override { ClipboardTextReader(const ClipboardTextReader&) = delete;
ClipboardTextReader& operator=(const ClipboardTextReader&) = delete;
void Read() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
String plain_text = String plain_text =
system_clipboard()->ReadPlainText(mojom::ClipboardBuffer::kStandard); system_clipboard()->ReadPlainText(mojom::ClipboardBuffer::kStandard);
// |plain_text| is empty if the clipboard is empty. if (plain_text.IsEmpty()) {
if (plain_text.IsEmpty()) NextRead(Vector<uint8_t>());
return nullptr; return;
}
worker_pool::PostTask(
FROM_HERE, CrossThreadBindOnce(
&ClipboardTextReader::EncodeTextOnBackgroundThread,
std::move(plain_text), WrapCrossThreadPersistent(this),
std::move(clipboard_task_runner_)));
}
private:
static void EncodeTextOnBackgroundThread(
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. // Encode WTF String to UTF-8, the standard text format for blobs.
StringUTF8Adaptor utf_text(plain_text); StringUTF8Adaptor utf8_text(plain_text);
return Blob::Create(reinterpret_cast<const uint8_t*>(utf_text.data()), Vector<uint8_t> utf8_bytes;
utf_text.size(), kMimeTypeTextPlain); 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) {
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);
} }
}; };
} // anonymous namespace } // anonymous namespace
// ClipboardReader functions. // ClipboardReader functions.
// static // static
ClipboardReader* ClipboardReader::Create(SystemClipboard* system_clipboard, ClipboardReader* ClipboardReader::Create(SystemClipboard* system_clipboard,
const String& mime_type) { const String& mime_type,
ClipboardPromise* promise) {
if (mime_type == kMimeTypeImagePng) if (mime_type == kMimeTypeImagePng)
return MakeGarbageCollected<ClipboardImageReader>(system_clipboard); return MakeGarbageCollected<ClipboardImageReader>(system_clipboard,
promise);
if (mime_type == kMimeTypeTextPlain) if (mime_type == kMimeTypeTextPlain)
return MakeGarbageCollected<ClipboardTextReader>(system_clipboard); return MakeGarbageCollected<ClipboardTextReader>(system_clipboard, promise);
// The MIME type is not supported. // The MIME type is not supported.
return nullptr; return nullptr;
} }
ClipboardReader::ClipboardReader(SystemClipboard* system_clipboard) ClipboardReader::ClipboardReader(SystemClipboard* system_clipboard,
: system_clipboard_(system_clipboard) {} ClipboardPromise* promise)
: clipboard_task_runner_(promise->GetExecutionContext()->GetTaskRunner(
TaskType::kUserInteraction)),
promise_(promise),
system_clipboard_(system_clipboard) {}
ClipboardReader::~ClipboardReader() = default; ClipboardReader::~ClipboardReader() = default;
void ClipboardReader::Trace(Visitor* visitor) const { void ClipboardReader::Trace(Visitor* visitor) const {
visitor->Trace(system_clipboard_); visitor->Trace(system_clipboard_);
visitor->Trace(promise_);
} }
} // namespace blink } // namespace blink
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_READER_H_ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_READER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_READER_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_CLIPBOARD_CLIPBOARD_READER_H_
#include "base/sequence_checker.h"
#include "third_party/blink/renderer/core/fileapi/blob.h" #include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
namespace blink { namespace blink {
class SystemClipboard; class SystemClipboard;
class ClipboardPromise;
// Interface for reading async-clipboard-compatible types from the sanitized // Interface for reading async-clipboard-compatible types from the sanitized
// System Clipboard as a Blob. // System Clipboard as a Blob.
...@@ -21,19 +23,28 @@ class SystemClipboard; ...@@ -21,19 +23,28 @@ class SystemClipboard;
// (3) Writing the contents to a blob. // (3) Writing the contents to a blob.
class ClipboardReader : public GarbageCollected<ClipboardReader> { class ClipboardReader : public GarbageCollected<ClipboardReader> {
public: public:
// Returns nullptr if there is no implementation for the given mime_type.
static ClipboardReader* Create(SystemClipboard* system_clipboard, static ClipboardReader* Create(SystemClipboard* system_clipboard,
const String& mime_type); const String& mime_type,
ClipboardPromise* promise);
virtual ~ClipboardReader(); virtual ~ClipboardReader();
// Returns nullptr if the data is empty or invalid. // Reads from the system clipboard and encodes on a background thread.
virtual Blob* ReadFromSystem() = 0; virtual void Read() = 0;
void Trace(Visitor* visitor) const; void Trace(Visitor* visitor) const;
protected: protected:
explicit ClipboardReader(SystemClipboard* system_clipboard); // TaskRunner for interacting with the system clipboard.
const scoped_refptr<base::SingleThreadTaskRunner> clipboard_task_runner_;
explicit ClipboardReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise);
SystemClipboard* system_clipboard() { return system_clipboard_; } SystemClipboard* system_clipboard() { return system_clipboard_; }
Member<ClipboardPromise> promise_;
SEQUENCE_CHECKER(sequence_checker_);
private: private:
// Access to the global sanitized system clipboard. // Access to the global sanitized system clipboard.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment