Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ora/App/OraRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ struct OraRoot: View {
.enableInjection()
.onAppear {
downloadManager.toastManager = toastManager
Task {
let containerIDs = await MainActor.run {
(try? tabContext.fetch(FetchDescriptor<TabContainer>()))?.map(\.id) ?? []
}
await AdBlockService.shared.start(containerIDs: containerIDs)
}

// Dialog keyboard shortcuts (highest priority — checked first)
keyModifierListener.registerKeyDownHandler { event in
// Escape: dismiss top dialog
Expand Down Expand Up @@ -271,6 +278,14 @@ struct OraRoot: View {
}
}

NotificationCenter.default
.addObserver(forName: .spacePrivacySettingsChanged, object: nil, queue: .main) { note in
Task { @MainActor in
guard let containerId = note.userInfo?["containerId"] as? UUID else { return }
tabManager.refreshPrivacySettings(for: containerId)
}
}

// Clear cache and reload
NotificationCenter.default
.addObserver(forName: .clearCacheAndReload, object: nil, queue: .main) { note in
Expand Down
9 changes: 7 additions & 2 deletions ora/Core/BrowserEngine/BrowserEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ struct BrowserPageConfiguration {
let mediaPlaybackRequiresUserAction: Bool
let scriptMessageNames: [String]
let userScripts: [BrowserUserScript]
let privacySettings: SpacePrivacySettings

static func oraDefault(userScripts: [BrowserUserScript]) -> BrowserPageConfiguration {
static func oraDefault(
userScripts: [BrowserUserScript],
privacySettings: SpacePrivacySettings
) -> BrowserPageConfiguration {
BrowserPageConfiguration(
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0.1 Safari/605.1.15",
allowsPictureInPicture: true,
Expand All @@ -23,7 +27,8 @@ struct BrowserPageConfiguration {
allowsBackForwardNavigationGestures: true,
mediaPlaybackRequiresUserAction: false,
scriptMessageNames: ["listener", "linkHover", "mediaEvent", "passwordManager"],
userScripts: userScripts
userScripts: userScripts,
privacySettings: privacySettings
)
}
}
Expand Down
36 changes: 36 additions & 0 deletions ora/Core/BrowserEngine/BrowserPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ final class BrowserPage: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptM
private(set) var lastCommittedURL: URL?
private(set) var isDownloadNavigation = false
private(set) var sslBypassedHosts: Set<String> = []
private var isReadyForNavigation = false
private var pendingLoadRequest: URLRequest?
private var pendingReload = false

init(
profile: BrowserEngineProfile,
Expand Down Expand Up @@ -74,6 +77,14 @@ final class BrowserPage: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptM
layer.isOpaque = true
layer.drawsAsynchronously = true
}

BrowserPrivacyService.shared.prepareConfiguration(
webConfiguration,
spaceID: profile.identifier
) { [weak self] in
self?.isReadyForNavigation = true
self?.flushPendingNavigationIfNeeded()
}
}

var contentView: NSView {
Expand Down Expand Up @@ -109,10 +120,22 @@ final class BrowserPage: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptM
}

func load(_ request: URLRequest) {
guard isReadyForNavigation else {
pendingLoadRequest = request
pendingReload = false
return
}

webView.load(request)
}

func reload() {
guard isReadyForNavigation else {
pendingReload = true
pendingLoadRequest = nil
return
}

webView.reload()
}

Expand Down Expand Up @@ -164,6 +187,19 @@ final class BrowserPage: NSObject, WKNavigationDelegate, WKUIDelegate, WKScriptM
sslBypassedHosts.insert(host)
}

private func flushPendingNavigationIfNeeded() {
if let pendingLoadRequest {
self.pendingLoadRequest = nil
webView.load(pendingLoadRequest)
return
}

if pendingReload {
pendingReload = false
webView.reload()
}
}

private func emitNavigationEvent(
phase: BrowserNavigationPhase,
url: URL?,
Expand Down
1 change: 1 addition & 0 deletions ora/Core/Constants/AppEvents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extension Notification.Name {
// Cache and cookies
static let clearCacheAndReload = Notification.Name("ClearCacheAndReload")
static let clearCookiesAndReload = Notification.Name("ClearCookiesAndReload")
static let spacePrivacySettingsChanged = Notification.Name("SpacePrivacySettingsChanged")

/// App lifecycle
static let quitRequested = Notification.Name("QuitRequested")
Expand Down
69 changes: 68 additions & 1 deletion ora/Core/Utilities/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class SettingsStore: ObservableObject {
private let fingerprintingKey = "settings.tracking.blockFingerprinting"
private let adBlockingKey = "settings.tracking.adBlocking"
private let cookiesPolicyKey = "settings.cookies.policy"
private let adBlockFilterListsKey = "settings.adBlock.filterLists"
private let sitePermissionsKey = "settings.permissions.sitePermissions"
private let customSearchEnginesKey = "settings.customSearchEngines"
private let globalDefaultSearchEngineKey = "settings.globalDefaultSearchEngine"
Expand Down Expand Up @@ -176,6 +177,10 @@ class SettingsStore: ObservableObject {
"settings.container.\(containerId.uuidString).autoClearTabsAfter"
}

private func keyForPrivacySettings(for containerId: UUID) -> String {
"settings.container.\(containerId.uuidString).privacy"
}

@Published var autoUpdateEnabled: Bool {
didSet { defaults.set(autoUpdateEnabled, forKey: autoUpdateKey) }
}
Expand All @@ -200,6 +205,10 @@ class SettingsStore: ObservableObject {
didSet { saveCodable(sitePermissions, forKey: sitePermissionsKey) }
}

@Published private(set) var adBlockFilterLists: [FilterListRecord] {
didSet { saveCodable(adBlockFilterLists, forKey: adBlockFilterListsKey) }
}

@Published var customSearchEngines: [CustomSearchEngine] {
didSet { saveCodable(customSearchEngines, forKey: customSearchEnginesKey) }
}
Expand Down Expand Up @@ -258,7 +267,7 @@ class SettingsStore: ObservableObject {
init() {
autoUpdateEnabled = defaults.bool(forKey: autoUpdateKey)
blockThirdPartyTrackers = defaults.bool(forKey: trackingThirdPartyKey)
blockFingerprinting = defaults.bool(forKey: fingerprintingKey)
blockFingerprinting = defaults.object(forKey: fingerprintingKey) as? Bool ?? true
adBlocking = defaults.bool(forKey: adBlockingKey)
if let raw = defaults.string(forKey: cookiesPolicyKey),
let policy = CookiesPolicy(rawValue: raw)
Expand All @@ -271,6 +280,10 @@ class SettingsStore: ObservableObject {
sitePermissions =
Self.loadCodable([String: SitePermissionSettings].self, key: sitePermissionsKey) ?? [:]

adBlockFilterLists = FilterListCatalogService.shared.normalizedRecords(
from: Self.loadCodable([FilterListRecord].self, key: adBlockFilterListsKey) ?? []
)

customSearchEngines =
Self.loadCodable([CustomSearchEngine].self, key: customSearchEnginesKey) ?? []

Expand Down Expand Up @@ -358,10 +371,29 @@ class SettingsStore: ObservableObject {
objectWillChange.send()
}

func privacySettings(for containerId: UUID) -> SpacePrivacySettings {
Self.loadCodable(SpacePrivacySettings.self, key: keyForPrivacySettings(for: containerId))
?? legacyPrivacySettings
}

func setPrivacySettings(_ value: SpacePrivacySettings, for containerId: UUID) {
saveCodable(value, forKey: keyForPrivacySettings(for: containerId))
objectWillChange.send()
}

func notifySpacePrivacySettingsChanged(for containerId: UUID) {
NotificationCenter.default.post(
name: .spacePrivacySettingsChanged,
object: nil,
userInfo: ["containerId": containerId]
)
}

func removeContainerSettings(for containerId: UUID) {
defaults.removeObject(forKey: keyForDefaultSearch(for: containerId))
defaults.removeObject(forKey: keyForDefaultAI(for: containerId))
defaults.removeObject(forKey: keyForAutoClear(for: containerId))
defaults.removeObject(forKey: keyForPrivacySettings(for: containerId))
objectWillChange.send()
}

Expand All @@ -387,6 +419,32 @@ class SettingsStore: ObservableObject {
customSearchEngines = engines
}

// MARK: - Ad block filter catalog

func adBlockFilterList(id: String) -> FilterListRecord? {
adBlockFilterLists.first { $0.id == id }
}

func setAdBlockFilterLists(_ records: [FilterListRecord]) {
adBlockFilterLists = FilterListCatalogService.shared.normalizedRecords(from: records)
}

func upsertAdBlockFilterList(_ record: FilterListRecord) {
var records = adBlockFilterLists
if let index = records.firstIndex(where: { $0.id == record.id }) {
records[index] = record
} else {
records.append(record)
}
adBlockFilterLists = FilterListCatalogService.shared.normalizedRecords(from: records)
}

func removeAdBlockFilterList(id: String) {
adBlockFilterLists = FilterListCatalogService.shared.normalizedRecords(
from: adBlockFilterLists.filter { $0.id != id }
)
}

func removeCustomSearchEngine(withId id: String) {
customSearchEngines = customSearchEngines.filter { $0.id != id }
}
Expand Down Expand Up @@ -459,4 +517,13 @@ class SettingsStore: ObservableObject {
abs(lhs - value) < abs(rhs - value)
} ?? defaultSeconds
}

private var legacyPrivacySettings: SpacePrivacySettings {
SpacePrivacySettings(
blockThirdPartyTrackers: blockThirdPartyTrackers,
blockFingerprinting: blockFingerprinting,
adBlocking: adBlocking,
cookiesPolicy: cookiesPolicy
)
}
}
Loading
Loading