diff --git a/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js b/guacamole/src/main/webapp/app/clipboard/services/clipboardService.js index f0529d8b8..07091f316 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 @@ -136,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); + + } }; @@ -167,7 +175,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'); @@ -176,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 @@ -399,53 +408,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.innerHTML = ''; + 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.textContent - })); - - } - - // 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; 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; }