Skip to content

Latest commit

 

History

History
191 lines (124 loc) · 10.8 KB

Conclusions.md

File metadata and controls

191 lines (124 loc) · 10.8 KB

Copying

Test results

Platforms tested:

  • Chrome 61.0.3163.100 (macOS 10.13.0)
  • Safari 11.0 (macOS 10.13)
  • Safari 11.0 (iOS 11.0 on an iPhone SE)
  • Edge 15.15063 (Windows 10.0 in a VirtualBox VM)
  • Firefox 54.0 (macOS 10.13)
Chrome 61 Safari 11 (macOS) Safari 11 (iOS) Edge 15 Firefox 54
supported always returns true †
enabled without selection returns true †
exec works without selection † ⚠️¹ ⚠️¹
enabled with selection returns true †
exec works with selection †
exec fails outside user gesture
setData() in listener works ❌ ²
getData() in listener shows if setData() worked ⚠️ ² ❌ ³
Copies all types set with setData() ❌ ⁴
exec reports success correctly ⚠️ ² ❌ ⁵
contenteditable does not break document selection
user-select: none does not break document selection ✅(Cr 64) ✅(Edge 16) ✅ (FF 57)
Can construct new DataTransfer()
Writes CF_HTML on Windows N/A N/A ❌⁶

† Here, we are only specifically interested in the case where the handler is called directly in response to a user gesture. I didn't test for behaviour when there is no user gesture.

supported always returns true

In all browsers, document.queryCommandSupported("copy") always returns true.

enabled without selection returns true (see issue 1 below)

When nothing on the page is selected, document.queryCommandEnabled("copy") returns true in Firefox, but not any other browsers.

exec fires listener without selection

On all platforms, document.execCommand("copy") always works (triggers a copy) during a user gesture, regardless of whether anything on the page is selected. However, on Safari listeners registered using document.addEventListener("copy") don't fire (and therefore don't have an opportunity to set the data on the clipboard) if there is no selection.

enabled with selection returns true

On all browsers, document.queryCommandEnabled("copy") returns true during a user gesture if some part of the page is selected (doesn't matter which part; can be the entire body or a single element). The selection may be made using Javascript during the user gesture handler itself.

exec fires listener with selection

On all platforms, document.execCommand("copy") works during a user gesture, regardless of whether anything on the page is selected. Listeners registered with document.addEventListener("copy") fire.

enabled returns false outside user gesture

In all browsers, document.execCommand("copy") fails when there is no user gesture, and returns false.

setData() works in listener (see issue 3 and issue 4 below)

This means that the following works:

document.addEventListener("copy", function(e) {
  e.clipboardData.setData("text/plain", "plain text")
  e.preventDefault();
});

On iOS, the setData call doesn't work – it actually empties the clipboard (at least for that data type). This is supposedly fixed in WebKit as of September 19, 2017: https://bugs.webkit.org/show_bug.cgi?id=177715 Fortunately, it is possible to detect Safari's behaviour (when the value is not empty), because the following returns "" even after the setData() call:

  e.clipboardData.getData("text/plain", "plain text")

getData() in listener shows if setData() worked

In Edge, setData() works inside the copy listener, but getData() never reports the data that was set, and returns the empty string instead.

Note that on iOS Safari, getData() also returns the empty string, but since setData() doesn't work, this is the correct return value (and can be used to detect if setting a non-empty string succeeded).

Copies all types set with setData() (see issue 2 below)

This means that the following listeners put both plain text and HTML on the clipboard:

document.addEventListener("copy", function(e) {
  e.clipboardData.setData("text/plain", "plain text")
  e.clipboardData.setData("text/html", "<b>markup</b> text")
  e.preventDefault();
});

document.addEventListener("copy", function(e) {
  e.clipboardData.setData("text/html", "<b>markup</b> text")
  e.clipboardData.setData("text/plain", "plain text")
  e.preventDefault();
});

Edge only places the last provided data type on the clipboard.

exec reports success correctly (see issue 5 below)

Most platforms correctly report if document.execCommand("copy") successfully copied something to the clipboard.

On iOS, document.execCommand("copy") also returns true when event.clipboardData.setData() clears the clipboard. In this case, the clipboard is set to empty, but the return value is arguably correct once we account for the relevant bug.

Edge, however, always returns false. Even when the copy attempt succeeds.

contenteditable does not break document seleciton

Consider the following code:

var sel = document.getSelection();
var range = document.createRange();
range.selectNodeContents(document.body);
sel.addRange(range);

This fails in Chrome and Safari if the last content in the DOM is the following:

<div contenteditable="true" class="editable"></div>

user-select: none does not break document selection

In Safari, the DOM selection API does not allow Javascript to select parts of the DOM that are not selectable by the user due to -webkit-user-select: none.

Reported at #75

As a workaround for Safari, it is possible to select an element nested unside an unselectable element that explicitly uses -webkit-user-select: text to enable selection. It seems that we should be able to rely on this, since it is the specified behaviour. However, note that other browsers (e.g. Firefox <21) have implemented behaviour that doesn't match the spec.

Writes CF_HTML on Windows

In Edge 16 and earlier, clipboardData.setData("text/html", data) does not properly write HTML to the clipbard in the Windows CF_HTML clipboard format.

Reported at #73

Can construct new DataTransfer() (see issue 6 below)

The new asynchronous clipboard API takes a DataTranfer input. However, the only browser in which you can call the DataTransfer constructor is Chrome. (The constructor was made publicly callable specifically for the async clipboard API.)

Strategy

Firstly:

  • Issue 1: queryCommandEnabled() doesn't tell us when copying will work.
    • Workaround: Don't consult queryCommandEnabled(); just try execCommand() every time.

All platforms except iOS can share the same default implementation. However:

  • Issue 2: Edge will only put the last provided data type on the clipboard.
    • Workaround: File a bug against Edge. (Started: Edge Bug #14080506)
    • Document that the caller should add the most important data type to the copy data last.

TODO: Add "Safari doesn't trigger listener without selection" issue.

iOS Safari requires the trickiest fallback:

  • Issue 3: For iOS Safari, it seems we can't attach data types in the listener at all.
    • Workaround: Detect the issue, and fall back to copying the text/plain data type with a different mechanism.
    • Document that callers should always provide a text/plain data type if they want copying to work on iOS.

The logic will be as follows:

  • Is there a text/plain data type in the input?
    • No? ⇒ No fallback. Clipboard will likely end up blank on iOS. (Consider warning the user if they don't provide a value for the text/plain data type.)
    • Yes? ⇒ Check setData() against getData() for the text/plain data type. Do they match?
      • Yes? ⇒ Do nothing. (This will result in a blank clipboard when the copied string is empty.)
      • No? ⇒ Fall back.

We fall back creating a temporary DOM element, assigning the text/plain value to it using textContent, selecting it using Javascript, and triggering execCommand("copy") again. (The repeated copy command appears to work on iOS.) We will place the element within a shadow root in order to prevent outside formatting (e.g. page background color) from affecting the text, and use white-space: pre-wrap to preserve newlines and whitespace. However:

  • Issue 4: On iOS, the copied text will still have the explicit formatting style of the default text in shadow root (issue 3)
    • Workaround: none.
    • Document this.

The Windows problem looks a bit annoying.

On Windows, we perform the copy, but we will always get back false.

  • Issue 5: On Windows, execCommand("copy") always returns false.
    • Workaround 0: Report this bug to Edge, and hope they fix it. (Started: Edge Bug #14080262)
    • Workaround 1: Pass on the return value blindly, and document that Windows has a bug.
    • Workaround 2: Never check the return value of execCommand("copy")
    • Workaround 3: Detect Edge using a different mechanism (e.g. UA sniffing), and ignore the return value only when we think we're in Edge.

We also need to add some more polyfilling than we might like:

  • Issue 6: The caller can't construct a DataTransfer to pass to the polyfill on any platform except Chrome.

    • Workaround: Provide an object with a sufficiently ergonomic subset of the interface of DataTransfer that the caller can use. (We can swap out the implementation with DataTransfer once platforms allow calling the constructor directly.)
  • Issue 7: Internet Explorer did its own thing.

    • Workaround: old implementation using window.clipboardData. Requires a Promise polyfill. :-/