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 @@
#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/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/platform/heap/heap.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"
// There are 2 clipboard permissions defined in the spec:
......@@ -234,28 +237,19 @@ void ClipboardPromise::HandleReadWithPermission(PermissionStatus status) {
if (is_raw_) {
RawSystemClipboard* raw_system_clipboard =
GetLocalFrame()->GetRawSystemClipboard();
raw_system_clipboard->ReadAvailableFormatNames(
WTF::Bind(&ClipboardPromise::OnReadAvailableRawFormatNames,
WrapPersistent(this)));
raw_system_clipboard->ReadAvailableFormatNames(WTF::Bind(
&ClipboardPromise::OnReadAvailableFormatNames, WrapPersistent(this)));
return;
}
SystemClipboard* system_clipboard = GetLocalFrame()->GetSystemClipboard();
Vector<String> available_types = system_clipboard->ReadAvailableTypes();
clipboard_item_data_.ReserveInitialCapacity(available_types.size());
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();
OnReadAvailableFormatNames(available_types);
}
void ClipboardPromise::ResolveRead() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetExecutionContext());
if (!clipboard_item_data_.size()) {
script_promise_resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kDataError, "No valid data on clipboard."));
......@@ -270,7 +264,7 @@ void ClipboardPromise::ResolveRead() {
script_promise_resolver_->Resolve(clipboard_items);
}
void ClipboardPromise::OnReadAvailableRawFormatNames(
void ClipboardPromise::OnReadAvailableFormatNames(
const Vector<String>& format_names) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!GetExecutionContext())
......@@ -281,10 +275,10 @@ void ClipboardPromise::OnReadAvailableRawFormatNames(
clipboard_item_data_.emplace_back(format_name,
/* Placeholder value. */ nullptr);
}
ReadNextRawRepresentation();
ReadNextRepresentation();
}
void ClipboardPromise::ReadNextRawRepresentation() {
void ClipboardPromise::ReadNextRepresentation() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!GetExecutionContext())
return;
......@@ -295,9 +289,20 @@ void ClipboardPromise::ReadNextRawRepresentation() {
String format_name =
clipboard_item_data_[clipboard_representation_index_].first;
if (is_raw_) {
GetLocalFrame()->GetRawSystemClipboard()->Read(
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) {
......@@ -317,7 +322,14 @@ void ClipboardPromise::OnRawRead(mojo_base::BigBuffer data) {
}
clipboard_item_data_[clipboard_representation_index_].second = blob;
++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) {
......
......@@ -14,6 +14,7 @@
#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/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/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
......@@ -50,6 +51,9 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
// For rejections originating from ClipboardWriter.
void RejectFromReadOrDecodeFailure();
// Adds the blob to the clipboard items.
void OnRead(Blob* blob);
void Trace(Visitor*) const override;
private:
......@@ -68,8 +72,8 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
void HandleWriteWithPermission(mojom::blink::PermissionStatus);
void HandleWriteTextWithPermission(mojom::blink::PermissionStatus);
void OnReadAvailableRawFormatNames(const Vector<String>& format_names);
void ReadNextRawRepresentation();
void OnReadAvailableFormatNames(const Vector<String>& format_names);
void ReadNextRepresentation();
void OnRawRead(mojo_base::BigBuffer data);
void ResolveRead();
......@@ -87,6 +91,7 @@ class ClipboardPromise final : public GarbageCollected<ClipboardPromise>,
Member<ScriptPromiseResolver> script_promise_resolver_;
Member<ClipboardWriter> clipboard_writer_;
// Checks for Read and Write permission.
HeapMojoRemote<mojom::blink::PermissionService,
HeapMojoWrapperMode::kWithoutContextObserver>
......
// 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"
......@@ -9,7 +8,11 @@
#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/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/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"
......@@ -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.
class ClipboardImageReader final : public ClipboardReader {
public:
explicit ClipboardImageReader(SystemClipboard* system_clipboard)
: ClipboardReader(system_clipboard) {}
explicit ClipboardImageReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~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 =
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;
bitmap.peekPixels(&pixmap);
image->peekPixels(&pixmap);
// Set encoding options to favor speed over size.
SkPngEncoder::Options options;
......@@ -40,56 +65,112 @@ class ClipboardImageReader final : public ClipboardReader {
Vector<uint8_t> png_data;
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.
class ClipboardTextReader final : public ClipboardReader {
public:
explicit ClipboardTextReader(SystemClipboard* system_clipboard)
: ClipboardReader(system_clipboard) {}
explicit ClipboardTextReader(SystemClipboard* system_clipboard,
ClipboardPromise* promise)
: ClipboardReader(system_clipboard, promise) {}
~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 =
system_clipboard()->ReadPlainText(mojom::ClipboardBuffer::kStandard);
// |plain_text| is empty if the clipboard is empty.
if (plain_text.IsEmpty())
return nullptr;
if (plain_text.IsEmpty()) {
NextRead(Vector<uint8_t>());
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.
StringUTF8Adaptor utf_text(plain_text);
return Blob::Create(reinterpret_cast<const uint8_t*>(utf_text.data()),
utf_text.size(), kMimeTypeTextPlain);
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) {
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
// ClipboardReader functions.
// static
ClipboardReader* ClipboardReader::Create(SystemClipboard* system_clipboard,
const String& mime_type) {
const String& mime_type,
ClipboardPromise* promise) {
if (mime_type == kMimeTypeImagePng)
return MakeGarbageCollected<ClipboardImageReader>(system_clipboard);
return MakeGarbageCollected<ClipboardImageReader>(system_clipboard,
promise);
if (mime_type == kMimeTypeTextPlain)
return MakeGarbageCollected<ClipboardTextReader>(system_clipboard);
return MakeGarbageCollected<ClipboardTextReader>(system_clipboard, promise);
// The MIME type is not supported.
return nullptr;
}
ClipboardReader::ClipboardReader(SystemClipboard* system_clipboard)
: system_clipboard_(system_clipboard) {}
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
......@@ -5,12 +5,14 @@
#ifndef 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/platform/heap/heap.h"
namespace blink {
class SystemClipboard;
class ClipboardPromise;
// Interface for reading async-clipboard-compatible types from the sanitized
// System Clipboard as a Blob.
......@@ -21,19 +23,28 @@ class SystemClipboard;
// (3) Writing the contents to a blob.
class ClipboardReader : public GarbageCollected<ClipboardReader> {
public:
// Returns nullptr if there is no implementation for the given mime_type.
static ClipboardReader* Create(SystemClipboard* system_clipboard,
const String& mime_type);
const String& mime_type,
ClipboardPromise* promise);
virtual ~ClipboardReader();
// Returns nullptr if the data is empty or invalid.
virtual Blob* ReadFromSystem() = 0;
// Reads from the system clipboard and encodes on a background thread.
virtual void Read() = 0;
void Trace(Visitor* visitor) const;
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_; }
Member<ClipboardPromise> promise_;
SEQUENCE_CHECKER(sequence_checker_);
private:
// 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