Skip to content

Commit 86295a4

Browse files
authored
Wording adjustments + iOS case (#2)
* Wording adjustments + iOS case * Add mailto * Amend wording regarding relationship with clipboardchange * VDI use case - example * Format README with line wrapping * correct security & privacy href
1 parent 24eac3b commit 86295a4

File tree

2 files changed

+91
-38
lines changed

2 files changed

+91
-38
lines changed

.vscode/settings.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"prettier.printWidth": 120,
3+
"prettier.proseWrap": "always"
4+
}

README.md

+87-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
This proposal is an early design sketch by ChromeOS team to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.
1+
This proposal is an early design sketch by ChromeOS team to describe the problem below and solicit feedback on the
2+
proposed solution. It has not been approved to ship in Chrome.
23

34
# Explainer: `navigator.clipboard.contentsID()`
45

@@ -20,7 +21,7 @@ This proposal is an early design sketch by ChromeOS team to describe the problem
2021
- [Non-goals](#non-goals)
2122
- [Token stability across tabs or app windows](#token-stability-across-tabs-or-app-windows)
2223
- [How to use it?](#how-to-use-it)
23-
- [Security considerations](#security-considerations)
24+
- [Security & Privacy considerations](#security--privacy-considerations)
2425
- [Alternatives](#alternatives)
2526
- [Functionality itself](#functionality-itself)
2627
- [Format of the token](#format-of-the-token)
@@ -30,45 +31,70 @@ This proposal is an early design sketch by ChromeOS team to describe the problem
3031

3132
### Why a new thing, aren’t other clipboard APIs enough?
3233

33-
In short, without this there's no efficient way to detect clipboard changes.
34-
To elaborate, let's consider a common use case: Virtual Desktop Infrastructure (VDI). Many Clipboard API use cases within VDI environments center around synchronizing the local clipboard with a remote machine, so that:
34+
In short, without this there's no efficient way to detect clipboard changes. To elaborate, let's consider a common use
35+
case: Virtual Desktop Infrastructure (VDI). While connecting to a remote desktop using a web browser, users expect the
36+
experience between computers to be seamless. Some native applications on the remote side react to clipboard changes
37+
before the user explicitly pastes the data, so users are surprised when they copy something locally and the remote side
38+
doesn't react (for example, native paste button on the remote side is greyed out or yields stale content). Many
39+
Clipboard API use cases within VDI environments center around synchronizing the local clipboard with a remote machine,
40+
so that:
3541

36-
1. When a user copies something locally outside the VDI app and then switches to it, the new clipboard contents are seamlessly available in the remote session.
37-
2. When a user copies something on the remote machine and switches away from the VDI app, they can paste the copied content locally.
42+
1. When a user copies something locally outside the VDI app and then switches to it, the new clipboard contents are
43+
seamlessly available in the remote session.
44+
2. When a user copies something on the remote machine and switches away from the VDI app, they can paste the copied
45+
content locally.
3846

3947
Without `contentsID()`, there are two primary ways to achieve the first scenario:
4048

41-
* Upon refocusing the VDI app, automatically send the content from the local clipboard to the remote machine.
42-
* Upon refocusing the VDI app, read the clipboard contents, compare them with the last known state, and send to the remote machine only if they have changed.
49+
- Upon refocusing the VDI app, automatically send the content from the local clipboard to the remote machine.
50+
- Upon refocusing the VDI app, read the clipboard contents, compare them with the last known state, and send to the
51+
remote machine only if they have changed.
4352

44-
Neither of these approaches is optimal (especially with large clipboard contents), and additional challenges related to sanitization and encoding make it difficult to directly compare the clipboard contents byte-by-byte with previously received data.
53+
Neither of these approaches is optimal (especially with large clipboard contents), and additional challenges related to
54+
sanitization and encoding make it difficult to directly compare the clipboard contents byte-by-byte with previously
55+
received data.
4556

4657
### What is the optimal solution then?
4758

48-
Several platforms (ex. [MacOS](https://developer.apple.com/documentation/uikit/uipasteboard/1622103-changecount?language=objc), [Windows](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-addclipboardformatlistener), [X11](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/x/x11_clipboard_helper.cc;drc=d815f515138991af2aa5b1d07c64906fd8a7366b;bpv=1;bpt=1;l=68?gsn=SelectionChangeObserver&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%3Fpath%3Dui%2Fbase%2Fx%2Fx11_clipboard_helper.cc%238ndnC55hoYsX0PuoXruTyg4VFTFux3LU_qg9KPKIcTE) and [Wayland](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_data_device.cc;drc=d815f515138991af2aa5b1d07c64906fd8a7366b;bpv=1;bpt=1;l=182?gsn=OnSelection&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%3Fpath%3Dui%2Fozone%2Fplatform%2Fwayland%2Fhost%2Fwayland_data_device.cc%23KBIABXwYhD42mocIlezMjghFMtoChm0IKDja7p09J9o), [Android](https://developer.android.com/reference/android/content/ClipboardManager.OnPrimaryClipChangedListener)) offer efficient ways to track clipboard content changes without directly reading the data. This is often achieved through clipboard sequence numbers or change notifications. The `navigator.clipboard.contentsID()` API aims to leverage these capabilities. It allows websites to request a numeric token (a 128-bit integer) representing the current clipboard state. If this token differs from a previously retrieved one, it indicates that the clipboard contents have changed between the two calls. Importantly, this operation has a constant time complexity (O(1)), independent of the clipboard's size. Therefore, even frequent checks (e.g., on window refocus) remain efficient, even when dealing with large amounts of copied data.
59+
Several platforms (ex.
60+
[MacOS](https://developer.apple.com/documentation/uikit/uipasteboard/1622103-changecount?language=objc),
61+
[Windows](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-addclipboardformatlistener),
62+
[X11](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/x/x11_clipboard_helper.cc;drc=d815f515138991af2aa5b1d07c64906fd8a7366b;bpv=1;bpt=1;l=68?gsn=SelectionChangeObserver&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%3Fpath%3Dui%2Fbase%2Fx%2Fx11_clipboard_helper.cc%238ndnC55hoYsX0PuoXruTyg4VFTFux3LU_qg9KPKIcTE)
63+
and
64+
[Wayland](https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/wayland/host/wayland_data_device.cc;drc=d815f515138991af2aa5b1d07c64906fd8a7366b;bpv=1;bpt=1;l=182?gsn=OnSelection&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fchromium.googlesource.com%2Fcodesearch%2Fchromium%2Fsrc%2F%2Fmain%3Flang%3Dc%252B%252B%3Fpath%3Dui%2Fozone%2Fplatform%2Fwayland%2Fhost%2Fwayland_data_device.cc%23KBIABXwYhD42mocIlezMjghFMtoChm0IKDja7p09J9o),
65+
[Android](https://developer.android.com/reference/android/content/ClipboardManager.OnPrimaryClipChangedListener) and
66+
[iOS](http://go/appledoc/uikit/uipasteboard/changecount)) offer efficient ways to track clipboard content changes
67+
without directly reading the data. This is often achieved through clipboard sequence numbers or change notifications.
68+
The `navigator.clipboard.contentsID()` API aims to leverage these capabilities. It allows websites to request a numeric
69+
token (a 128-bit integer) representing the current clipboard state. If this token differs from a previously retrieved
70+
one, it indicates that the clipboard contents have changed between the two calls. Importantly, this operation has a
71+
constant time complexity (O(1)), independent of the clipboard's size. Therefore, even frequent checks (e.g., on window
72+
refocus) remain efficient, even when dealing with large amounts of copied data.
4973

5074
## Goals
5175

52-
* Provide a way to check if the clipboard changed between two points in time that is:
53-
* Easy to use
54-
* Efficient, no matter how big the clipboard contents are
55-
* Usable across multiple windows/tabs under one browser process
56-
* Improve potential current heuristics for clipboard synchronization…
76+
- Provide a way to check if the clipboard changed between two points in time that is:
77+
- Easy to use
78+
- Efficient, no matter how big the clipboard contents are
79+
- Usable across multiple windows/tabs under one browser process
80+
- Improve potential current heuristics for clipboard synchronization…
5781

5882
## Non-goals
5983

60-
* …without providing a new fingerprinting surface.
84+
- …without providing a new fingerprinting surface.
6185

6286
## Token stability across tabs or app windows
6387

64-
One of the goals of this API is to enable cross-app synchronization of clipboard \- so this should be as close to the stability of the clipboard itself as possible. So, every site under the same browser process should get the same token from calling `contentsID()`.
88+
One of the goals of this API is to enable cross-app synchronization of clipboard \- so this should be as close to the
89+
stability of the clipboard itself as possible. So, every site under the same browser process should get the same token
90+
from calling `contentsID()`.
6591

6692
## How to use it?
6793

6894
Frankly, quite straightforwardly. Signature of the method will look somewhat like this:
6995

7096
```javascript
71-
Promise<BigInt> contentsID();
97+
Promise < BigInt > contentsID();
7298
```
7399

74100
So in the mentioned VDI case, the code could look somewhat like this:
@@ -79,7 +105,7 @@ var lastToken = null;
79105
// Handler called on every window refocus.
80106
// It checks if it's necessary to sync clipboard contents to remote.
81107
window.addEventListener("focus", () => {
82-
navigator.clipboard.contentsID().then(token => {
108+
navigator.clipboard.contentsID().then((token) => {
83109
if (token !== lastToken) {
84110
// Clipboard contents have changed!
85111
// Send to remote machine
@@ -95,32 +121,52 @@ async function onRemoteClipboardChanged(remoteClipboardItems) {
95121
}
96122
```
97123

98-
Then, all that remains is to call `onRemoteClipboardChanged` every time the clipboard changes remotely \- and provided that no changes occur locally while the window is in focus (which is usually the case, as clipboard changes mostly occur due to user actions \- especially in case of local clipboard and VDI), clipboard synchronization will look seamless.
99-
In the unfortunate case of anticipated local changes to the clipboard done in the background, this can be improved in two ways:
124+
Then, all that remains is to call `onRemoteClipboardChanged` every time the clipboard changes remotely \- and provided
125+
that no changes occur locally while the window is in focus (which is usually the case, as clipboard changes mostly occur
126+
due to user actions \- especially in case of local clipboard and VDI), clipboard synchronization will look seamless. In
127+
the unfortunate case of anticipated local changes to the clipboard done in the background, this can be improved in two
128+
ways:
100129

101-
* Regular polling of the token and invoking a similar handler to the `focus` handler in the snippet above: this is generally not the best solution, but this API should be lightweight enough that it doesn’t create much overhead.
102-
* Integrating this with `clipboardchange` event in addition (or instead) or the `focus` event: this depends on whether `clipboardchange` event becomes a part of the web standard.
130+
- Regular polling of the token and invoking a similar handler to the `focus` handler in the snippet above: this is
131+
generally not the best solution, but this API should be lightweight enough that it doesn’t create much overhead.
132+
- Integrating this with `clipboardchange` event in addition (or instead) or the `focus` event: this depends on whether
133+
`clipboardchange` event becomes a part of the web standard. This API's design - or the particular implementation -
134+
will need to be integrated with the `clipboardchange` design to ensure it isn't delivered between writing to the
135+
clipboard and updating the last-known token value.
103136

104-
Both however would require some synchronization of the handler and `onRemoteClipboardChanged` to prevent handlers getting between `write` and `contentsID`.
137+
Both however would require some synchronization of the handler and `onRemoteClipboardChanged` to prevent handlers
138+
getting between `write` and `contentsID`.
105139

106-
**Note:** In any case, this will be in some degree prone to inherent race conditions due to lack of clipboard atomic operations \- which will show themselves mostly in case of user switching apps very rapidly. This API exists in order to enable heuristics to make this invisible in most cases, but will not fix it completely.
140+
**Note:** In any case, this will be in some degree prone to inherent race conditions due to lack of clipboard atomic
141+
operations \- which will show themselves mostly in case of user switching apps very rapidly. This API exists in order to
142+
enable heuristics to make this invisible in most cases, but will not fix it completely.
107143

108-
## Security considerations
144+
## Security & Privacy considerations
109145

110146
This should be under the same restrictions as the `navigator.clipboard.read()`:
111147

112-
* It should require `clipboard-read` permissions and request them on call.
113-
* It should be available only while the tab has focus.
148+
- It should require `clipboard-read` permissions and request them on call.
149+
- It should be available only while the tab has focus.
114150

115-
Thus, it doesn’t expose any new not-available-before security-sensitive information.
116-
The only potential attack vector would be correlating different sessions with the same user based on the token, which provides a more precise way of ensuring across sessions that those to clipboards are in fact the same user. In practice however, this could be done by just re-reading the clipboard contents and comparing them, especially across changes \- which is possible already. Correlating users across sites by the origins that have clipboard permissions is already trivially easy and existence of this API does not change this state significantly.
151+
Thus, it doesn’t expose any new not-available-before security-sensitive information. The only potential attack vector
152+
would be correlating different sessions with the same user based on the token, which provides a more precise way of
153+
ensuring across sessions that those to clipboards are in fact the same user. In practice however, this could be done by
154+
just re-reading the clipboard contents and comparing them, especially across changes \- which is possible already.
155+
Correlating users across sites by the origins that have clipboard permissions is already trivially easy and existence of
156+
this API does not change this state significantly.
117157

118158
## Alternatives
119159

120160
### Functionality itself
121161

122-
There is another proposed API for tracking clipboard changes \- a `clipboardchange` event. However, even if implemented and standardized, it operates differently. Instead of determining if a change has occurred between two points in time, it provides real-time notifications for every change, without detailed information about the cause. Therefore, if your app also writes to the clipboard, it can be challenging to determine whether you or another source caused the change (especially with multiple windows/tabs of the same app open), potentially leading to unnecessary data transfers or having to implement comparison anyway.
123-
In case of `contentsID()`, you can save the new token just after writing \- and it will be irrelevant for all active tabs/windows irrelevant what caused the change, only that this change is already in sync with the remote and no action is needed.
162+
There is another proposed API for tracking clipboard changes \- a `clipboardchange` event. However, even if implemented
163+
and standardized, it operates differently. Instead of determining if a change has occurred between two points in time,
164+
it provides real-time notifications for every change, without detailed information about the cause. Therefore, if your
165+
app also writes to the clipboard, it can be challenging to determine whether you or another source caused the change
166+
(especially with multiple windows/tabs of the same app open), potentially leading to unnecessary data transfers or
167+
having to implement comparison anyway. In case of `contentsID()`, you can save the new token just after writing \- and
168+
it will be irrelevant for all active tabs/windows irrelevant what caused the change, only that this change is already in
169+
sync with the remote and no action is needed.
124170

125171
### Format of the token
126172

@@ -129,15 +175,17 @@ There are several ways in which the token could look like, including:
129175
1. Sequence number that would increase with each change (or with each call that detected a change)
130176
2. Timestamp of the last change (or call that detected it)
131177
3. Hash of the clipboard contents
132-
4. Random 128-bit number without any specified scheme or significance \- other than “after something is written to the clipboard, `contentsID()` should yield a different value than it did before the write”
178+
4. Random 128-bit number without any specified scheme or significance \- other than “after something is written to the
179+
clipboard, `contentsID()` should yield a different value than it did before the write”
133180

134181
Preferred approach is 4, for the following reasons:
135182

136-
* It doesn’t provide any information about the user’s action other than already available
137-
* Randomness of this degree is enough to ensure the lack of false positives, conforming with [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) standards
138-
* It’s implementationally and computationally the simplest
139-
* It’s the simplest solution that is sufficient for the provided use case
140-
* It’s trivial to compare and store
183+
- It doesn’t provide any information about the user’s action other than already available
184+
- Randomness of this degree is enough to ensure the lack of false positives, conforming with
185+
[UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) standards
186+
- It’s implementationally and computationally the simplest
187+
- It’s the simplest solution that is sufficient for the provided use case
188+
- It’s trivial to compare and store
141189

142190
## References & acknowledgements
143191

@@ -146,4 +194,5 @@ Many thanks for valuable feedback and advice from:
146194
- [Andrew Rayskiy](mailto:[email protected])
147195
- [Ayu Ishii](mailto:[email protected])
148196
- [Dominik Bylica](mailto:[email protected])
197+
- [Jeffrey Yasskin](mailto:[email protected])
149198
- [Robert Ferens](mailto:[email protected])

0 commit comments

Comments
 (0)