blob: f1ea1d9ff98d84971c7926ac68ea97ccc0642076 [file] [log] [blame]
// Copyright 2017 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 "modules/clipboard/ClipboardPromise.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/clipboard/DataObject.h"
#include "core/clipboard/DataTransfer.h"
#include "core/clipboard/DataTransferItem.h"
#include "core/clipboard/DataTransferItemList.h"
#include "core/dom/ExecutionContext.h"
#include "core/frame/LocalFrame.h"
#include "modules/permissions/PermissionUtils.h"
#include "platform/clipboard/ClipboardMimeTypes.h"
#include "public/platform/Platform.h"
#include "public/platform/TaskType.h"
#include "public/platform/modules/permissions/permission.mojom-blink.h"
#include "third_party/WebKit/public/platform/WebClipboard.h"
// And now, a brief note about clipboard permissions.
//
// There are 2 clipboard permissions defined in the spec:
// * clipboard-read
// * clipboard-write
// See https://w3c.github.io/clipboard-apis/#clipboard-permissions
//
// In Chromium we automatically grant clipboard-write access and clipboard-read
// access is gated behind a permission prompt. Both clipboard read and write
// require the tab to be focused (and Chromium must be the foreground app) for
// the operation to be allowed.
namespace blink {
using mojom::blink::PermissionStatus;
using mojom::blink::PermissionService;
using mojom::PageVisibilityState;
ScriptPromise ClipboardPromise::CreateForRead(ScriptState* script_state) {
ClipboardPromise* clipboard_promise = new ClipboardPromise(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&ClipboardPromise::HandleRead,
WrapPersistent(clipboard_promise)));
return clipboard_promise->script_promise_resolver_->Promise();
}
ScriptPromise ClipboardPromise::CreateForReadText(ScriptState* script_state) {
ClipboardPromise* clipboard_promise = new ClipboardPromise(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&ClipboardPromise::HandleReadText,
WrapPersistent(clipboard_promise)));
return clipboard_promise->script_promise_resolver_->Promise();
}
ScriptPromise ClipboardPromise::CreateForWrite(ScriptState* script_state,
DataTransfer* data) {
ClipboardPromise* clipboard_promise = new ClipboardPromise(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
FROM_HERE,
WTF::Bind(&ClipboardPromise::HandleWrite,
WrapPersistent(clipboard_promise), WrapPersistent(data)));
return clipboard_promise->script_promise_resolver_->Promise();
}
ScriptPromise ClipboardPromise::CreateForWriteText(ScriptState* script_state,
const String& data) {
ClipboardPromise* clipboard_promise = new ClipboardPromise(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
FROM_HERE, WTF::Bind(&ClipboardPromise::HandleWriteText,
WrapPersistent(clipboard_promise), data));
return clipboard_promise->script_promise_resolver_->Promise();
}
ClipboardPromise::ClipboardPromise(ScriptState* script_state)
: ContextLifecycleObserver(blink::ExecutionContext::From(script_state)),
script_state_(script_state),
script_promise_resolver_(ScriptPromiseResolver::Create(script_state)),
buffer_(mojom::ClipboardBuffer::kStandard),
write_data_() {}
scoped_refptr<WebTaskRunner> ClipboardPromise::GetTaskRunner() {
// TODO(garykac): Replace MiscPlatformAPI with TaskType specific to clipboard.
return GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI);
}
PermissionService* ClipboardPromise::GetPermissionService() {
if (!permission_service_) {
ConnectToPermissionService(ExecutionContext::From(script_state_),
mojo::MakeRequest(&permission_service_));
}
return permission_service_.get();
}
bool ClipboardPromise::IsFocusedDocument(ExecutionContext* context) {
DCHECK(context->IsDocument());
Document* doc = ToDocumentOrNull(context);
return doc && doc->hasFocus();
}
void ClipboardPromise::RequestReadPermission(
PermissionService::RequestPermissionCallback callback) {
DCHECK(script_promise_resolver_);
ExecutionContext* context = ExecutionContext::From(script_state_);
DCHECK(context->IsSecureContext()); // [SecureContext] in IDL
// Document must be focused.
if (!IsFocusedDocument(context) || !GetPermissionService()) {
script_promise_resolver_->Reject();
return;
}
// Query for permission if necessary.
// See crbug.com/795929 for moving this check into the Browser process.
permission_service_->RequestPermission(
CreateClipboardPermissionDescriptor(
mojom::blink::PermissionName::CLIPBOARD_READ, false),
false, std::move(callback));
}
void ClipboardPromise::CheckWritePermission(
PermissionService::HasPermissionCallback callback) {
DCHECK(script_promise_resolver_);
ExecutionContext* context = ExecutionContext::From(script_state_);
DCHECK(context->IsSecureContext()); // [SecureContext] in IDL
// Document must be focused.
if (!IsFocusedDocument(context) || !GetPermissionService()) {
script_promise_resolver_->Reject();
return;
}
// Check current permission (but do not query the user).
// See crbug.com/795929 for moving this check into the Browser process.
permission_service_->HasPermission(
CreateClipboardPermissionDescriptor(
mojom::blink::PermissionName::CLIPBOARD_WRITE, false),
std::move(callback));
}
void ClipboardPromise::HandleRead() {
RequestReadPermission(WTF::Bind(&ClipboardPromise::HandleReadWithPermission,
WrapPersistent(this)));
}
// TODO(garykac): This currently only handles plain text.
void ClipboardPromise::HandleReadWithPermission(PermissionStatus status) {
if (status != PermissionStatus::GRANTED) {
script_promise_resolver_->Reject();
return;
}
String plain_text = Platform::Current()->Clipboard()->ReadPlainText(buffer_);
const DataTransfer::DataTransferType type =
DataTransfer::DataTransferType::kCopyAndPaste;
const DataTransferAccessPolicy access =
DataTransferAccessPolicy::kDataTransferReadable;
DataObject* data = DataObject::CreateFromString(plain_text);
DataTransfer* dt = DataTransfer::Create(type, access, data);
script_promise_resolver_->Resolve(dt);
}
void ClipboardPromise::HandleReadText() {
RequestReadPermission(WTF::Bind(
&ClipboardPromise::HandleReadTextWithPermission, WrapPersistent(this)));
}
void ClipboardPromise::HandleReadTextWithPermission(PermissionStatus status) {
if (status != PermissionStatus::GRANTED) {
script_promise_resolver_->Reject();
return;
}
String text = Platform::Current()->Clipboard()->ReadPlainText(buffer_);
script_promise_resolver_->Resolve(text);
}
// TODO(garykac): This currently only handles plain text.
void ClipboardPromise::HandleWrite(DataTransfer* data) {
// Scan DataTransfer and extract data types that we support.
size_t num_items = data->items()->length();
for (unsigned long i = 0; i < num_items; i++) {
DataTransferItem* item = data->items()->item(i);
DataObjectItem* objectItem = item->GetDataObjectItem();
if (objectItem->Kind() == DataObjectItem::kStringKind &&
objectItem->GetType() == kMimeTypeTextPlain) {
write_data_ = objectItem->GetAsString();
break;
}
}
CheckWritePermission(WTF::Bind(&ClipboardPromise::HandleWriteWithPermission,
WrapPersistent(this)));
}
void ClipboardPromise::HandleWriteWithPermission(PermissionStatus status) {
if (status != PermissionStatus::GRANTED) {
script_promise_resolver_->Reject();
return;
}
Platform::Current()->Clipboard()->WritePlainText(write_data_);
script_promise_resolver_->Resolve();
}
void ClipboardPromise::HandleWriteText(const String& data) {
write_data_ = data;
CheckWritePermission(WTF::Bind(
&ClipboardPromise::HandleWriteTextWithPermission, WrapPersistent(this)));
}
void ClipboardPromise::HandleWriteTextWithPermission(PermissionStatus status) {
if (status != PermissionStatus::GRANTED) {
script_promise_resolver_->Reject();
return;
}
DCHECK(script_promise_resolver_);
Platform::Current()->Clipboard()->WritePlainText(write_data_);
script_promise_resolver_->Resolve();
}
void ClipboardPromise::Trace(blink::Visitor* visitor) {
visitor->Trace(script_promise_resolver_);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink