From df261fabb34894ac1d23525d2df16ca0a3237048 Mon Sep 17 00:00:00 2001
From: bbblending
Date: Fri, 19 Jun 2026 13:32:45 +0800
Subject: [PATCH] fix(macos): open NSOpenPanel for embedded Control UI file
inputs (#94468) (#94612)
Summary:
- The PR wires the macOS Dashboard and Canvas WKWebViews to WKUIDelegate and presents NSOpenPanel for HTML file inputs.
- PR surface: Other +61. Total +61 across 3 files.
- Reproducibility: yes. at source level: current main renders the affected file inputs while the macOS Dashboa ... fore-fix packaged macOS app in this read-only review, but the after-fix screenshots show the real app path.
Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.
Validation:
- ClawSweeper review passed for head 4f477c4ed076da98c54fae9eafa8ca85fb5188f7.
- Required merge gates passed before the squash merge.
Prepared head SHA: 4f477c4ed076da98c54fae9eafa8ca85fb5188f7
Review: https://github.com/openclaw/openclaw/pull/94612#issuecomment-4743165861
Co-authored-by: bbblending
---
.../CanvasWindowController+UIDelegate.swift | 32 +++++++++++++++++++
.../OpenClaw/CanvasWindowController.swift | 3 +-
.../OpenClaw/DashboardWindowController.swift | 30 ++++++++++++++++-
3 files changed, 63 insertions(+), 2 deletions(-)
create mode 100644 apps/macos/Sources/OpenClaw/CanvasWindowController+UIDelegate.swift
diff --git a/apps/macos/Sources/OpenClaw/CanvasWindowController+UIDelegate.swift b/apps/macos/Sources/OpenClaw/CanvasWindowController+UIDelegate.swift
new file mode 100644
index 000000000000..33bd1439a092
--- /dev/null
+++ b/apps/macos/Sources/OpenClaw/CanvasWindowController+UIDelegate.swift
@@ -0,0 +1,32 @@
+import AppKit
+import WebKit
+
+extension CanvasWindowController {
+ // MARK: - WKUIDelegate
+
+ /// Bridges `` clicks in canvas HTML to a native `NSOpenPanel`.
+ /// Without a `WKUIDelegate`, WebKit silently drops the request and file-picker
+ /// buttons in canvas pages do nothing.
+ @MainActor
+ func webView(
+ _ webView: WKWebView,
+ runOpenPanelWith parameters: WKOpenPanelParameters,
+ initiatedByFrame frame: WKFrameInfo,
+ completionHandler: @escaping @MainActor @Sendable ([URL]?) -> Void)
+ {
+ let panel = NSOpenPanel()
+ panel.canChooseFiles = true
+ panel.canChooseDirectories = parameters.allowsDirectories
+ panel.allowsMultipleSelection = parameters.allowsMultipleSelection
+ panel.resolvesAliases = true
+ if let window = self.window {
+ panel.beginSheetModal(for: window) { response in
+ completionHandler(response == .OK ? panel.urls : nil)
+ }
+ return
+ }
+ panel.begin { response in
+ completionHandler(response == .OK ? panel.urls : nil)
+ }
+ }
+}
diff --git a/apps/macos/Sources/OpenClaw/CanvasWindowController.swift b/apps/macos/Sources/OpenClaw/CanvasWindowController.swift
index df5cb4fe2ff7..9ad31703da8c 100644
--- a/apps/macos/Sources/OpenClaw/CanvasWindowController.swift
+++ b/apps/macos/Sources/OpenClaw/CanvasWindowController.swift
@@ -5,7 +5,7 @@ import OpenClawKit
import WebKit
@MainActor
-final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NSWindowDelegate {
+final class CanvasWindowController: NSWindowController, WKNavigationDelegate, WKUIDelegate, NSWindowDelegate {
let sessionKey: String
private let root: URL
private let sessionDir: URL
@@ -159,6 +159,7 @@ final class CanvasWindowController: NSWindowController, WKNavigationDelegate, NS
}
self.webView.navigationDelegate = self
+ self.webView.uiDelegate = self
self.window?.delegate = self
self.container.onClose = { [weak self] in
self?.hideCanvas()
diff --git a/apps/macos/Sources/OpenClaw/DashboardWindowController.swift b/apps/macos/Sources/OpenClaw/DashboardWindowController.swift
index 9bb2f080f219..1bfd610631c7 100644
--- a/apps/macos/Sources/OpenClaw/DashboardWindowController.swift
+++ b/apps/macos/Sources/OpenClaw/DashboardWindowController.swift
@@ -19,7 +19,7 @@ private final class DashboardWindowDragRegionView: NSView {
}
@MainActor
-final class DashboardWindowController: NSWindowController, WKNavigationDelegate, NSWindowDelegate {
+final class DashboardWindowController: NSWindowController, WKNavigationDelegate, WKUIDelegate, NSWindowDelegate {
private let webView: WKWebView
private var currentURL: URL
private var auth: DashboardWindowAuth
@@ -44,9 +44,37 @@ final class DashboardWindowController: NSWindowController, WKNavigationDelegate,
super.init(window: window)
self.webView.navigationDelegate = self
+ self.webView.uiDelegate = self
self.window?.delegate = self
}
+ // MARK: - WKUIDelegate
+
+ /// Bridges `` clicks in the embedded Control UI to a native
+ /// `NSOpenPanel`; without a `WKUIDelegate`, WebKit silently drops the request
+ /// and "Choose image" / file-picker buttons do nothing.
+ func webView(
+ _ webView: WKWebView,
+ runOpenPanelWith parameters: WKOpenPanelParameters,
+ initiatedByFrame frame: WKFrameInfo,
+ completionHandler: @escaping @MainActor @Sendable ([URL]?) -> Void)
+ {
+ let panel = NSOpenPanel()
+ panel.canChooseFiles = true
+ panel.canChooseDirectories = parameters.allowsDirectories
+ panel.allowsMultipleSelection = parameters.allowsMultipleSelection
+ panel.resolvesAliases = true
+ if let window = self.window {
+ panel.beginSheetModal(for: window) { response in
+ completionHandler(response == .OK ? panel.urls : nil)
+ }
+ return
+ }
+ panel.begin { response in
+ completionHandler(response == .OK ? panel.urls : nil)
+ }
+ }
+
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) is not supported")