Lazy initialization, multi-window cloning, and configuration management for WKWebView instances
Nook’s WebView architecture supports lazy initialization, multi-window coordination, and profile-based data isolation through a sophisticated coordination layer.
Problem: Loading all tab webviews at startup consumes too much memory and slows launch time.Solution: Tabs exist as lightweight Tab objects until their webview is actually needed (Tab.swift:18).
@MainActorpublic class Tab: NSObject, Identifiable { public let id: UUID var url: URL var name: String // Webview is NOT stored directly private var _assignedWebView: WKWebView? var assignedWindowId: UUID? // Lazy computed property var webView: WKWebView { get { if let existing = _assignedWebView { return existing } // Create webview on first access let config = resolveWebViewConfiguration() let webView = createWebView(configuration: config) setupWebView(webView) _assignedWebView = webView return webView } }}
Problem: Same tab can be displayed in multiple windows simultaneously. Each window needs its own WKWebView instance (can’t share a single view across NSWindows).Solution: WebViewCoordinator manages a pool of webviews indexed by (tabId, windowId) (WebViewCoordinator.swift:14-16).
Critical pattern (BrowserConfig.swift:92-102): All webview configs MUST derive via .copy().
func webViewConfiguration(for profile: Profile) -> WKWebViewConfiguration { // CRITICAL: Copy from base config to inherit extension controller + process pool let config = webViewConfiguration.copy() as! WKWebViewConfiguration // Fresh user content controller per tab (avoid cross-tab handler conflicts) config.userContentController = freshUserContentController() // Use profile's isolated data store config.websiteDataStore = profile.dataStore return config}
Never create a fresh WKWebViewConfiguration() and just set .webExtensionController on it. This breaks extension support because the config won’t share the same process pool.Always derive from BrowserConfiguration.shared.webViewConfiguration via .copy().
Problem: Extensions require all webviews to share the same WKProcessPool and internal WebKit state.Solution chain (CLAUDE.md:97-101):
BrowserConfig.shared.webViewConfiguration (base) ↓ ExtensionManager sets .webExtensionController on it ↓webViewConfiguration(for: profile) calls .copy() ↓ Copies process pool + extension controller ↓ Sets profile-specific data store ↓Tab gets derived config ↓WebView created with extension support
private static func createDataStore(for profileId: UUID) -> WKWebsiteDataStore { if #available(macOS 15.4, *) { // Profile-specific persistent store let store = WKWebsiteDataStore(forIdentifier: profileId) if !store.isPersistent { print("⚠️ Data store is not persistent for profile: \(profileId)") } else { print("✅ Using persistent data store for profile \(profileId)") } return store } else { // Fallback: shared default store on older macOS return WKWebsiteDataStore.default() }}
When user switches profiles, existing tabs’ webviews must be recreated with new data store:
func switchProfile(_ newProfile: Profile, for windowState: BrowserWindowState) { windowState.currentProfileId = newProfile.id // Recreate webviews for all visible tabs with new profile's data store for tab in visibleTabs(in: windowState) { // Clear old webview if let oldWebView = webViewCoordinator.getWebView(for: tab.id, in: windowState.id) { tab.clearAssignedWebView() webViewCoordinator.removeWebView(for: tab.id, in: windowState.id) } // Create new webview with new profile's config let newConfig = BrowserConfig.shared.webViewConfiguration(for: newProfile) let newWebView = WKWebView(frame: .zero, configuration: newConfig) setupWebView(newWebView, for: tab) // Register new webview webViewCoordinator.setWebView(newWebView, for: tab.id, in: windowState.id) tab.assignWebViewToWindow(newWebView, windowId: windowState.id) // Reload URL newWebView.load(URLRequest(url: tab.url)) }}
// ExtensionManager sets controller on base configfunc setupExtensionController() { let controller = WKWebExtensionController( configuration: .nonPersistent() // Extensions use separate storage ) // Attach to base config BrowserConfiguration.shared.webViewConfiguration.webExtensionController = controller // Load extensions for extension in installedExtensions { try? controller.load(extension.webExtension) }}
// User switches away from tab, then backTab webview already exists ↓WebViewCoordinator.getWebView(for: tabId, in: windowId) → returns existing ↓No new webview created (memory optimization)
// Window closesWindowRegistry.unregister(windowId) ↓WindowRegistry.onWindowClose?(windowId) ↓WebViewCoordinator.cleanupWindow(windowId, tabManager: tabManager) ↓For each tab shown in window: ↓ Remove webview from coordinator pool ↓ Clear tab.assignedWebView if this was primary ↓ Deallocate webview (delegates = nil, stopLoading())
Only create webviews when tabs become visible. 200 hidden tabs = 0 webviews = 0 memory.
Webview unloading
CompositorManager can unload webviews for tabs that haven’t been viewed recently:
// After N minutes of inactivityfunc unloadInactiveTabs() { for tab in tabs where tab.lastViewedAt < threshold { if let webView = tab.assignedWebView { webView.stopLoading() tab.clearAssignedWebView() webViewCoordinator.removeWebView(for: tab.id, in: windowId) } }}
Clone cleanup
Clone webviews are immediately destroyed when their window closes (not kept alive like primary webviews).
Favicon caching
Global LRU cache with disk persistence prevents re-downloading favicons (Tab.swift:56-70):
private static var faviconCache: [String: SwiftUI.Image] = [:]private static var faviconCacheOrder: [String] = [] // LRU trackingprivate static let faviconCacheMaxSize = 200
Notify ExtensionManager after webview creationCall ExtensionManager.shared.notifyTabOpened(tab) after setting up webview so extensions can inject content scripts.