From 7d409cfcf2a898bb62dcbc67ea3afd22c9f2daf5 Mon Sep 17 00:00:00 2001 From: "Artem.Pichugin" Date: Wed, 23 Mar 2022 20:29:35 +0300 Subject: [PATCH 1/2] Update server clipboard on tab activation --- .../src/doc/docs/ij_user_guide/accessing.md | 8 ++++++- .../projector/client/web/ClipboardHandler.kt | 24 +++++++++++++++++++ .../client/web/window/WebWindowManager.kt | 6 +++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docSrc/src/doc/docs/ij_user_guide/accessing.md b/docSrc/src/doc/docs/ij_user_guide/accessing.md index d9867c9e0..a60f4874a 100644 --- a/docSrc/src/doc/docs/ij_user_guide/accessing.md +++ b/docSrc/src/doc/docs/ij_user_guide/accessing.md @@ -59,7 +59,13 @@ There are some limitations with clipboard. When your clipboard is changed on the client side, the server needs to apply the change on its side. -We implement it on the client side via setting ["paste" listener](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). So clipboard is updated on the server only if you invoke that listener, for example, by hitting Ctrl+V or Ctrl+Shift+V. **If you have an application on the server side with a "paste" button, a click on it can paste outdated information unless the listener wasn't invoked**. +We implement it on the client side by reading clipboard contents when the browser tab is activated, then sending to the server. +This allows a pretty seamless user experience. +This approach is tested in Firefox (requires `dom.events.testing.asyncClipboard` flag enabled in`about:config`) and Chromium based browsers. +Safari is not supported due to restrictions on clipboard access. +Also site requires secure context support. + +Fallback implementation uses ["paste" listener](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). So clipboard is updated on the server only if you invoke that listener, for example, by hitting Ctrl+V or Ctrl+Shift+V. **If you have an application on the server side with a "paste" button, a click on it can paste outdated information unless the listener wasn't invoked**. Unfortunately, we can't just continuously get clipboard data from [`window.navigator.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard) and send it to the server because when it's invoked not from user's context, there will be alert from the browser like "the site wants to read clipboard info, do you grant?". diff --git a/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/ClipboardHandler.kt b/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/ClipboardHandler.kt index e27c897a5..c795326e5 100644 --- a/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/ClipboardHandler.kt +++ b/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/ClipboardHandler.kt @@ -115,4 +115,28 @@ class ClipboardHandler(private val onCopyFailed: (ClientNotificationEvent) -> Un } } + companion object { + + private var lastClipboardString: String? = null + + /** + * Attempts to get clipboard contents, then passes it to the callback function if it's a new value. + * Restrictions: + * - Requires secure context + * - In firefox function `window.navigator.clipboard.readText` requires flag `dom.events.testing.asyncClipboard` in + * `about:config` to be enabled + * - Safari can only read clipboard from handlers of mouse press events and similar + */ + fun getNewClipboardString(callback: (String) -> Unit) { + if (isDefined(window.navigator.clipboard) && isDefined(js("window.navigator.clipboard.readText"))) { + window.navigator.clipboard.readText().then { + if (it != lastClipboardString) { + lastClipboardString = it + callback(it) + } + } + } + } + } + } diff --git a/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/window/WebWindowManager.kt b/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/window/WebWindowManager.kt index 20903d53a..78f92b096 100644 --- a/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/window/WebWindowManager.kt +++ b/projector-client-web/src/main/kotlin/org/jetbrains/projector/client/web/window/WebWindowManager.kt @@ -26,10 +26,12 @@ package org.jetbrains.projector.client.web.window import kotlinx.browser.window import org.jetbrains.projector.client.common.misc.ImageCacher import org.jetbrains.projector.client.common.window.WindowManager +import org.jetbrains.projector.client.web.ClipboardHandler import org.jetbrains.projector.client.web.state.ClientAction import org.jetbrains.projector.client.web.state.ClientStateMachine import org.jetbrains.projector.client.web.state.LafListener import org.jetbrains.projector.common.protocol.toClient.WindowData +import org.jetbrains.projector.common.protocol.toServer.ClientClipboardEvent import org.jetbrains.projector.common.protocol.toServer.ClientWindowsActivationEvent import org.jetbrains.projector.common.protocol.toServer.ClientWindowsDeactivationEvent import org.w3c.dom.HTMLCanvasElement @@ -52,6 +54,10 @@ class WebWindowManager(private val stateMachine: ClientStateMachine, override va private fun onActivated(@Suppress("UNUSED_PARAMETER") event: FocusEvent) { val windowIds = visibleWindows.map { it.id } stateMachine.fire(ClientAction.AddEvent(ClientWindowsActivationEvent(windowIds))) + + ClipboardHandler.getNewClipboardString { + stateMachine.fire(ClientAction.AddEvent(ClientClipboardEvent(it))) + } } // todo: remove SUPPRESS after KT-8112 is implemented or KTIJ-15401 is solved in some other way From dfc78117339216bf63182ed09a212d568e3f230d Mon Sep 17 00:00:00 2001 From: Art Pinch Date: Wed, 30 Mar 2022 18:10:14 +0300 Subject: [PATCH 2/2] Improve documentation --- docSrc/src/doc/docs/ij_user_guide/accessing.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docSrc/src/doc/docs/ij_user_guide/accessing.md b/docSrc/src/doc/docs/ij_user_guide/accessing.md index a60f4874a..228dccc63 100644 --- a/docSrc/src/doc/docs/ij_user_guide/accessing.md +++ b/docSrc/src/doc/docs/ij_user_guide/accessing.md @@ -65,7 +65,10 @@ This approach is tested in Firefox (requires `dom.events.testing.asyncClipboard` Safari is not supported due to restrictions on clipboard access. Also site requires secure context support. -Fallback implementation uses ["paste" listener](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). So clipboard is updated on the server only if you invoke that listener, for example, by hitting Ctrl+V or Ctrl+Shift+V. **If you have an application on the server side with a "paste" button, a click on it can paste outdated information unless the listener wasn't invoked**. +Main limitation of that current solution is inability to detect clipboard change if Projector tab is active and clipboard was changed +by some other app in background. + +So we also make use of ["paste" listener](https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event). Clipboard is updated on the server if you invoke that listener, for example, by hitting Ctrl+V or Ctrl+Shift+V. **If you have an application on the server side with a "paste" button, a click on it can paste outdated information unless the listener wasn't invoked**. Unfortunately, we can't just continuously get clipboard data from [`window.navigator.clipboard`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/clipboard) and send it to the server because when it's invoked not from user's context, there will be alert from the browser like "the site wants to read clipboard info, do you grant?".