Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
2cafcd2
Implement the async Clipboard API (navigator.clipboard)
cirospaciari Jul 3, 2026
94a2dc3
Address review feedback: MIME normalization, helper watchdog, spawn s…
cirospaciari Jul 3, 2026
af987ad
Gate spawn signal forwarding on the real main thread; fix the watchdog
cirospaciari Jul 7, 2026
1274cd4
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
cirospaciari Jul 7, 2026
44104b4
Support text/html on the Windows clipboard via CF_HTML
cirospaciari Jul 7, 2026
a4b663b
Treat an unparseable CF_HTML payload as an absent representation
cirospaciari Jul 7, 2026
3b2abc3
Close the watchdog trap race and normalize getType()/duplicate types
cirospaciari Jul 7, 2026
a42960a
Pass the read() representations to the item factory by reference
cirospaciari Jul 7, 2026
8ff1962
Drive the clipboard promises with reaction handlers instead of chaining
cirospaciari Jul 17, 2026
7973142
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
82b761a
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
cirospaciari Jul 17, 2026
b71e64e
clipboard: ToString-coerce non-Blob getType() data and keep empty rep…
robobun Jul 17, 2026
4bb9da8
clipboard: sync doc comments with b71e64ed and assert types [SameObje…
robobun Jul 17, 2026
c158a29
clipboard: drop JSClipboardWriteState; carry write() state in a plain…
robobun Jul 24, 2026
f7ba2c0
clipboard: await Promise.all for write() instead of a resumable loop
robobun Jul 24, 2026
f9bca1d
Merge remote-tracking branch 'origin/main' into worktree-clipboard-33312
cirospaciari Jul 24, 2026
71d8301
clipboard: port WebCore's Clipboard/ClipboardItem architecture
cirospaciari Jul 24, 2026
9e36795
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
9470349
clipboard: release the binding throw scope before the impl call
robobun Jul 24, 2026
5d18031
clipboard: add JSClipboardItem::destroy, thread-local event names, an…
robobun Jul 24, 2026
2dd9c37
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
5418d88
clipboard: normalize MIME keys through one parseMIMETypeEssence helper
robobun Jul 24, 2026
d20d7cf
clipboard: snapshot the completion handler, drop dead Clipboard::getT…
robobun Jul 24, 2026
e188365
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Jul 24, 2026
a0ee5ab
clipboard(windows): drop dead Option layer from copy_global
robobun Jul 24, 2026
2ce9586
clipboard: break the whenSettled cycle, convert options before record…
robobun Jul 24, 2026
7ce792b
clipboard: give ItemWriter ownership of its items, and retire collect…
cirospaciari Jul 24, 2026
b3027ef
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 24, 2026
896099a
clipboard: drop the strong DOMPromise capture from getType()'s reaction
robobun Jul 24, 2026
eb0813c
clipboard: keep the item alive across getType()'s async gap
robobun Jul 24, 2026
0d999e7
clipboard: state the getType() reaction's lifetime tradeoff honestly
robobun Jul 24, 2026
ba903e5
Merge branch 'main' into claude/clipboard-api-native
cirospaciari Jul 24, 2026
3068162
clipboard: writeText() supersedes an in-flight write(); null m_clipbo…
robobun Jul 24, 2026
672f562
ci: retrigger
robobun Jul 24, 2026
e31b709
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 3, 2026
eda512f
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 3, 2026
c4c6500
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
c2ec31b
clipboard: resolve getType() with a plain Blob, not a File
robobun Aug 3, 2026
effdc61
getType() returns a fresh Blob; write([]) supersedes an in-flight write
robobun Aug 3, 2026
23b0f77
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 4, 2026
b2f7b5b
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 4, 2026
45dc611
spawn_sync: scope the signal-forwarding opt-out to an explicit Option…
robobun Aug 4, 2026
7c52c0c
collectDataForWriting: bail if a tampered Promise.all re-enters the c…
robobun Aug 4, 2026
bb51b2b
clipboard test: save/restore the OS clipboard once around the whole file
robobun Aug 4, 2026
a8b0bfe
clipboard test: fall back to writeText when restoring a multi-rep item
robobun Aug 4, 2026
9082a1b
Clipboard::writeText: correct the comment about how upstream supersedes
robobun Aug 4, 2026
ed969cd
clipboard: read file-backed Blobs asynchronously; CRLF for Windows text
robobun Aug 4, 2026
3562f5d
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 4, 2026
5adb96c
getType(): stamp a lazy pass-through dupe with the representation key
robobun Aug 4, 2026
d65e110
clippy: finish() consumes the handler by value, not by Box
robobun Aug 4, 2026
ff4c12b
clipboard: centralize the Win32 externs in bun_sys::windows::clipboard
robobun Aug 5, 2026
58934ec
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 5, 2026
55e1905
clipboard: cancel a superseded write's queued platform job
robobun Aug 5, 2026
a650df4
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 5, 2026
962ce97
clipboard: push the remaining unsafe behind shared wrappers
robobun Aug 5, 2026
2a9a464
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 5, 2026
87bdf22
clipboard: make read() one coherent snapshot per platform
robobun Aug 5, 2026
503cd4b
clipboard: serialize platform jobs on a dedicated FIFO thread
cirospaciari Aug 5, 2026
2201bb2
clipboard: derive a cleared name from the File bit; safe serial-sched…
cirospaciari Aug 5, 2026
5db8b5e
any_task_job: build jobs as an owned Box until the hand-off
cirospaciari Aug 5, 2026
a095579
clipboard: serialize MIME types per spec; never abort on scheduling
cirospaciari Aug 5, 2026
4efc364
getType(): exact serialization match before the essence fallback
robobun Aug 5, 2026
a7fc660
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 7, 2026
58ffb28
Restore DOMPromise::whenSettled/result/status for the clipboard
robobun Aug 7, 2026
fd4e819
clipboard: route byte searches through bun_core::strings
robobun Aug 7, 2026
e0e404d
DOMPromise::whenSettled takes WTF::Function like its sibling
robobun Aug 7, 2026
a0bd728
clipboard: name the helper temp file with FileSystem::tmpname
robobun Aug 8, 2026
c055952
Merge origin/main into claude/clipboard-api-native
robobun Aug 10, 2026
d2d4bf6
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 10, 2026
2acb4b4
Blob__fromBytesWithType: make the three lifetime comments agree
robobun Aug 10, 2026
af0d5b3
clipboard: run the platform op before the job callback, outside the V…
robobun Aug 10, 2026
83124c7
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 10, 2026
fca521f
Merge branch 'main' into claude/clipboard-api-native
alii Aug 11, 2026
5de6fd3
ci: opt this head into the hosted macOS test lane [macos tests]
robobun Aug 11, 2026
3af2df0
clipboard: drop the dedicated thread, test the native error and platf…
robobun Aug 12, 2026
acb8cf0
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 12, 2026
b88f339
clipboard tests: race reads against writes on both in-process backends
robobun Aug 12, 2026
bbc4d1c
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 12, 2026
2368e16
clipboard: one transaction at a time for every in-process clipboard user
robobun Aug 12, 2026
3710bc0
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 12, 2026
bac22e8
Blob__implReadBytes: the handler is consumed whatever read_bytes_to_h…
robobun Aug 12, 2026
65c2fe7
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 16, 2026
e9099ed
clipboard: follow main's job model, structure slots, and trimmed even…
robobun Aug 16, 2026
dc299bf
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 21, 2026
8ad5812
clipboard bindings: follow main's shared class helpers and promise API
robobun Aug 21, 2026
864c6eb
Merge remote-tracking branch 'origin/main' into claude/clipboard-api-…
robobun Aug 26, 2026
7800f0b
clipboard: follow main's String API and trimmed DOMConstructorID
robobun Aug 26, 2026
2d4c3e2
ClipboardItem: trim MIME types with HTTP whitespace, not ASCII whites…
robobun Aug 26, 2026
57f1cc8
OpenedClipboard::open: no sleep after the last attempt
robobun Aug 26, 2026
b170bed
ClipboardItem: name web custom formats as unsupported
robobun Aug 26, 2026
757972b
Merge origin/main into claude/clipboard-api-native
robobun Aug 27, 2026
63a3153
Merge branch 'main' into claude/clipboard-api-native
cirospaciari Aug 28, 2026
a350c1d
Merge branch 'main' into claude/clipboard-api-native
cirospaciari Sep 4, 2026
ffb5a3a
Merge branch 'main' into claude/clipboard-api-native
cirospaciari Sep 4, 2026
fe565a4
Merge branch 'main' into claude/clipboard-api-native
cirospaciari Sep 15, 2026
7aab8b8
clipboard: address review of the async Clipboard API
cirospaciari Sep 15, 2026
e4b2b65
Blob.name: remove the File-bit guard from get_name_string
robobun Sep 15, 2026
f4a6dbb
js_parser: do not treat Clipboard and ClipboardItem as pure globals
robobun Sep 15, 2026
d634abb
ClipboardItem: collect write() representations with one reaction each
robobun Sep 15, 2026
43e4fc3
clipboard: keep the job outcome as a Result
robobun Sep 15, 2026
11d66f7
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 15, 2026
1efafa0
clipboard: fix Linux write stdin, read() types and promise registration
cirospaciari Sep 15, 2026
2cb7ef3
Merge robobun's blocking-item fixes into the review-fix branch
cirospaciari Sep 15, 2026
0a18eeb
clipboard.test: fix the three CI failures on 2cb7ef3
robobun Sep 15, 2026
4fb09ca
clipboard: reject with caught exceptions instead of clearing them; fi…
cirospaciari Sep 15, 2026
13d88fc
Merge robobun's CI test fixes
cirospaciari Sep 15, 2026
c45a28e
ClipboardItem: run the coercion as a promise reaction
robobun Sep 15, 2026
f1bff6a
clipboard: stop writing through xsel; drop tests that cannot fail
cirospaciari Sep 21, 2026
ad2c560
Merge main into the clipboard branch
cirospaciari Sep 21, 2026
50a4c8d
clipboard: adapt to the JsThread and UTF8View changes on main
cirospaciari Sep 21, 2026
49fed6b
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 21, 2026
44c3176
clipboard: read() offers image/png for a bitmap-only Windows clipboard
cirospaciari Sep 21, 2026
81acafe
clipboard: account for a DIB's colour table when wrapping it as a BMP
cirospaciari Sep 21, 2026
3c0c714
clipboard: run the Linux helpers with the script's own environment
cirospaciari Sep 21, 2026
5ba4ac3
clipboard: read the live process.env; decode UTF-16 HTML; cover biClr…
cirospaciari Sep 21, 2026
22b949b
clipboard: report when a clipboard operation could not be scheduled
cirospaciari Sep 21, 2026
8d3eb0b
clipboard: check for an exception when reading process.env
cirospaciari Sep 21, 2026
9d5f7a2
clipboard: take process.env through the exception checking wrapper
cirospaciari Sep 21, 2026
194f307
backend_wic: skip the colour masks Windows repeats after a V5 header
robobun Sep 21, 2026
22621e8
clipboard.test: a worker's first read of process.env is a clipboard o…
robobun Sep 21, 2026
6320501
clipboard: close the Windows clipboard before converting a bitmap to PNG
cirospaciari Sep 21, 2026
45942c1
env_var: drop the unused DISPLAY and WAYLAND_DISPLAY entries
cirospaciari Sep 21, 2026
d28ff32
Merge branch 'main' into claude/clipboard-api-native
robobun Sep 28, 2026
96de433
clipboard: reject readText() text that does not fit a string
robobun Oct 2, 2026
8cfe6b5
spawn: add an opt-in time limit to the synchronous spawn
robobun Oct 2, 2026
cb80b73
clipboard: run each helper directly, under the time limit of the spawn
robobun Oct 2, 2026
e46e2a1
spawn: say that SyncStdio::Fd is for stdin, and assert it
robobun Oct 2, 2026
94e7f1f
clipboard tests: drop the 1 GiB test, and bound the waits for a stand-in
robobun Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 20 additions & 19 deletions docs/runtime/web-apis.mdx

Large diffs are not rendered by default.

179 changes: 179 additions & 0 deletions packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ declare module "bun" {
type LibEmptyOrBroadcastChannel = LibDomIsLoaded extends true ? {} : import("node:worker_threads").BroadcastChannel;
type LibEmptyOrEventSource = LibDomIsLoaded extends true ? {} : import("undici-types").EventSource;

interface BunClipboardEvent extends Event {
/** Always `null`: Bun does not implement `DataTransfer`. */
readonly clipboardData: null;
}
type LibEmptyOrBunClipboardEvent = LibDomIsLoaded extends true ? {} : BunClipboardEvent;

type LibEmptyOrReadableByteStreamController = LibDomIsLoaded extends true
? {}
: import("node:stream/web").ReadableByteStreamController;
Expand Down Expand Up @@ -1478,10 +1484,183 @@ interface Navigator {
readonly userAgent: string;
readonly platform: "MacIntel" | "Win32" | "Linux x86_64";
readonly hardwareConcurrency: number;
/**
* The system clipboard, from the
* [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API).
*
* Supported representations on every platform: `text/plain`,
* `text/html`, and `image/png`.
*
* On Linux this drives `wl-paste`/`wl-copy` (Wayland) or `xclip`, with
* `xsel` as a fallback for reading text, so one of those must be installed and `$WAYLAND_DISPLAY` or
* `$DISPLAY` must be set; otherwise the methods reject with a
* `"NotAllowedError"` `DOMException`.
*
* @example
* ```ts
* await navigator.clipboard.writeText("Hello from Bun!");
* console.log(await navigator.clipboard.readText());
* ```
*/
readonly clipboard: Clipboard;
}

declare var navigator: Navigator;

/**
* The async [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard).
*
* Reachable as the `navigator.clipboard` singleton. It has no public
* constructor: `new Clipboard()` throws a `TypeError`.
*
* Bun fires a `"copy"` `ClipboardEvent` at `navigator.clipboard` after every
* successful `writeText()` or `write()` that places data on the clipboard
* (an empty `write([])` is a no-op), and a `"paste"` after every successful
* `read()`/`readText()`: there is no document or focused element in a
* runtime, so this `EventTarget` stands in for the spec's event target.
* `"cut"` is never fired automatically (there is no selection to remove).
*
* @example
* ```ts
* navigator.clipboard.addEventListener("copy", () => console.log("copied"));
* await navigator.clipboard.writeText("hi"); // logs "copied"
* ```
*/
interface Clipboard extends EventTarget {
/**
* Read the system clipboard's plain-text contents.
*
* @returns the text, or `""` when nothing (or nothing textual) is on the
* clipboard. Rejects with a `"NotAllowedError"` `DOMException` when the
* platform clipboard cannot be reached at all, or when the text is too
* large for a string.
*/
readText(): Promise<string>;
/**
* Replace the system clipboard's contents with `data` as plain text.
*
* Rejects with a `"NotAllowedError"` `DOMException` when the platform
* clipboard cannot be reached at all.
*/
writeText(data: string): Promise<void>;
/**
* Read every supported representation of the clipboard's current item as a
* single `ClipboardItem`, in one operation.
*
* @returns `[item]`, or `[]` when no supported representation is present.
* Rejects with a `"NotAllowedError"` `DOMException` when the platform
* clipboard cannot be reached at all.
*
* @example
* ```ts
* for (const item of await navigator.clipboard.read()) {
* if (item.types.includes("image/png")) {
* await Bun.write("clip.png", await item.getType("image/png"));
* }
* }
* ```
*/
read(): Promise<ClipboardItem[]>;
/**
* Replace the system clipboard with the representations of one
* `ClipboardItem` (every type `ClipboardItem.supports()` reports).
*
* Rejects with a `"NotAllowedError"` `DOMException` if any representation
* is unsupported, if more than one item is passed, on Linux/BSD if the
* item has more than one representation (the helper programs can only own
* one), or when the platform clipboard cannot be reached.
*
* Writes are not queued: one that is not awaited can land before or after
* a later `write()`/`writeText()`.
*/
write(data: ClipboardItem[]): Promise<void>;
}
declare var Clipboard: Bun.__internal.UseLibDomIfAvailable<
"Clipboard",
{
prototype: Clipboard;
/**
* Lets `x instanceof Clipboard` type-check (it narrows to `Clipboard`).
* Deliberately no `new ()`: per the spec `Clipboard` has no constructor,
* so `new Clipboard()` is a compile error and a runtime `TypeError`.
*/
[Symbol.hasInstance](value: unknown): value is Clipboard;
}
>;

/**
* One clipboard "item" and its representations, keyed by MIME type.
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardItem)
*
* Bun supports `text/plain`, `text/html`, and `image/png` on every
* platform (on Linux, `xsel` only reads text).
*/
interface ClipboardItem {
/** The MIME types this item holds, in insertion order (frozen). */
readonly types: readonly string[];
/** How the item should be presented; defaults to `"unspecified"`. */
readonly presentationStyle: "unspecified" | "inline" | "attachment";
/**
* The item's representation for `type` as a `Blob` whose `type` matches.
* Rejects with a `"NotFoundError"` `DOMException` for a type the item does
* not hold.
*/
getType(type: string): Promise<Blob>;
}
declare var ClipboardItem: Bun.__internal.UseLibDomIfAvailable<
"ClipboardItem",
{
prototype: ClipboardItem;
/**
* @param items one entry per MIME type; each value is a string, a `Blob`,
* or a promise of either, resolved lazily by `getType()`/`write()`.
* @example
* ```ts
* await navigator.clipboard.write([
* new ClipboardItem({ "text/plain": "hi", "text/html": "<b>hi</b>" }),
* ]);
* ```
*/
new (
items: Record<string, string | Blob | PromiseLike<string | Blob>>,
options?: { presentationStyle?: "unspecified" | "inline" | "attachment" },
): ClipboardItem;
/**
* Whether Bun handles `type`. On Linux it does not check which helper
* program is installed.
*/
supports(type: string): boolean;
}
>;

/**
* The `copy`/`cut`/`paste` event type.
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ClipboardEvent)
*
* Bun fires `"copy"` and `"paste"` at `navigator.clipboard` after its own
* successful write/read operations (there is no document, focused element,
* or user gesture in a runtime), and never fires `"cut"`. The class is also
* constructible, so synthetic events can be dispatched through any
* `EventTarget`. `clipboardData` is always `null` at runtime because Bun
* does not implement `DataTransfer`.
*/
interface ClipboardEvent extends Bun.__internal.LibEmptyOrBunClipboardEvent {}
declare var ClipboardEvent: Bun.__internal.UseLibDomIfAvailable<
"ClipboardEvent",
{
prototype: ClipboardEvent;
new (
type: string,
eventInitDict?: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
clipboardData?: null;
},
): ClipboardEvent;
}
>;

interface BlobPropertyBag {
/** Set a default "type". Not yet implemented. */
type?: string;
Expand Down
1 change: 1 addition & 0 deletions src/codegen/generate-js2native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const rustIdentifierPaths: Record<string, string> = {
"runtime/socket/socket.rs": "runtime/socket/socket.rs",
"runtime/timer/Timer.rs": "runtime/timer/Timer.rs",
"runtime/webcore/ByteStream.rs": "runtime/webcore/ByteStream.rs",
"runtime/webcore/clipboard.rs": "runtime/webcore/clipboard.rs",
"runtime/webcore/FileSink.rs": "runtime/webcore/FileSink.rs",
"runtime/webcore/fetch.rs": "runtime/webcore/fetch.rs",
"shell.rs": "runtime/shell/shell.rs",
Expand Down
8 changes: 8 additions & 0 deletions src/js/internal-for-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ export const setMaxMarkdownBlockBytesForTesting: (limit: number) => number = $ne
1,
);

// How long one run of a clipboard helper program (`wl-paste`, `xclip`, ...) may
// take, in milliseconds. Returns the previous limit.
export const setClipboardHelperTimeoutForTesting: (milliseconds: number) => number = $newRustFunction(
"runtime/webcore/clipboard.rs",
"setHelperTimeoutForTesting",
1,
);

export const npm_manifest_test_helpers = $rust("npm.rs", "PackageManifest.bindings.generate") as {
/**
* Returns the parsed manifest file. Currently only returns an array of available versions.
Expand Down
Loading
Loading