2d7e2e192ffe1d44e13badf0d68b9236730192fc58bec7b544846e5ea750b74ccb32232ddb9c3472edad886211f6167a130149e78d920ea29e2b6265a2436f 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { isSafari, isWebkitWebView } from '../../../base/browser/browser.js';
  24. import { $, addDisposableListener } from '../../../base/browser/dom.js';
  25. import { DeferredPromise } from '../../../base/common/async.js';
  26. import { Disposable } from '../../../base/common/lifecycle.js';
  27. import { ILayoutService } from '../../layout/browser/layoutService.js';
  28. import { ILogService } from '../../log/common/log.js';
  29. let BrowserClipboardService = class BrowserClipboardService extends Disposable {
  30. constructor(layoutService, logService) {
  31. super();
  32. this.layoutService = layoutService;
  33. this.logService = logService;
  34. this.mapTextToType = new Map(); // unsupported in web (only in-memory)
  35. this.findText = ''; // unsupported in web (only in-memory)
  36. this.resources = [];
  37. if (isSafari || isWebkitWebView) {
  38. this.installWebKitWriteTextWorkaround();
  39. }
  40. }
  41. // In Safari, it has the following note:
  42. //
  43. // "The request to write to the clipboard must be triggered during a user gesture.
  44. // A call to clipboard.write or clipboard.writeText outside the scope of a user
  45. // gesture(such as "click" or "touch" event handlers) will result in the immediate
  46. // rejection of the promise returned by the API call."
  47. // From: https://webkit.org/blog/10855/async-clipboard-api/
  48. //
  49. // Since extensions run in a web worker, and handle gestures in an asynchronous way,
  50. // they are not classified by Safari as "in response to a user gesture" and will reject.
  51. //
  52. // This function sets up some handlers to work around that behavior.
  53. installWebKitWriteTextWorkaround() {
  54. const handler = () => {
  55. const currentWritePromise = new DeferredPromise();
  56. // Cancel the previous promise since we just created a new one in response to this new event
  57. if (this.webKitPendingClipboardWritePromise && !this.webKitPendingClipboardWritePromise.isSettled) {
  58. this.webKitPendingClipboardWritePromise.cancel();
  59. }
  60. this.webKitPendingClipboardWritePromise = currentWritePromise;
  61. // The ctor of ClipboardItem allows you to pass in a promise that will resolve to a string.
  62. // This allows us to pass in a Promise that will either be cancelled by another event or
  63. // resolved with the contents of the first call to this.writeText.
  64. // see https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#parameters
  65. navigator.clipboard.write([new ClipboardItem({
  66. 'text/plain': currentWritePromise.p,
  67. })]).catch((err) => __awaiter(this, void 0, void 0, function* () {
  68. if (!(err instanceof Error) || err.name !== 'NotAllowedError' || !currentWritePromise.isRejected) {
  69. this.logService.error(err);
  70. }
  71. }));
  72. };
  73. if (this.layoutService.hasContainer) {
  74. this._register(addDisposableListener(this.layoutService.container, 'click', handler));
  75. this._register(addDisposableListener(this.layoutService.container, 'keydown', handler));
  76. }
  77. }
  78. writeText(text, type) {
  79. return __awaiter(this, void 0, void 0, function* () {
  80. // With type: only in-memory is supported
  81. if (type) {
  82. this.mapTextToType.set(type, text);
  83. return;
  84. }
  85. if (this.webKitPendingClipboardWritePromise) {
  86. // For Safari, we complete this Promise which allows the call to `navigator.clipboard.write()`
  87. // above to resolve and successfully copy to the clipboard. If we let this continue, Safari
  88. // would throw an error because this call stack doesn't appear to originate from a user gesture.
  89. return this.webKitPendingClipboardWritePromise.complete(text);
  90. }
  91. // Guard access to navigator.clipboard with try/catch
  92. // as we have seen DOMExceptions in certain browsers
  93. // due to security policies.
  94. try {
  95. return yield navigator.clipboard.writeText(text);
  96. }
  97. catch (error) {
  98. console.error(error);
  99. }
  100. // Fallback to textarea and execCommand solution
  101. const activeElement = document.activeElement;
  102. const textArea = document.body.appendChild($('textarea', { 'aria-hidden': true }));
  103. textArea.style.height = '1px';
  104. textArea.style.width = '1px';
  105. textArea.style.position = 'absolute';
  106. textArea.value = text;
  107. textArea.focus();
  108. textArea.select();
  109. document.execCommand('copy');
  110. if (activeElement instanceof HTMLElement) {
  111. activeElement.focus();
  112. }
  113. document.body.removeChild(textArea);
  114. return;
  115. });
  116. }
  117. readText(type) {
  118. return __awaiter(this, void 0, void 0, function* () {
  119. // With type: only in-memory is supported
  120. if (type) {
  121. return this.mapTextToType.get(type) || '';
  122. }
  123. // Guard access to navigator.clipboard with try/catch
  124. // as we have seen DOMExceptions in certain browsers
  125. // due to security policies.
  126. try {
  127. return yield navigator.clipboard.readText();
  128. }
  129. catch (error) {
  130. console.error(error);
  131. return '';
  132. }
  133. });
  134. }
  135. readFindText() {
  136. return __awaiter(this, void 0, void 0, function* () {
  137. return this.findText;
  138. });
  139. }
  140. writeFindText(text) {
  141. return __awaiter(this, void 0, void 0, function* () {
  142. this.findText = text;
  143. });
  144. }
  145. readResources() {
  146. return __awaiter(this, void 0, void 0, function* () {
  147. return this.resources;
  148. });
  149. }
  150. };
  151. BrowserClipboardService = __decorate([
  152. __param(0, ILayoutService),
  153. __param(1, ILogService)
  154. ], BrowserClipboardService);
  155. export { BrowserClipboardService };