From 535b70fdce8ff5df2405e23b30897f65ecda66bf Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 29 Aug 2017 11:38:05 -0700 Subject: [PATCH 1/4] GUACAMOLE-310: Switch clipboard service back to using textarea for contents. --- .../webapp/app/clipboard/services/clipboardService.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index f0529d8b8..ff626ea4f 100644 --- a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js @@ -57,10 +57,9 @@ angular.module('clipboard').factory('clipboardService', ['$injector', * * @type Element */ - var clipboardContent = document.createElement('div'); + var clipboardContent = document.createElement('textarea'); // Ensure clipboard target is selectable but not visible - clipboardContent.setAttribute('contenteditable', 'true'); clipboardContent.className = 'clipboard-service-target'; // Add clipboard target to DOM @@ -167,7 +166,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', // Copy the given value into the clipboard DOM element if (typeof data.data === 'string') - clipboardContent.textContent = data.data; + clipboardContent.value = data.data; else { clipboardContent.innerHTML = ''; var img = document.createElement('img'); @@ -400,7 +399,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', pushSelection(); // Clear and select the clipboard DOM element - clipboardContent.innerHTML = ''; + clipboardContent.value = ''; clipboardContent.focus(); selectAll(clipboardContent); @@ -431,7 +430,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', else deferred.resolve(new ClipboardData({ type : 'text/plain', - data : clipboardContent.textContent + data : clipboardContent.value })); } From cc22f23c027bc4dd688eb71372c90a7b31e71e40 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 29 Aug 2017 11:39:08 -0700 Subject: [PATCH 2/4] GUACAMOLE-310: Ensure clipboard service target remains hidden (1x1 px may be below browser's minimum dimensions for a textarea). --- .../src/main/webapp/app/clipboard/styles/clipboard.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css b/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css index b4513e1b0..3d61c2be3 100644 --- a/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css +++ b/guacamole/src/main/webapp/app/clipboard/styles/clipboard.css @@ -52,10 +52,10 @@ .clipboard-service-target { position: fixed; - left: -1px; - right: -1px; - width: 1px; - height: 1px; + left: -1em; + right: -1em; + width: 1em; + height: 1em; white-space: pre; overflow: hidden; } From 47acaf5b41c911d402a9f242cc58619bc74c4cb9 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 29 Aug 2017 11:39:49 -0700 Subject: [PATCH 3/4] GUACAMOLE-310: Wait until clipboard target is actually focused before attempting paste. --- .../clipboard/services/clipboardService.js | 108 +++++++++++------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index ff626ea4f..311dbc0ba 100644 --- a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js @@ -398,53 +398,81 @@ angular.module('clipboard').factory('clipboardService', ['$injector', var originalElement = document.activeElement; pushSelection(); + /** + * Attempts to paste the clipboard contents into the + * currently-focused element. The promise related to the current + * attempt to read the clipboard will be resolved or rejected + * depending on whether the attempt to paste succeeds. + */ + var performPaste = function performPaste() { + + // Attempt paste local clipboard into clipboard DOM element + if (document.execCommand('paste')) { + + // If the pasted data is a single image, resolve with a blob + // containing that image + var currentImage = service.getImageContent(clipboardContent); + if (currentImage) { + + // Convert the image's data URL into a blob + var blob = service.parseDataURL(currentImage); + if (blob) { + deferred.resolve(new ClipboardData({ + type : blob.type, + data : blob + })); + } + + // Reject if conversion fails + else + deferred.reject(); + + } // end if clipboard is an image + + // Otherwise, assume the clipboard contains plain text + else + deferred.resolve(new ClipboardData({ + type : 'text/plain', + data : clipboardContent.value + })); + + } + + // Otherwise, reading from the clipboard has failed + else + deferred.reject(); + + }; + + // Clean up event listener and selection once the paste attempt has + // completed + deferred.promise['finally'](function cleanupReadAttempt() { + + // Do not use future changes in focus + clipboardContent.removeEventListener('focus', performPaste); + + // Unfocus the clipboard DOM event to avoid mobile keyboard opening, + // restoring whichever element was originally focused + clipboardContent.blur(); + originalElement.focus(); + popSelection(); + + }); + + // Ensure clipboard element is blurred (and that the "focus" event + // will fire) + clipboardContent.blur(); + clipboardContent.addEventListener('focus', performPaste); + // Clear and select the clipboard DOM element clipboardContent.value = ''; clipboardContent.focus(); selectAll(clipboardContent); - // Attempt paste local clipboard into clipboard DOM element - if (document.activeElement === clipboardContent && document.execCommand('paste')) { - - // If the pasted data is a single image, resolve with a blob - // containing that image - var currentImage = service.getImageContent(clipboardContent); - if (currentImage) { - - // Convert the image's data URL into a blob - var blob = service.parseDataURL(currentImage); - if (blob) { - deferred.resolve(new ClipboardData({ - type : blob.type, - data : blob - })); - } - - // Reject if conversion fails - else - deferred.reject(); - - } // end if clipboard is an image - - // Otherwise, assume the clipboard contains plain text - else - deferred.resolve(new ClipboardData({ - type : 'text/plain', - data : clipboardContent.value - })); - - } - - // Otherwise, reading from the clipboard has failed - else + // If focus failed to be set, we cannot read the clipboard + if (document.activeElement !== clipboardContent) deferred.reject(); - // Unfocus the clipboard DOM event to avoid mobile keyboard opening, - // restoring whichever element was originally focused - clipboardContent.blur(); - originalElement.focus(); - popSelection(); - }, CLIPBOARD_READ_DELAY); return deferred.promise; From 7e0cdd2adf156b6aa15ddf96a7e78a41c8f47c9a Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 3 Sep 2017 17:07:24 -0700 Subject: [PATCH 4/4] GUACAMOLE-310: Use input element select() function when available. --- .../clipboard/services/clipboardService.js | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index 311dbc0ba..07091f316 100644 --- a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js +++ b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js @@ -135,14 +135,23 @@ angular.module('clipboard').factory('clipboardService', ['$injector', */ var selectAll = function selectAll(element) { - // Generate a range which selects all nodes within the given element - var range = document.createRange(); - range.selectNodeContents(element); + // Use the select() function defined for input elements, if available + if (element.select) + element.select(); - // Replace any current selection with the generated range - var selection = $window.getSelection(); - selection.removeAllRanges(); - selection.addRange(range); + // Fallback to manual manipulation of the selection + else { + + // Generate a range which selects all nodes within the given element + var range = document.createRange(); + range.selectNodeContents(element); + + // Replace any current selection with the generated range + var selection = $window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + + } }; @@ -175,6 +184,7 @@ angular.module('clipboard').factory('clipboardService', ['$injector', } // Select all data within the clipboard target + clipboardContent.focus(); selectAll(clipboardContent); // Attempt to copy data from clipboard element into local clipboard