A modern API for asynchronous clipboard access.
-
A new object:
navigator.clipboard
. -
Basic async methods for reading/writing clipboard data that return a
Promise
. -
A new
clipboardchange
event to detect clipboard changes.
For a complete description of this API, see the Clipboard API and events specification.
An API for clipboard actions (cut, copy and paste) has long been desired for webpages, but agreement has been slow because of various security and usability concerns with the feature. In summary, giving web pages access to the clipboard introduces problems with clobbering (overwriting) and sniffing (surreptitiously reading) the clipboard.
This lack of a consistent API has forced web developers who need this feature to rely on alternate methods (e.g., Flash, via ZeroClipboard) which simply trades one set of problems for another.
Recently, however, all major browsers have converged on support for clipboard access
using document.execCommand()
:
cut | copy | paste | |
---|---|---|---|
IE 1 |
9 |
9 |
9 |
Chrome 2 |
43 |
43 |
no |
Firefox 2 |
41 |
41 |
? |
Opera 2 |
29 |
29 |
29 |
Safari 2 |
yes 3 |
yes 3 |
? |
Edge 2 |
12 |
12 |
14 4 |
1 A permission prompt to the user when the feature is used.
2 Only permitted when executing code in response to a user action or gesture.
3 The Safari Technology Preview (announced on 2016-Mar-30)
has support for cut and copy.
4 Edge 14 supports paste only through an offline-managed Allow List.
However, even with this increased support, there are still many inconsistencies across browser implementations. See this blog post for a description of the problems Javascript programmers face, and note the existence of various Javascript clipboard libraries to help address these issues.
As noted earlier, the current Clipboard API
specification that the browsers are implementing assumes that the API should be
based on document.execCommand()
. However, there have recently been
discussions on public-webapps
questioning whether this is appropriate in the long term.
The specific issues that motivated this updated API are:
-
The current model is synchronous, so it blocks the main page.
-
This makes permission prompts more irritating (since the page is blocked).
-
This prevents sanitizing data types (like images) that need to be transcoded.
-
Transcoding is sometimes required to guard against exploits in external parsers.
-
It’s not reasonable to block page while large images are being sanitized.
-
The code required for programmatic clipboard actions is… bizarre:
-
To modify the clipboard, you need to add an event listener for 'copy'. This listener will fire when you call
execCommand('copy')
and also for any user initiated copies. -
There is a need for a notification event when the clipboard is mutated.
-
Apps that synchronize the clipboard (like remote access apps) need this to know when to send the updated clipboard contents to the secondary system.
-
execCommand
is old API originally designed for editing the DOM and it has a large number of interoperability bugs. The latest version of the execCommand spec states that it is incomplete and not expected to advance beyond draft status. -
In practice, many developers load a library to use
execCommand
properly. That shouldn’t be necessary for something as basic clipboard cut/copy/paste.
And finally,
-
Make it easier to support alternate permission models.
-
For example, to enable web apps to copy to the clipboard without requiring a user gesture.
-
Enable use of the Permissions API or a consent dialog that doesn’t block the browser.
This section provides more details about the new API.
The main clipboard object is part of the navigator
because it is a
"system" level object that is not tied to the current window or document.
The general write()
method takes a DataTransfer object and returns a
Promise<>
.
Example of writing to the clipboard using write()
:
var data = new DataTransfer();
data.items.add("Howdy, partner!", "text/plain");
navigator.clipboard.write(data);
Multiple MIME types can be added to the DataTransfer
object and they
will all be written to the clipboard:
var data = new DataTransfer();
data.items.add("Howdy, partner!", "text/plain");
data.items.add("<b>Howdy</b>, partner!", "text/html");
navigator.clipboard.write(data);
For convenience, when writing text to the clipboard, writeText()
can
be used:
navigator.clipboard.writeText("some text");
Because the return type of these write methods is a Promise<>
, they will
return immediately. If it is necessary to know when the operation has been
completed, then the Promise<>
can be handled as follows:
navigator.clipboard.writeText("some text").then(function() {
// Promise resolved successfully.
console.log("Copied to clipboard successfully!");
}, function() {
// Promise rejected.
console.error("Unable to write to clipboard. :-(");
});
The general read()
method returns a Promise<DataTransfer>
.
Example of reading from the clipboard using read()
:
navigator.clipboard.read().then(function(data) {
for (var i = 0; i < data.items.length; i++) {
if (data.items[i].type == "text/plain") {
console.log("Your string: " + data.items[i].getAs("text/plain"))
} else {
console.error("No text/plain data on clipboard.");
}
}
})
For convenience, when reading text from the clipboard, readText()
can
be used.
navigator.clipboard.readText().then(function(data) {
console.log(data);
})
To catch if the read operation fails, a second function can be passed to the
then
as follows:
navigator.clipboard.readText().then(function(data) {
// Successful read.
console.log("Read from clipboard: " + data);
}, function() {
// Read failed.
console.log("Failed to read from clipboard");
})
This event fires whenever the clipboard contents are changed. If the clipboard contents are changed outside the browser, then this event fires when the browser regains focus.
Example of detecting clipboard changes:
function callback(event) {
// Do stuff with navigator.clipboard
}
navigator.clipboard.addEventListener("clipboardchange", callback);
Because of the potential for abuse, two permissions are defined that allow user agents to give use control over how the Async APIs are used.
The clipboard-write
permission controls access to the write methods.
The clipboard-read
permission controls access to the read methods and
the clipboardchange
event.
Normally, there is no need to request permission before using the APIs
because the appropriate permission will be checked whenever an API
method is invoked. If permission is denied by the user, then the returned
Promise<>
will be rejected.
The one case where permission must be explicitly requested is with the
clipboardchange
event. Because there is no API call for this event,
permission must have already been granted (either by requesting permission
explicitly or by calling one of the read methods).
Important
|
The exact method for explicitly requesting a permission is still an active discussion. See this permission bug. |
The current Clipboard API describes events that are fired when either:
-
The user selects one of the standard clipboard actions via the browser’s UI or keyboard shortcuts (these are "trusted" events), or
-
Javascript code sends one of these events (in which case, they are "synthetic" and "untrusted").
With this proposal, these events would still be present, but the recommended way
to access the clipboard would be through the Promise-based APIs rather than
via execCommand
(although the current execCommand
-based API would stick
around for compatibility reasons).
At least initially, the new permissions being introduced for the Async Clipboard API will not affect the operation of the existing clipboard APIs.
There are a few avenues for abuse that are not specific to the Async API, but are applicable to any API that provides clipboard access.
It is one of these abuse vectors in particular, copying images, that motivated the creation of the Async Clipboard API. In order to clean up malicious images, they would need to be transcoded and it is not appropriate to do this on the main thread (large images could lock the browser while the image is being processed).
Inject malicious content onto the clipboard.
Note, that it is already possible to clobber the clipboard contents:
document.addEventListener('copy', function(e) {
// Modify the document selection or call e.clipboardData.setData()
}
Sniffing the clipboard contents. Of concern not just because of the possibility of PII, but also because it is not uncommon for users to copy/paste passwords (e.g., from a password manager to a website).
Malicious text can be in the form of commands (e.g., 'rm -rf /\n') or script (Self-XSS).
Currently, user agents mitigate abuse by untrusted actions by either requiring a user gesture (e.g., clicking on a button) or with a permission dialog. These approaches suffer from the following issues:
User gestures provide defense against "drive-by" clipboard access, but the user receives no notifications if the clipboard is accessed as part of an unrelated user gesture. An example or this would be tricking user to click on innocous "OK" button and then silently writing to the clipboard. In this situation, the user grants no permission and receives no notification.
Pop-up permission dialogs can be problematic because clipboard events are
cancelable, so the browser needs to wait until the event handler is done (to know
whether or not it was canceled) before continuing. If the event handler
directly calls execCommand
(which is also synchronous), then the browser is
blocked until the command (including any permission dialogs) is complete.
Note that replacing execCommand
with an asychronous clipboard API would
make these permission dialogs more user-friendly.
To protect against abuse, implementers should consider some combination of the following:
-
Require a user gesture. To protect against drive-by access, although this may not be necessary with the right set of permissions.
-
Only allow clipboard access from code running in the front tab.
-
Pop-up Notifications. A post-facto notification similar to what is done for fullscreen. Display something like: "New data pasted to clipboard" or "Data read from clipboard".
-
Permission Dialog. Require the user to grant permission before accessing the clipboard.
Thanks to the following people for the discussions that lead to the creation of the original proposal:
Daniel Cheng (Google), Lucas Garron (Google), Gary Kacmarcik (Google), Hallvord R. M. Steen (Mozilla),