From 3ed54ba93a2d3df098da1bb3abe5ee26ee858228 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 15 Sep 2026 09:15:27 +0800 Subject: [PATCH] fix(macos): resign WebView input focus before disposal --- ui/flutter/third_party/README.md | 4 ++-- .../macos/Classes/GopeedProfiles.swift | 18 ------------------ .../FlutterWebViewController.swift | 3 --- .../Classes/InAppWebView/InAppWebView.swift | 6 ++++++ 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/ui/flutter/third_party/README.md b/ui/flutter/third_party/README.md index b250c9c8..560be9c4 100644 --- a/ui/flutter/third_party/README.md +++ b/ui/flutter/third_party/README.md @@ -18,8 +18,8 @@ Local native changes are limited to: setting and attach the profile before navigation. - `MyCookieManager`: route cookie operations to the requested profile, capturing the store per operation so asynchronous calls cannot cross profiles. -- macOS `FlutterWebViewController`: weakly register profile views so uninstall - can release their WKWebViews even when Flutter retains a platform-view wrapper. +- macOS `InAppWebView`: resign input focus before disposing a view so AppKit + cannot retain its profile through the responder chain after closing the page. `lib/app/rpc/webview_profile.dart` uses the existing plugin method channel and serializes these internal settings. No extension JavaScript API is added. diff --git a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/GopeedProfiles.swift b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/GopeedProfiles.swift index 8d5ab9b1..ca0602f5 100644 --- a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/GopeedProfiles.swift +++ b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/GopeedProfiles.swift @@ -7,25 +7,8 @@ import FlutterMacOS final class GopeedProfiles { private static var proxyURLs: [String: String] = [:] private static var stores: [String: WKWebsiteDataStore] = [:] - private static var views: [String: NSHashTable] = [:] static func store(_ identifier: String) -> WKWebsiteDataStore? { stores[identifier] } - static func register(_ view: FlutterWebViewController, identifier: String) { - let profileViews = views[identifier] ?? NSHashTable.weakObjects() - profileViews.add(view) - views[identifier] = profileViews - } - - private static func disposeViews(_ identifier: String) { - // Flutter may retain a removed platform-view wrapper beyond the frame - // boundary. Release its WKWebView now, before deleting the named store. - // The host has already closed all pages belonging to this profile. - let profileViews = views.removeValue(forKey: identifier) - for view in profileViews?.allObjects ?? [] { - view.dispose(removeFromSuperview: true) - } - } - static func prepare(arguments: NSDictionary?, result: @escaping FlutterResult) { guard #available(macOS 14.0, iOS 17.0, *), let identifier = arguments?["gopeedProfileId"] as? String, @@ -62,7 +45,6 @@ final class GopeedProfiles { } // Deleting a named store alone can leave in-process cookie caches. // Clear every website data type before releasing/removing the store. - disposeViews(identifier) let store = stores[identifier] ?? WKWebsiteDataStore(forIdentifier: uuid) store.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: Date.distantPast) { stores.removeValue(forKey: identifier) diff --git a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift index 7bbb22f6..2caa19c6 100755 --- a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift +++ b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/FlutterWebViewController.swift @@ -68,9 +68,6 @@ public class FlutterWebViewController: NSView, Disposable { webView!.settings = settings webView!.prepare() webView!.windowCreated = true - if let identifier = initialSettings["gopeedProfileId"] as? String { - GopeedProfiles.register(self, identifier: identifier) - } } required init?(coder nsCoder: NSCoder) { diff --git a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift index 849fd870..42327983 100755 --- a/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift +++ b/ui/flutter/third_party/flutter_inappwebview_macos/macos/Classes/InAppWebView/InAppWebView.swift @@ -2603,6 +2603,12 @@ if(window.\(JAVASCRIPT_BRIDGE_NAME)[\(_callHandlerID)] != null) { } public func dispose() { + // An edited web page can remain in AppKit's responder chain even after + // its headless wrapper is removed. Resign it while still in the window. + if let responder = window?.firstResponder as? NSView, + responder === self || responder.isDescendant(of: self) { + window?.makeFirstResponder(nil) + } channelDelegate?.dispose() channelDelegate = nil runWindowBeforeCreatedCallbacks()