Commit ac319a7e authored by garykac's avatar garykac Committed by Commit Bot

Initial version of Async Clipboard API

Includes basic support for plaintext read/write.

See https://w3c.github.io/clipboard-apis/

BUG=677564

Review-Url: https://codereview.chromium.org/2695593006
Cr-Commit-Position: refs/heads/master@{#485121}
parent cbd45891
......@@ -19,6 +19,8 @@
## Owners: paint-dev@chromium.org
# external/wpt/2dcontext [ Pass ]
## Owners: garykac@chromium.org
# external/wpt/clipboard-apis [Pass]
## Owners: jsbell@chromium.org
# external/wpt/FileAPI [ Pass ]
## Owners: jsbell@chromium.org
......
<!DOCTYPE html>
<meta charset=utf-8>
<title>Clipboard IDL test</title>
<link rel="help" href="https://w3c.github.io/clipboard-apis/">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<script src=/resources/WebIDLParser.js></script>
<script src=/resources/idlharness.js></script>
<script>
'use strict';
function doTest(idls) {
var idl_array = new IdlArray();
idl_array.add_untested_idls('interface Navigator {};');
idl_array.add_untested_idls('interface EventTarget {};');
for (let idl of idls) {
idl_array.add_idls(idl);
}
idl_array.add_objects({
Navigator: ['navigator'],
Clipboard: ['navigator.clipboard'],
});
idl_array.test();
};
function fetchText(url) {
return fetch(url).then((response) => response.text());
}
promise_test(() => {
return Promise.all(["/interfaces/clipboard.idl"].map(fetchText))
.then(doTest);
}, "Test driver");
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async Clipboard basic tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
test(function() {
assert_not_equals(navigator.clipboard, undefined);
assert_true(navigator.clipboard instanceof Clipboard);
assert_equals(navigator.clipboard, navigator.clipboard);
}, "navigator.clipboard exists");
/* clipboard.write() */
promise_test(function() {
var dt = new DataTransfer();
dt.items.add("Howdy", "text/plain");
return navigator.clipboard.write(dt);
}, "navigator.clipboard.write(DataTransfer) succeeds");
promise_test(function(t) {
return promise_rejects(t, new TypeError(),
navigator.clipboard.write());
}, "navigator.clipboard.write() fails (expect DataTransfer)");
promise_test(function(t) {
return promise_rejects(t, new TypeError(),
navigator.clipboard.write(null));
}, "navigator.clipboard.write(null) fails (expect DataTransfer)");
promise_test(function(t) {
return promise_rejects(t, new TypeError(),
navigator.clipboard.write("Bad string"));
}, "navigator.clipboard.write(DOMString) fails (expect DataTransfer)");
/* clipboard.writeText() */
promise_test(function() {
return navigator.clipboard.writeText("New clipboard text");
}, "navigator.clipboard.writeText(DOMString) succeeds");
promise_test(function(t) {
return promise_rejects(t, new TypeError(),
navigator.clipboard.writeText());
}, "navigator.clipboard.writeText() fails (expect DOMString)");
/* clipboard.read() */
promise_test(function() {
return navigator.clipboard.read()
.then(function(result) {
assert_true(result instanceof DataTransfer);
});
}, "navigator.clipboard.read() succeeds");
/* clipboard.readText() */
promise_test(function() {
return navigator.clipboard.readText()
.then(function(result) {
assert_equals(typeof result, "string");
});
}, "navigator.clipboard.readText() succeeds");
</script>
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async Clipboard write (dt/text) -> read (dt/text) tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test(function(t) {
var test_data = "Clipboard write (dt/text) -> read (dt/text) test data";
var cb = navigator.clipboard;
var dt = new DataTransfer();
dt.items.add(test_data, "text/plain");
cb.write(dt).then(t.step_func(() => {
cb.read().then(t.step_func((data) => {
assert_equals(data.items.length, 1);
data.items[0].getAsString(t.step_func((s) => {
assert_equals(s, test_data);
t.done();
}));
}), function() {
t.unreached_func("clipboard.read() fail");
});
}), function() {
t.unreached_func("clipboard.write() fail");
});
}, "Verify write and read clipboard (DataTransfer/text)");
</script>
Note: This is a manual test because it writes/reads to the shared system
clipboard and thus cannot be run async with other tests that might interact
with the clipboard.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async Clipboard write (dt/text) -> readText tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test(function(t) {
var test_data = "Clipboard write (dt/text) -> readText test data";
var cb = navigator.clipboard;
var dt = new DataTransfer();
dt.items.add(test_data, "text/plain");
cb.write(dt).then(t.step_func(() => {
cb.readText().then(t.step_func((data) => {
assert_equals(data, test_data);
t.done();
}), function() {
t.unreached_func("clipboard.read() fail");
});
}), function() {
t.unreached_func("clipboard.write() fail");
});
}, "Verify write and read clipboard (DataTransfer/text)");
</script>
Note: This is a manual test because it writes/reads to the shared system
clipboard and thus cannot be run async with other tests that might interact
with the clipboard.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async Clipboard writeText -> read (dt/text) tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test(function(t) {
var test_data = "Clipboard writeText -> read(dt/text) test data";
var cb = navigator.clipboard;
cb.writeText(test_data).then(t.step_func(() => {
cb.read().then(t.step_func((data) => {
assert_equals(data.items.length, 1);
data.items[0].getAsString(t.step_func((s) => {
assert_equals(s, test_data);
t.done();
}));
}), function() {
t.unreached_func("clipboard.read() fail");
});
}), function() {
t.unreached_func("clipboard.writeText() fail");
});
}, "Verify write and read clipboard (DOMString)");
</script>
Note: This is a manual test because it writes/reads to the shared system
clipboard and thus cannot be run async with other tests that might interact
with the clipboard.
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async Clipboard writeText -> readText tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test(function(t) {
var test_data = "Clipboard writeText -> readText test data";
var cb = navigator.clipboard;
cb.writeText(test_data).then(t.step_func(() => {
cb.readText().then(t.step_func((data) => {
assert_equals(data, test_data);
t.done();
}), function() {
t.unreached_func("clipboard.readText() fail");
});
}), function() {
t.unreached_func("clipboard.writeText() fail");
});
}, "Verify write and read clipboard (DOMString)");
</script>
Note: This is a manual test because it writes/reads to the shared system
clipboard and thus cannot be run async with other tests that might interact
with the clipboard.
[SecureContext]
interface Clipboard : EventTarget {
Promise<DataTransfer> read();
Promise<DOMString> readText();
Promise<void> write(DataTransfer data);
Promise<void> writeText(DOMString data);
};
[SecureContext]
partial interface Navigator {
[SameObject] readonly attribute Clipboard clipboard;
};
......@@ -615,6 +615,13 @@ interface ClientRectList
method @@iterator
method constructor
method item
interface Clipboard : EventTarget
attribute @@toStringTag
method constructor
method read
method readText
method write
method writeText
interface ClipboardEvent : Event
attribute @@toStringTag
getter clipboardData
......
......@@ -544,6 +544,13 @@ interface ClientRectList
method @@iterator
method constructor
method item
interface Clipboard : EventTarget
attribute @@toStringTag
method constructor
method read
method readText
method write
method writeText
interface ClipboardEvent : Event
attribute @@toStringTag
getter clipboardData
......
......@@ -1025,6 +1025,13 @@ interface ClientRectList
method @@iterator
method constructor
method item
interface Clipboard : EventTarget
attribute @@toStringTag
method constructor
method read
method readText
method write
method writeText
interface ClipboardEvent : Event
attribute @@toStringTag
getter clipboardData
......@@ -4411,6 +4418,7 @@ interface Navigator
getter authentication
getter bluetooth
getter budget
getter clipboard
getter connection
getter cookieEnabled
getter credentials
......
......@@ -1025,6 +1025,13 @@ interface ClientRectList
method @@iterator
method constructor
method item
interface Clipboard : EventTarget
attribute @@toStringTag
method constructor
method read
method readText
method write
method writeText
interface ClipboardEvent : Event
attribute @@toStringTag
getter clipboardData
......@@ -4418,6 +4425,7 @@ interface Navigator
getter authentication
getter bluetooth
getter budget
getter clipboard
getter connection
getter cookieEnabled
getter credentials
......
......@@ -6,6 +6,10 @@ import("//third_party/WebKit/Source/core/core.gni")
blink_core_sources("clipboard") {
sources = [
"Clipboard.cpp",
"Clipboard.h",
"ClipboardPromise.cpp",
"ClipboardPromise.h",
"DataObject.cpp",
"DataObject.h",
"DataObjectItem.cpp",
......
// 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 "core/clipboard/Clipboard.h"
#include "bindings/core/v8/ScriptPromiseResolver.h"
#include "core/clipboard/ClipboardPromise.h"
namespace blink {
Clipboard::Clipboard(ExecutionContext* context)
: ContextLifecycleObserver(context) {}
ScriptPromise Clipboard::read(ScriptState* script_state) {
return ClipboardPromise::CreateForRead(script_state);
}
ScriptPromise Clipboard::readText(ScriptState* script_state) {
return ClipboardPromise::CreateForReadText(script_state);
}
ScriptPromise Clipboard::write(ScriptState* script_state, DataTransfer* data) {
return ClipboardPromise::CreateForWrite(script_state, data);
}
ScriptPromise Clipboard::writeText(ScriptState* script_state,
const String& data) {
return ClipboardPromise::CreateForWriteText(script_state, data);
}
const AtomicString& Clipboard::InterfaceName() const {
return EventTargetNames::Clipboard;
}
ExecutionContext* Clipboard::GetExecutionContext() const {
return ContextLifecycleObserver::GetExecutionContext();
}
DEFINE_TRACE(Clipboard) {
EventTarget::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink
// 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.
#ifndef Clipboard_h
#define Clipboard_h
#include "bindings/core/v8/ScriptPromise.h"
#include "core/dom/ContextLifecycleObserver.h"
#include "core/events/EventTarget.h"
#include "platform/wtf/Noncopyable.h"
namespace blink {
class DataTransfer;
class ScriptState;
class Clipboard : public EventTargetWithInlineData,
public ContextLifecycleObserver {
USING_GARBAGE_COLLECTED_MIXIN(Clipboard);
DEFINE_WRAPPERTYPEINFO();
WTF_MAKE_NONCOPYABLE(Clipboard);
public:
explicit Clipboard(ExecutionContext*);
ScriptPromise read(ScriptState*);
ScriptPromise readText(ScriptState*);
ScriptPromise write(ScriptState*, DataTransfer*);
ScriptPromise writeText(ScriptState*, const String&);
// EventTarget
const AtomicString& InterfaceName() const override;
ExecutionContext* GetExecutionContext() const override;
DECLARE_VIRTUAL_TRACE();
};
} // namespace blink
#endif // Clipboard_h
// 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.
// https://w3c.github.io/clipboard-apis/#clipboard-interface
[SecureContext]
interface Clipboard : EventTarget {
[CallWith=ScriptState] Promise<DataTransfer> read();
[CallWith=ScriptState] Promise<DOMString> readText();
[CallWith=ScriptState] Promise<void> write(DataTransfer data);
[CallWith=ScriptState] Promise<void> writeText(DOMString data);
};
// 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 "core/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/TaskRunnerHelper.h"
#include "platform/CrossThreadFunctional.h"
#include "platform/clipboard/ClipboardMimeTypes.h"
#include "public/platform/Platform.h"
namespace blink {
ScriptPromise ClipboardPromise::CreateForRead(ScriptState* script_state) {
ClipboardPromise* clipboard_promise = new ClipboardPromise(script_state);
clipboard_promise->GetTaskRunner()->PostTask(
BLINK_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(
BLINK_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(
BLINK_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(
BLINK_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_promise_resolver_(ScriptPromiseResolver::Create(script_state)),
buffer_(WebClipboard::kBufferStandard) {}
WebTaskRunner* ClipboardPromise::GetTaskRunner() {
// TODO(garykac): Replace MiscPlatformAPI with TaskType specific to clipboard.
return TaskRunnerHelper::Get(TaskType::kMiscPlatformAPI,
GetExecutionContext())
.Get();
}
// TODO(garykac): This currently only handles plain text.
void ClipboardPromise::HandleRead() {
DCHECK(script_promise_resolver_);
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() {
DCHECK(script_promise_resolver_);
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) {
DCHECK(script_promise_resolver_);
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) {
String text = objectItem->GetAsString();
Platform::Current()->Clipboard()->WritePlainText(text);
script_promise_resolver_->Resolve();
return;
}
}
script_promise_resolver_->Reject();
}
void ClipboardPromise::HandleWriteText(const String& data) {
DCHECK(script_promise_resolver_);
Platform::Current()->Clipboard()->WritePlainText(data);
script_promise_resolver_->Resolve();
}
DEFINE_TRACE(ClipboardPromise) {
visitor->Trace(script_promise_resolver_);
ContextLifecycleObserver::Trace(visitor);
}
} // namespace blink
// 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.
#ifndef ClipboardPromise_h
#define ClipboardPromise_h
#include "bindings/core/v8/ScriptPromise.h"
#include "core/CoreExport.h"
#include "core/dom/ContextLifecycleObserver.h"
#include "public/platform/WebClipboard.h"
namespace blink {
class DataTransfer;
class ScriptPromiseResolver;
class ClipboardPromise final
: public GarbageCollectedFinalized<ClipboardPromise>,
public ContextLifecycleObserver {
USING_GARBAGE_COLLECTED_MIXIN(ClipboardPromise);
WTF_MAKE_NONCOPYABLE(ClipboardPromise);
public:
virtual ~ClipboardPromise(){};
static ScriptPromise CreateForRead(ScriptState*);
static ScriptPromise CreateForReadText(ScriptState*);
static ScriptPromise CreateForWrite(ScriptState*, DataTransfer*);
static ScriptPromise CreateForWriteText(ScriptState*, const String&);
DECLARE_VIRTUAL_TRACE();
private:
ClipboardPromise(ScriptState*);
WebTaskRunner* GetTaskRunner();
void HandleRead();
void HandleReadText();
void HandleWrite(DataTransfer*);
void HandleWriteText(const String&);
Member<ScriptPromiseResolver> script_promise_resolver_;
WebClipboard::Buffer buffer_;
};
} // namespace blink
#endif // ClipboardPromise_h
......@@ -35,6 +35,7 @@ core_idl_files = get_path_info([
"animation/KeyframeEffect.idl",
"animation/KeyframeEffectReadOnly.idl",
"animation/ScrollTimeline.idl",
"clipboard/Clipboard.idl",
"clipboard/DataTransfer.idl",
"clipboard/DataTransferItemList.idl",
"css/CSS.idl",
......@@ -470,6 +471,7 @@ core_dependency_idl_files =
"events/EventListener.idl",
"events/NavigatorEvents.idl",
"fileapi/URLFileAPI.idl",
"frame/NavigatorClipboard.idl",
"frame/NavigatorConcurrentHardware.idl",
"frame/NavigatorCookies.idl",
"frame/NavigatorID.idl",
......
......@@ -6,6 +6,7 @@
data: [
"core/animation/AnimationPlayer",
"core/clipboard/Clipboard",
"core/css/FontFaceSet",
"core/css/MediaQueryList",
"core/dom/BroadcastChannel",
......
......@@ -68,6 +68,8 @@ blink_core_sources("frame") {
"Location.h",
"Navigator.cpp",
"Navigator.h",
"NavigatorClipboard.cpp",
"NavigatorClipboard.h",
"NavigatorConcurrentHardware.cpp",
"NavigatorConcurrentHardware.h",
"NavigatorID.cpp",
......
// 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 "core/frame/NavigatorClipboard.h"
#include "core/clipboard/Clipboard.h"
#include "core/dom/Document.h"
#include "core/frame/LocalFrame.h"
namespace blink {
Clipboard* NavigatorClipboard::clipboard(ScriptState* script_state,
Navigator& navigator) {
NavigatorClipboard* supplement = static_cast<NavigatorClipboard*>(
Supplement<Navigator>::From(navigator, SupplementName()));
if (!supplement) {
supplement = new NavigatorClipboard(navigator);
ProvideTo(navigator, SupplementName(), supplement);
}
return supplement->clipboard_;
}
DEFINE_TRACE(NavigatorClipboard) {
visitor->Trace(clipboard_);
Supplement<Navigator>::Trace(visitor);
}
NavigatorClipboard::NavigatorClipboard(Navigator& navigator)
: Supplement<Navigator>(navigator) {
clipboard_ =
new Clipboard(GetSupplementable()->GetFrame()
? GetSupplementable()->GetFrame()->GetDocument()
: nullptr);
}
const char* NavigatorClipboard::SupplementName() {
return "NavigatorClipboard";
}
} // namespace blink
// 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.
#ifndef NavigatorClipboard_h
#define NavigatorClipboard_h
#include "core/CoreExport.h"
#include "core/frame/Navigator.h"
#include "platform/Supplementable.h"
#include "platform/heap/Handle.h"
#include "platform/wtf/Noncopyable.h"
namespace blink {
class Clipboard;
class ScriptState;
class NavigatorClipboard final : public GarbageCollected<NavigatorClipboard>,
public Supplement<Navigator> {
USING_GARBAGE_COLLECTED_MIXIN(NavigatorClipboard);
WTF_MAKE_NONCOPYABLE(NavigatorClipboard);
public:
static Clipboard* clipboard(ScriptState*, Navigator&);
DECLARE_TRACE();
private:
explicit NavigatorClipboard(Navigator&);
static const char* SupplementName();
Member<Clipboard> clipboard_;
};
} // namespace blink
#endif // NavigatorClipboard.h
// 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.
// https://w3c.github.io/clipboard-apis/#navigator-interface
[RuntimeEnabled=AsyncClipboard]
partial interface Navigator {
[CallWith=ScriptState, SecureContext, SameObject]
readonly attribute Clipboard clipboard;
};
......@@ -79,6 +79,10 @@
name: "AllowContentInitiatedDataUrlNavigations",
status: "stable",
},
{
name: "AsyncClipboard",
status: "experimental",
},
{
name: "AudioOutputDevices",
status: "stable",
......
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