From 07b150c299cb45263ba7f1448b51a50bcf145115 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 21 Oct 2026 17:03:36 +0200 Subject: [PATCH 1/3] Extract clipboard logic into shared utility Expose copyToClipboard and getClipboardUnavailableReason that can be used by clipboard wanting components. --- Framework/Frontend/js/src/index.js | 1 + .../Frontend/js/src/utilities/clipboard.js | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 Framework/Frontend/js/src/utilities/clipboard.js diff --git a/Framework/Frontend/js/src/index.js b/Framework/Frontend/js/src/index.js index 27391cf72..d4023c69a 100644 --- a/Framework/Frontend/js/src/index.js +++ b/Framework/Frontend/js/src/index.js @@ -27,6 +27,7 @@ export { documentClickTaggedEventRegistry } from './utilities/documentClickTagge export { buildUrl } from './utilities/buildUrl.js'; export { parseUrlParameters } from './utilities/parseUrlParameters.js'; export { isContextSecure } from './utilities/browserContext.js'; +export { copyToClipboard, getClipboardUnavailableReason } from './utilities/clipboard.js'; // Formatters export { formatTimeDuration } from './formatter/formatTimeDuration.js'; diff --git a/Framework/Frontend/js/src/utilities/clipboard.js b/Framework/Frontend/js/src/utilities/clipboard.js new file mode 100644 index 000000000..4dc310ebe --- /dev/null +++ b/Framework/Frontend/js/src/utilities/clipboard.js @@ -0,0 +1,52 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +/* Global: window, navigator */ + +import { isContextSecure } from './browserContext.js'; + +/** + * Returns why the clipboard cannot be written to in the current context, if anything prevents it. + * @returns {string|null} A descriptive reason if the clipboard is unavailable, `null` otherwise. + */ +export const getClipboardUnavailableReason = () => { + if (!isContextSecure()) { + return 'Clipboard not available in a non-secure context.'; + } + + if (!navigator.clipboard) { + return 'Clipboard API is not supported in this browser.'; + } + + if (window !== window.parent) { + return 'Clipboard access is restricted in iframes.'; + } + + return null; +}; + +/** + * Writes the given text to the user's clipboard. + * @param {string} value - The text to be copied to the clipboard. + * @returns {Promise} Resolves once the text has been written. + * @throws {Error} If the clipboard is unavailable or the write is rejected. + */ +export const copyToClipboard = async (value) => { + const unavailableReason = getClipboardUnavailableReason(); + if (unavailableReason) { + throw new Error(unavailableReason); + } + + await navigator.clipboard.writeText(value); +}; From 3938049ad9359d641cb65cbede468c06271631c3 Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Wed, 21 Oct 2026 17:04:05 +0200 Subject: [PATCH 2/3] Extract clipboard logic into utility CopyToClipboardComponent now uses copyToClipboard instead of its own availability checks. --- .../components/CopyToClipboardComponent.js | 53 ++----------------- 1 file changed, 4 insertions(+), 49 deletions(-) diff --git a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js index b8b5dd5e3..f2d12e2d6 100644 --- a/Framework/Frontend/js/src/components/CopyToClipboardComponent.js +++ b/Framework/Frontend/js/src/components/CopyToClipboardComponent.js @@ -14,7 +14,7 @@ import { StatefulComponent } from './StatefulComponent.js'; import { iconCheck, iconLinkIntact } from '../icons.js'; import { h } from '../renderer.js'; -import { isContextSecure } from '../utilities/browserContext.js'; +import { copyToClipboard, getClipboardUnavailableReason } from '../utilities/clipboard.js'; /** * Represents a component that allows copying text to the clipboard. @@ -27,15 +27,8 @@ export class CopyToClipboardComponent extends StatefulComponent { super(); this._successStateTimeout = null; - this._available = true; - this._message = ''; - - try { - this.checkClipboardAvailability(); - } catch ({ message: errorMessage }) { - this._available = false; - this._message = errorMessage; - } + this._message = getClipboardUnavailableReason() ?? ''; + this._available = !this._message; } /** @@ -47,7 +40,7 @@ export class CopyToClipboardComponent extends StatefulComponent { */ async copyToClipboard(clipboardTargetValue, onFailure) { try { - await navigator.clipboard.writeText(clipboardTargetValue); + await copyToClipboard(clipboardTargetValue); if (this._successStateTimeout) { clearTimeout(this._successStateTimeout); @@ -66,44 +59,6 @@ export class CopyToClipboardComponent extends StatefulComponent { } } - /** - * Checks the availability of the clipboard and provides a message if it is not accessible. - * - * @throws {Error} Throws an error with a descriptive message if the clipboard is not available. - * @returns {void} - */ - checkClipboardAvailability() { - if (!isContextSecure()) { - throw new Error('Clipboard not available in a non-secure context.'); - } - - if (!this.isClipboardSupported()) { - throw new Error('Clipboard API is not supported in this browser.'); - } - - if (this.isWindowEmbedded()) { - throw new Error('Clipboard access is restricted in iframes.'); - } - } - - /** - * Checks if the clipboard API is available in the user's browser. - * - * @returns {boolean} Returns `true` if it is available - */ - isClipboardSupported() { - return Boolean(navigator.clipboard); - } - - /** - * Check if the window is embedded in a frame. - * - * @returns {boolean} Returns `true` if it is embedded - */ - isWindowEmbedded() { - return window !== window.parent; - } - /** * Renders the button that allows copying text to the clipboard. * Attributes other than those listed are not forwarded to the button element. From 6a1d862839766c4d935c3ad51dc08c6b93bfddea Mon Sep 17 00:00:00 2001 From: Isaac Hill <71404865+isaachilly@users.noreply.github.com> Date: Thu, 22 Oct 2026 16:50:02 +0200 Subject: [PATCH 3/3] Reword JSDoc --- Framework/Frontend/js/src/utilities/clipboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Framework/Frontend/js/src/utilities/clipboard.js b/Framework/Frontend/js/src/utilities/clipboard.js index 4dc310ebe..621810d6d 100644 --- a/Framework/Frontend/js/src/utilities/clipboard.js +++ b/Framework/Frontend/js/src/utilities/clipboard.js @@ -17,7 +17,7 @@ import { isContextSecure } from './browserContext.js'; /** - * Returns why the clipboard cannot be written to in the current context, if anything prevents it. + * Checks the availability of the clipboard and provides a message if it is not accessible. * @returns {string|null} A descriptive reason if the clipboard is unavailable, `null` otherwise. */ export const getClipboardUnavailableReason = () => {