Skip to content

BbCopyText: async ValueFuncAsync needs ClipboardItem to survive the transient user activation #466

Description

@mathewtaylor

Split out of #453 (@DCarlson12), by agreement on that issue. The synchronous Func<string> half stays there and can land on its own; this covers the asynchronous variant, which is a materially different piece of work.

Why this is not just another parameter

clipboard.js currently does:

export async function copyToClipboard(text) {
  try {
    await navigator.clipboard.writeText(text);
    return true;
  } catch {
    // textarea + document.execCommand fallback
  }
}

Clipboard writes require transient user activation. Today Value is a string already in hand, so the click handler runs essentially straight into writeText and the activation is still live.

If we await a consumer-supplied Func<Task<string>> first — a fetch, a database read, anything genuinely async — several browsers, Safari most strictly, treat the activation as spent by the time writeText is called and reject the write.

The failure is quiet, which is the dangerous part. The rejection is caught by the existing catch, which falls through to the document.execCommand('copy') fallback — and that is also gesture-gated (and deprecated), so it fails too. The user clicks copy, sees the success state, and gets nothing on the clipboard.

The actual fix is in the JS layer

Hand the promise to ClipboardItem so the browser keeps the activation alive across the await, rather than awaiting first and writing second:

navigator.clipboard.write([
  new ClipboardItem({ 'text/plain': textPromise })
]);

Work involved:

  • Restructure copyToClipboard to accept a promise-producing callback rather than a resolved string, so the .NET side can be invoked inside the ClipboardItem rather than before the write.
  • Feature-detect: ClipboardItem support for promise values differs across browsers, and navigator.clipboard.write is not universally available where writeText is. Needs a path for browsers that only support the resolved-string form — accepting that those will fail on a genuinely slow func, and deciding whether that surfaces as an error rather than a silent no-op.
  • Revisit the execCommand fallback. It cannot rescue an expired activation, so treating it as the catch-all for every failure hides this class of bug. It should probably only run for the insecure-context case it was written for.
  • Decide what a failed copy reports. Today failure is invisible; with an async source, failure becomes much more likely and the component should probably surface it.

Testing

This cannot be verified by unit test or by building — activation semantics only exist in a real browser, and they differ per engine. Needs manual verification in Safari specifically, plus Chrome and Firefox, with a deliberately slow func (a second or more) rather than an immediately-resolved task, which will pass everywhere and prove nothing.

Depends on

#453 — the sync ValueFunc should land first. It settles the parameter naming and the precedence rules (see below), which this variant then follows.

Shared API decisions, settled on #453

Both halves need these, so recording them here too:

  1. Value is currently [Parameter, EditorRequired]. Adding func-based parameters means dropping EditorRequired, or the analyzer nags anyone who sets only a func. That is an API-surface snapshot change.
  2. "First non-null" precedence needs tightening — Value is a string, so a non-null empty string would win a literal reading and silently copy nothing. Precedence should key on !string.IsNullOrEmpty(Value), and the docs should state plainly what happens when both a value and a func are set.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions