Clipboard.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. 'use strict';
  2. var getWindow = require('../misc/getWindow.js');
  3. var Blob = require('./Blob.js');
  4. var DataTransfer = require('./DataTransfer.js');
  5. // Clipboard is not available in jsdom
  6. function _define_property(obj, key, value) {
  7. if (key in obj) {
  8. Object.defineProperty(obj, key, {
  9. value: value,
  10. enumerable: true,
  11. configurable: true,
  12. writable: true
  13. });
  14. } else {
  15. obj[key] = value;
  16. }
  17. return obj;
  18. }
  19. // MDN lists string|Blob|Promise<Blob|string> as possible types in ClipboardItemData
  20. // lib.dom.d.ts lists only Promise<Blob|string>
  21. // https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem/ClipboardItem#syntax
  22. function createClipboardItem(window, ...blobs) {
  23. const dataMap = Object.fromEntries(blobs.map((b)=>[
  24. typeof b === 'string' ? 'text/plain' : b.type,
  25. Promise.resolve(b)
  26. ]));
  27. // use real ClipboardItem if available
  28. /* istanbul ignore if */ if (typeof window.ClipboardItem !== 'undefined') {
  29. return new window.ClipboardItem(dataMap);
  30. }
  31. return new class ClipboardItem {
  32. get types() {
  33. return Array.from(Object.keys(this.data));
  34. }
  35. async getType(type) {
  36. const value = await this.data[type];
  37. if (!value) {
  38. throw new Error(`${type} is not one of the available MIME types on this item.`);
  39. }
  40. return value instanceof window.Blob ? value : new window.Blob([
  41. value
  42. ], {
  43. type
  44. });
  45. }
  46. constructor(d){
  47. _define_property(this, "data", undefined);
  48. this.data = d;
  49. }
  50. }(dataMap);
  51. }
  52. const ClipboardStubControl = Symbol('Manage ClipboardSub');
  53. function createClipboardStub(window, control) {
  54. return Object.assign(new class Clipboard extends window.EventTarget {
  55. async read() {
  56. return Array.from(this.items);
  57. }
  58. async readText() {
  59. let text = '';
  60. for (const item of this.items){
  61. const type = item.types.includes('text/plain') ? 'text/plain' : item.types.find((t)=>t.startsWith('text/'));
  62. if (type) {
  63. text += await item.getType(type).then((b)=>Blob.readBlobText(b, window.FileReader));
  64. }
  65. }
  66. return text;
  67. }
  68. async write(data) {
  69. this.items = data;
  70. }
  71. async writeText(text) {
  72. this.items = [
  73. createClipboardItem(window, text)
  74. ];
  75. }
  76. constructor(...args){
  77. super(...args), _define_property(this, "items", []);
  78. }
  79. }(), {
  80. [ClipboardStubControl]: control
  81. });
  82. }
  83. function isClipboardStub(clipboard) {
  84. return !!(clipboard === null || clipboard === undefined ? undefined : clipboard[ClipboardStubControl]);
  85. }
  86. function attachClipboardStubToView(window) {
  87. if (isClipboardStub(window.navigator.clipboard)) {
  88. return window.navigator.clipboard[ClipboardStubControl];
  89. }
  90. const realClipboard = Object.getOwnPropertyDescriptor(window.navigator, 'clipboard');
  91. let stub;
  92. const control = {
  93. resetClipboardStub: ()=>{
  94. stub = createClipboardStub(window, control);
  95. },
  96. detachClipboardStub: ()=>{
  97. /* istanbul ignore if */ if (realClipboard) {
  98. Object.defineProperty(window.navigator, 'clipboard', realClipboard);
  99. } else {
  100. Object.defineProperty(window.navigator, 'clipboard', {
  101. value: undefined,
  102. configurable: true
  103. });
  104. }
  105. }
  106. };
  107. stub = createClipboardStub(window, control);
  108. Object.defineProperty(window.navigator, 'clipboard', {
  109. get: ()=>stub,
  110. configurable: true
  111. });
  112. return stub[ClipboardStubControl];
  113. }
  114. function resetClipboardStubOnView(window) {
  115. if (isClipboardStub(window.navigator.clipboard)) {
  116. window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
  117. }
  118. }
  119. function detachClipboardStubFromView(window) {
  120. if (isClipboardStub(window.navigator.clipboard)) {
  121. window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
  122. }
  123. }
  124. async function readDataTransferFromClipboard(document) {
  125. const window = document.defaultView;
  126. const clipboard = window === null || window === undefined ? undefined : window.navigator.clipboard;
  127. const items = clipboard && await clipboard.read();
  128. if (!items) {
  129. throw new Error('The Clipboard API is unavailable.');
  130. }
  131. const dt = DataTransfer.createDataTransfer(window);
  132. for (const item of items){
  133. for (const type of item.types){
  134. dt.setData(type, await item.getType(type).then((b)=>Blob.readBlobText(b, window.FileReader)));
  135. }
  136. }
  137. return dt;
  138. }
  139. async function writeDataTransferToClipboard(document, clipboardData) {
  140. const window = getWindow.getWindow(document);
  141. const clipboard = window.navigator.clipboard;
  142. const items = [];
  143. for(let i = 0; i < clipboardData.items.length; i++){
  144. const dtItem = clipboardData.items[i];
  145. const blob = await DataTransfer.getBlobFromDataTransferItem(window, dtItem);
  146. items.push(createClipboardItem(window, blob));
  147. }
  148. const written = clipboard && await clipboard.write(items).then(()=>true, // Can happen with other implementations that e.g. require permissions
  149. /* istanbul ignore next */ ()=>false);
  150. if (!written) {
  151. throw new Error('The Clipboard API is unavailable.');
  152. }
  153. }
  154. const g = globalThis;
  155. /* istanbul ignore else */ if (typeof g.afterEach === 'function') {
  156. g.afterEach(()=>resetClipboardStubOnView(globalThis.window));
  157. }
  158. /* istanbul ignore else */ if (typeof g.afterAll === 'function') {
  159. g.afterAll(()=>detachClipboardStubFromView(globalThis.window));
  160. }
  161. exports.attachClipboardStubToView = attachClipboardStubToView;
  162. exports.createClipboardItem = createClipboardItem;
  163. exports.detachClipboardStubFromView = detachClipboardStubFromView;
  164. exports.readDataTransferFromClipboard = readDataTransferFromClipboard;
  165. exports.resetClipboardStubOnView = resetClipboardStubOnView;
  166. exports.writeDataTransferToClipboard = writeDataTransferToClipboard;