fancy new browser
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 123 lines 4.3 kB view raw
1import Foundation 2import MereKit 3import Combine 4 5/// Drives a single browser window: tab list, active tab, engine routing. 6/// Mirrors ADK2.BrowserApplicationController / ADK2.BrowserController. 7@MainActor 8public final class WindowViewModel: ObservableObject { 9 10 @Published public private(set) var tabs: [Tab] = [] 11 @Published public var activeTab: Tab? 12 @Published public private(set) var newTabBackgroundColor: PlatformColor? 13 14 /// Sidebar visibility owned here so keyboard shortcuts in commands can toggle it. 15 @Published public var sidebarVisible = true 16 /// Incrementing this triggers the address bar to take focus and select-all. 17 @Published public var addressFocusTrigger = 0 18 19 private let webkitContext: any BrowserContext 20 private let chromiumContext: (any BrowserContext)? 21 private let cookieSync: CookieSyncController 22 public let adBlock: AdBlockController 23 private var activeTabObservation: AnyCancellable? 24 25 private let tabsFileURL: URL 26 27 public init( 28 webkitContext: any BrowserContext, 29 chromiumContext: (any BrowserContext)? = nil, 30 adBlockEngines: [any AdBlockEngine] = [] 31 ) { 32 self.webkitContext = webkitContext 33 self.chromiumContext = chromiumContext 34 self.cookieSync = CookieSyncController( 35 webkit: webkitContext, 36 chromium: chromiumContext 37 ) 38 self.adBlock = AdBlockController(engines: adBlockEngines) 39 40 let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! 41 let appDir = appSupport.appendingPathComponent("Mere", isDirectory: true) 42 try? FileManager.default.createDirectory(at: appDir, withIntermediateDirectories: true) 43 self.tabsFileURL = appDir.appendingPathComponent("saved_tabs.json") 44 } 45 46 // MARK: - Tab management 47 48 @discardableResult 49 public func openTab(url: URL? = nil, engine: EngineType? = nil) -> Tab { 50 // Carry the previous tab's theme color into the new tab page background. 51 if url == nil, let current = activeTab, current.url != nil { 52 newTabBackgroundColor = current.themeColor 53 } 54 activeTab?.deactivate() 55 let resolvedEngine = engine ?? url.map(EngineType.preferred) ?? .webkit 56 let context = context(for: resolvedEngine) 57 let content = context.makeWebContent() 58 let tab = Tab(content: content) 59 tabs.append(tab) 60 activeTab = tab 61 tab.activate() 62 subscribeToActiveTab() 63 if let url { tab.loadURL(url) } 64 return tab 65 } 66 67 public func closeTab(_ tab: Tab) { 68 guard tabs.count > 1 else { 69 tab.resetToNewTab() 70 return 71 } 72 tab.deactivate() 73 tab.content.close() 74 tabs.removeAll { $0.id == tab.id } 75 if activeTab?.id == tab.id { 76 activeTab = tabs.last 77 activeTab?.activate() 78 subscribeToActiveTab() 79 } 80 } 81 82 public func activateTab(_ tab: Tab) { 83 activeTab?.deactivate() 84 activeTab = tab 85 tab.activate() 86 subscribeToActiveTab() 87 } 88 89 /// Reopen a tab in the other engine, syncing cookies first. 90 public func switchEngine(for tab: Tab) async { 91 guard let url = tab.url else { return } 92 let newEngine: EngineType = tab.engine == .webkit ? .chromium : .webkit 93 guard newEngine == .webkit || chromiumContext != nil else { return } 94 95 await cookieSync.sync(from: tab.engine, url: url) 96 97 let idx = tabs.firstIndex(where: { $0.id == tab.id }) 98 closeTab(tab) 99 100 let newTab = openTab(url: url, engine: newEngine) 101 if let idx { 102 tabs.move(fromOffsets: IndexSet(integer: tabs.count - 1), toOffset: idx) 103 } 104 activeTab = newTab 105 newTab.activate() 106 subscribeToActiveTab() 107 } 108 109 // MARK: - Helpers 110 111 private func subscribeToActiveTab() { 112 activeTabObservation = activeTab?.objectWillChange 113 .receive(on: RunLoop.main) 114 .sink { [weak self] _ in self?.objectWillChange.send() } 115 } 116 117 private func context(for engine: EngineType) -> any BrowserContext { 118 switch engine { 119 case .webkit: return webkitContext 120 case .chromium: return chromiumContext ?? webkitContext 121 } 122 } 123}