Use this file to discover all available pages before exploring further.
The ProfileManager handles creation and lifecycle of both persistent and ephemeral (incognito) profiles, ensuring complete data isolation via WKWebsiteDataStore.
func loadProfiles() { do { let descriptor = FetchDescriptor<ProfileEntity>( sortBy: [SortDescriptor(\.index, order: .forward)] ) let entities = try context.fetch(descriptor) self.profiles = entities.map { e in Profile(id: e.id, name: e.name, icon: e.icon) } // Normalize indices if not sequential 0..n-1 let expected = Array(0..<entities.count) let actual = entities.map { $0.index } if actual != expected { persistProfiles() } } catch { print("[ProfileManager] Failed to load profiles: \(error)") self.profiles = [] }}
@discardableResultfunc createProfile(name: String, icon: String = "person.crop.circle") -> Profile { let nextIndex = profiles.count let profile = Profile(name: name, icon: icon) let entity = ProfileEntity(id: profile.id, name: name, icon: icon, index: nextIndex) context.insert(entity) do { try context.save() } catch { print("[ProfileManager] Save failed during create: \(error)") } profiles.append(profile) return profile}
Each profile gets a unique WKWebsiteDataStore identified by profile.id, providing complete isolation of cookies, localStorage, IndexedDB, and other web data.Source: Nook/Managers/ProfileManager/ProfileManager.swift:47-57
func deleteProfile(_ profile: Profile) -> Bool { guard profiles.count > 1 else { return false } // Prevent deleting last profile // Remove from SwiftData first do { let pid = profile.id let predicate = #Predicate<ProfileEntity> { $0.id == pid } if let entity = try context.fetch(FetchDescriptor<ProfileEntity>(predicate: predicate)).first { context.delete(entity) } try context.save() } catch { print("[ProfileManager] Delete failed: \(error)") return false } // Remove from runtime and reindex if let idx = profiles.firstIndex(where: { $0.id == profile.id }) { profiles.remove(at: idx) } persistProfiles() return true}
Deleting a profile removes its SwiftData entity and destroys its WKWebsiteDataStore, clearing all associated browsing data.Source: Nook/Managers/ProfileManager/ProfileManager.swift:59-79
Deleting a profile is irreversible. All browsing data (cookies, localStorage, history, etc.) associated with that profile is permanently destroyed.
func persistProfiles() { do { // Fetch all existing entities let all = try context.fetch(FetchDescriptor<ProfileEntity>()) var byId: [UUID: ProfileEntity] = Dictionary(uniqueKeysWithValues: all.map { ($0.id, $0) }) // Update or insert to match runtime profiles order for (index, p) in profiles.enumerated() { if let e = byId[p.id] { e.name = p.name e.icon = p.icon e.index = index } else { let e = ProfileEntity(id: p.id, name: p.name, icon: p.icon, index: index) context.insert(e) byId[p.id] = e } } // Remove entities not present in runtime array let keep = Set(profiles.map { $0.id }) for (id, e) in byId where !keep.contains(id) { context.delete(e) } try context.save() } catch { print("[ProfileManager] Persist failed: \(error)") }}
@Observableclass Profile: Identifiable { let id: UUID var name: String var icon: String var isEphemeral: Bool = false private var dataStore: WKWebsiteDataStore? var websiteDataStore: WKWebsiteDataStore { if let store = dataStore { return store } if isEphemeral { let store = WKWebsiteDataStore.nonPersistent() dataStore = store return store } else { let store = WKWebsiteDataStore(forIdentifier: id) dataStore = store return store } } static func createEphemeral() -> Profile { let profile = Profile(name: "Incognito", icon: "eye.slash") profile.isEphemeral = true return profile }}
Key properties:
Persistent profiles: WKWebsiteDataStore(forIdentifier: profile.id) creates a stable, on-disk data store
Ephemeral profiles: WKWebsiteDataStore.nonPersistent() creates an in-memory store
Ephemeral data stores are destroyed asynchronously via WKWebsiteDataStore.removeData(). The timeout protection prevents window close operations from hanging if WebKit’s cleanup is slow.
// In ExtensionManager.swiftprivate func getExtensionDataStore(for profileId: UUID) -> WKWebsiteDataStore { if let store = profileExtensionStores[profileId] { return store } let store = WKWebsiteDataStore(forIdentifier: profileId) profileExtensionStores[profileId] = store return store}func switchProfile(_ profileId: UUID) { guard let controller = extensionController else { return } let store = getExtensionDataStore(for: profileId) controller.configuration.defaultWebsiteDataStore = store currentProfileId = profileId}
This means:
Extension chrome.storage.* data is isolated per profile
When the user switches profiles, multiple systems coordinate:
// 1. BrowserManager triggers the switchfunc switchProfile(_ profile: Profile) { currentProfile = profile // 2. Update extension data store if #available(macOS 15.4, *) { ExtensionManager.shared.switchProfile(profile.id) } // 3. Reload tabs to use new profile's data store for tab in tabManager.allTabs() { tab.reloadWithProfile(profile) } // 4. Update window states for (_, windowState) in windowRegistry?.windows ?? [:] { windowState.profileId = profile.id }}
Before persisting profile-related data, check if the profile is ephemeral:
if !profile.isEphemeral { // Safe to persist persistProfileSettings(profile)}
Use async cleanup for ephemeral profiles
Always await ephemeral profile cleanup to ensure data is destroyed:
Task { await profileManager.removeEphemeralProfile(for: windowId) // Now safe to close window}
Prevent deleting the last profile
The UI should disable profile deletion when only one profile remains:
let canDelete = profileManager.profiles.count > 1
Profile switching requires tab reload
After switching profiles, tabs must be reloaded to pick up the new data store:
func switchProfile(_ profile: Profile) { currentProfile = profile for tab in tabManager.allTabs() { tab.reloadWebView() // Critical: Apply new data store }}