fancy new browser
1import Foundation
2import WebKit
3import MereKit
4
5/// BrowserContext backed by a WKWebsiteDataStore.
6@MainActor
7public final class WebKitBrowserContext: BrowserContext {
8
9 public let engine: EngineType = .webkit
10
11 private let dataStore: WKWebsiteDataStore
12 private let sharedConfiguration: WKWebViewConfiguration
13 private var _activeDownloads: [DownloadItem] = []
14 public let adBlocker: WebKitAdBlocker
15
16 public init(persistent: Bool = true) {
17 self.dataStore = persistent ? .default() : .nonPersistent()
18 let config = WKWebViewConfiguration()
19 config.websiteDataStore = dataStore
20 config.preferences.isElementFullscreenEnabled = true
21 self.sharedConfiguration = config
22 self.adBlocker = WebKitAdBlocker(configuration: config)
23 }
24
25 // MARK: - BrowserContext
26
27 public func makeWebContent() -> any WebContent {
28 // Each tab needs its own WKWebViewConfiguration so that script message
29 // handler names (mereAudio, mereTheme) don't collide. We share only the
30 // websiteDataStore so cookies and storage are common across tabs.
31 let config = WKWebViewConfiguration()
32 config.websiteDataStore = dataStore
33 config.preferences.isElementFullscreenEnabled = true
34 adBlocker.applyCurrentRules(to: config.userContentController)
35 return WebKitWebContent(configuration: config)
36 }
37
38 public func cookies(for url: URL) async -> [HTTPCookie] {
39 await dataStore.httpCookieStore.allCookies().filter { cookie in
40 url.host?.hasSuffix(cookie.domain.hasPrefix(".") ? String(cookie.domain.dropFirst()) : cookie.domain) ?? false
41 }
42 }
43
44 public func setCookies(_ cookies: [HTTPCookie], for url: URL) async {
45 for cookie in cookies {
46 await dataStore.httpCookieStore.setCookie(cookie)
47 }
48 }
49
50 public func clearCookies(for url: URL) async {
51 let existing = await cookies(for: url)
52 for cookie in existing {
53 await dataStore.httpCookieStore.deleteCookie(cookie)
54 }
55 }
56
57 public func history(limit: Int) async -> [HistoryItem] {
58 // WKWebView doesn't expose browsing history via public API.
59 // Must be tracked manually — see SessionController.
60 return []
61 }
62
63 public func clearHistory() async {
64 await dataStore.removeData(
65 ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
66 modifiedSince: .distantPast
67 )
68 }
69
70 public var activeDownloads: [DownloadItem] { _activeDownloads }
71
72 public func close() {
73 _activeDownloads.removeAll()
74 }
75}