Source: xxe/editor/ClipboardIntegration.js

/**
 * Used by {@link XMLEditor} to synchronize the contents of 
 * the local, system clipboard with the remote, server-side, 
 * private clipboard of the XML editor.
 */
class ClipboardIntegration {
    constructor(editor) {
        this._xmlEditor = editor;
        
        this._level = ClipboardIntegration.UNKNOWN;
        
        this._systemClipboardText = null;
    }

    getLevel() {
        if (this._level === ClipboardIntegration.UNKNOWN) {
            // The Clipboard API is available only in secure contexts.
            if (window.isSecureContext) {
                return ClipboardIntegration.doGetLevel(
                    [ ClipboardIntegration.NONE ])
                    .then((level) => {
                        this._level = level;
                        return Promise.resolve(level);
                    });
            } else {
                this._level = ClipboardIntegration.NONE;
                console.warn(`Current context not secure; \
SYSTEM CLIPBOARD INTEGRATION NOT AVAILABLE`);
                return Promise.resolve(this._level);
            }
        } else {
            return Promise.resolve(this._level);
        }
    }

    static doGetLevel(level) {
        return ClipboardIntegration.queryPermission("clipboard-write")
            .then((canWrite) => {
                if (canWrite.state === "denied") {
                  throw new Error('"clipboard-write" permission is denied');
                } else {
                  // When supported, the "clipboard-write" permission is
                  // granted automatically.
                  level[0] = ClipboardIntegration.CAN_WRITE;
                  return ClipboardIntegration.queryPermission("clipboard-read");
                }
            })
            .then((canRead) => {
                if (canRead.state === "denied") {
                    throw new Error('"clipboard-read" permission is denied');
                } else {
                    // May be "granted" or "prompt". Here we assume that the
                    // user will grant this permission.
                    level[0] = ClipboardIntegration.CAN_READ_WRITE;
                    return Promise.resolve(level[0]);
                }
            })
            .catch((error) => {
                console.warn(`${error}; \
SYSTEM CLIPBOARD INTEGRATION NOT AVAILABLE`);
                return Promise.resolve(level[0]);
            });
    }

    static queryPermission(name) {
        if (BROWSER_ENGINE_IS_GECKO) {
            // Firefox does not support permissions "clipboard-write" and
            // "clipboard-read".
            let state;
            if (name === "clipboard-write") {
                state = "granted";
            } else {
                // Firefox does not support read/readText normally even if
                // about:config, dom.events.asyncClipboard.read/readText is
                // set to true.
                state = "denied";
            }
            return Promise.resolve({ name: name, state: state });
        } else {
            return navigator.permissions.query({ name: name });
        }
    }
    
    // -----------------------------------
    // Invoked by DocumentView
    // -----------------------------------
    
    autoUpdatePrivateClipboard(docView) {
        if (this._level === ClipboardIntegration.CAN_READ_WRITE) {
            docView.addEventListener("focusin", (event) => {
                if (this._xmlEditor.documentIsOpened) {
                    this.updatePrivateClipboard(docView);
                }
            });

            // Some browsers like Brave, unlike Chrome, will refuse to read
            // the clipboard on a "focusin". They want a "user gesture" to do
            // that.

            docView.addEventListener("click", (event) => {
                if (this._xmlEditor.documentIsOpened &&
                    event.button === 0 && // primary button
                    event.detail === 1) { // clickCount
                    this.updatePrivateClipboard(docView);
                }
            }, true); // useCapture
        }
    }
    
    updatePrivateClipboard(docView) {
        // The question here is: is the readText() below reading a new text
        // regardless of what happens during its execution.
        // (See comment in readText() below about how
        // this._systemClipboardText could be changed by server-side
        // clipboardUpdated() during its execution.)
        
        const systemClipboardText = this._systemClipboardText;
        
        let hasHTML = [ false ];
        ClipboardIntegration.readText(ClipboardIntegration.MAX_READ_SIZE,
                                      hasHTML)
            .then((text) => {
                if (text && text !== systemClipboardText) {
                    this._systemClipboardText = text;

                    if (hasHTML[0]) {
                        text = ClipboardIntegration.HTML_MAGIC_STRING + text;
                    }
                    docView.sendSetClipboard(text);
                    // An editing context change should follow to confirm
                    // that text has been copied to the private clipboard.
                    //
                    // (This is why updatePrivateClipboard is invoked
                    // when xmlEditor.documentIsOpened and not just when
                    // xmlEditor.connected.)
                }
            });
    }

    static readText(maxReadSize, hasHTML) {
        // Not equivalent to navigator.clipboard.readText().
        // Server-side clipboardUpdated() may occur between
        // any of the .then() below.
        // In other words, this._systemClipboardText may be changed
        // by clipboardUpdated() during the execution of this function.
        
        hasHTML[0] = false;
        return navigator.clipboard.read()
            .then((items) => {
                let item = null;
                for (let i of items) {
                    const types = i.types;
                    if (types.includes("text/plain")) {
                        item = i;
                        if (types.includes("text/html")) {
                            hasHTML[0] = true;
                        }
                        // Done.
                        break;
                    }
                }
                
                return item;
            })
            .then((item) => {
                if (item === null) {
                    return null;
                } else {
                    return item.getType("text/plain");
                }
            })
            .then((blob) => {
                if (blob === null) {
                    return null;
                } else {
                    if (maxReadSize > 0 && blob.size > maxReadSize) {
                        throw new Error(`system clipboard content is \
too large (size=${blob.size}b > max=${maxReadSize}b)`);
                    }

                    return blob.text();
                }
            })
            .catch((error) => {
                // Generally a transient error related to focus or user
                // gesture, e.g. SecurityError, "must be handling a user
                // gesture to do that".
                const msg =
                      `Could not read text from the system clipboard: ${error}`;
                if (error.name === "SecurityError") {
                    console.warn(msg);
                } else {
                    console.error(msg);
                }
                return null;
            });
    }
    
    // -----------------------------------
    // Invoked by XMLEditor
    // -----------------------------------
    
    clipboardUpdated(clipboardUpdate) {
        if (this._level >= ClipboardIntegration.CAN_WRITE) {
            if (clipboardUpdate !== null) {
                this.updateSystemClipboard(clipboardUpdate.source);
            } else {
                // Otherwise, editing context changed but clipboard not updated
                // OR the document being edited has been opened or closed.
            
                if (!this._xmlEditor.documentIsOpened) {
                    this._systemClipboardText = null;
                }
            }
        }
    }
    
    updateSystemClipboard(newText) {
        if (newText && newText !== this._systemClipboardText) {
            navigator.clipboard.writeText(newText)
                .then(() => {
                    this._systemClipboardText = newText;
                })
                .catch((error) => {
                    // A transient error?
                    console.warn(`Could not write text to \
the system clipboard: ${error}`);
                });
        }
    }
}

ClipboardIntegration.UNKNOWN = -1;
ClipboardIntegration.NONE = 0;
ClipboardIntegration.CAN_WRITE = 1;
ClipboardIntegration.CAN_READ_WRITE = 2;

ClipboardIntegration.HTML_MAGIC_STRING = "\uEFED\uEFEC\uEFEB\uEFEA";

ClipboardIntegration.MAX_READ_SIZE = 500 * 1024; // 500Kb