From bae059df30bc86269e3facfc46da880810deee2a Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 30 Oct 2023 22:02:55 +0100 Subject: [PATCH 1/7] Custom configuration settings based on new requirements and new MainMenu.swift --- .../AppConfigurationURLProvider.swift | 17 +++++++++- .../Common/Extensions/NSAlertExtension.swift | 10 ++++++ .../Utilities/UserDefaultsWrapper.swift | 1 + DuckDuckGo/Menus/MainMenu.swift | 4 ++- DuckDuckGo/Menus/MainMenuActions.swift | 34 ++++++++++++++++++- 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift index 333d16cb3c..beb35c65a9 100644 --- a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift +++ b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift @@ -21,6 +21,21 @@ import Configuration struct AppConfigurationURLProvider: ConfigurationURLProviding { + internal init(customPrivacyConfiguration: URL? = nil) { + if let customPrivacyConfiguration { + // Overwrite custom privacy configuration if provided + self.customPrivacyConfiguration = customPrivacyConfiguration + } + // Otherwise use the default or already stored custom configuration + } + + @UserDefaultsWrapper(key: .customConfigurationUrl, defaultValue: nil) + private var customPrivacyConfiguration: URL? + + mutating func resetToDefaultConfigurationUrl() { + self.customPrivacyConfiguration = nil + } + func url(for configuration: Configuration) -> URL { // URLs for privacyConfiguration and trackerDataSet shall match the ones in update_embedded.sh. // Danger checks that the URLs match on every PR. If the code changes, the regex that Danger uses may need an update. @@ -28,7 +43,7 @@ struct AppConfigurationURLProvider: ConfigurationURLProviding { case .bloomFilterBinary: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom.bin")! case .bloomFilterSpec: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom-spec.json")! case .bloomFilterExcludedDomains: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-false-positives.json")! - case .privacyConfiguration: return URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/config/v3/macos-config.json")! + case .privacyConfiguration: return customPrivacyConfiguration ?? URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/config/v3/macos-config.json")! case .surrogates: return URL(string: "https://staticcdn.duckduckgo.com/surrogates.txt")! case .trackerDataSet: return URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/v5/current/macos-tds.json")! // In archived repo, to be refactored shortly (https://staticcdn.duckduckgo.com/useragents/social_ctp_configuration.json) diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index d232fd5d08..12f8d2e8bd 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -201,6 +201,16 @@ extension NSAlert { return alert } + static func customConfigurationAlert() -> NSAlert { + let alert = NSAlert() + alert.messageText = "Set custom configuration URL:" + alert.addButton(withTitle: UserText.ok) + alert.addButton(withTitle: UserText.cancel) + alert.accessoryView = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24)) + alert.window.initialFirstResponder = alert.accessoryView + return alert + } + @discardableResult func runModal() async -> NSApplication.ModalResponse { await withCheckedContinuation { continuation in diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 5cfe2a744b..30ed95c4b4 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -133,6 +133,7 @@ public struct UserDefaultsWrapper { case loggingCategories = "logging.categories" case firstLaunchDate = "first.app.launch.date" + case customConfigurationUrl = "custom.configuration.url" // Network Protection diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index f2aead764d..e7b0ae5490 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -546,7 +546,9 @@ import Subscription NSMenuItem(title: "Show Pop Up Window", action: #selector(MainViewController.showPopUpWindow)) } NSMenuItem(title: "Remote Configuration") { - NSMenuItem(title: "Fetch Configuration Now", action: #selector(MainViewController.fetchConfigurationNow)) + NSMenuItem(title: "Reload Configuration Now", action: #selector(MainViewController.reloadConfigurationNow)) + NSMenuItem(title: "Set custom configuration URL…", action: #selector(MainViewController.setCustomConfigurationURL)) + NSMenuItem(title: "Reset configuration to default", action: #selector(MainViewController.resetConfigurationToDefault)) } NSMenuItem(title: "Sync") .submenu(SyncDebugMenu()) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 2cd87a9257..97719fa932 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -20,6 +20,7 @@ import BrowserServicesKit import Cocoa import Common import WebKit +import Configuration // Actions are sent to objects of responder chain @@ -733,10 +734,41 @@ extension MainViewController { EmailManager().resetEmailProtectionInContextPrompt() } - @objc func fetchConfigurationNow(_ sender: Any?) { + @objc func reloadConfigurationNow(_ sender: Any?) { ConfigurationManager.shared.forceRefresh() } + private func setConfigurationUrl(_ configurationUrl: URL?) { + var configurationProvider = AppConfigurationURLProvider(customPrivacyConfiguration: configurationUrl) + if configurationUrl == nil { + configurationProvider.resetToDefaultConfigurationUrl() + } + Configuration.setURLProvider(configurationProvider) + ConfigurationManager.shared.forceRefresh() + if let configurationUrl { + os_log("New configuration URL set to \(configurationUrl.absoluteString)", type: .info) + } else { + os_log("New configuration URL reset to default", type: .info) + } + } + + @objc func setCustomConfigurationURL(_ sender: Any?) { + let alert = NSAlert.customConfigurationAlert() + if alert.runModal() != .cancel { + guard let textField = alert.accessoryView as? NSTextField, + let newConfigurationUrl = URL(string: textField.stringValue) else { + os_log("Failed to set custom configuration URL", type: .error) + return + } + + setConfigurationUrl(newConfigurationUrl) + } + } + + @objc func resetConfigurationToDefault(_ sender: Any?) { + setConfigurationUrl(nil) + } + // MARK: - Developer Tools @objc func toggleDeveloperTools(_ sender: Any?) { From b8b2e9fc81d5c04dd0fd6f455cac8375be156f58 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 30 Oct 2023 22:46:14 +0100 Subject: [PATCH 2/7] Current configuration URL and the last update time added to the Debug menu --- .../AppConfigurationURLProvider.swift | 17 ++++++++++++++--- .../Configuration/ConfigurationManager.swift | 2 +- DuckDuckGo/Menus/MainMenu.swift | 13 +++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift index beb35c65a9..9c570ae5f4 100644 --- a/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift +++ b/DuckDuckGo/AppDelegate/AppConfigurationURLProvider.swift @@ -21,21 +21,32 @@ import Configuration struct AppConfigurationURLProvider: ConfigurationURLProviding { + // MARK: - Debug + internal init(customPrivacyConfiguration: URL? = nil) { if let customPrivacyConfiguration { // Overwrite custom privacy configuration if provided - self.customPrivacyConfiguration = customPrivacyConfiguration + self.customPrivacyConfiguration = customPrivacyConfiguration.absoluteString } // Otherwise use the default or already stored custom configuration } @UserDefaultsWrapper(key: .customConfigurationUrl, defaultValue: nil) - private var customPrivacyConfiguration: URL? + private var customPrivacyConfiguration: String? + + private var customPrivacyConfigurationUrl: URL? { + if let customPrivacyConfiguration { + return URL(string: customPrivacyConfiguration) + } + return nil + } mutating func resetToDefaultConfigurationUrl() { self.customPrivacyConfiguration = nil } + // MARK: - Main + func url(for configuration: Configuration) -> URL { // URLs for privacyConfiguration and trackerDataSet shall match the ones in update_embedded.sh. // Danger checks that the URLs match on every PR. If the code changes, the regex that Danger uses may need an update. @@ -43,7 +54,7 @@ struct AppConfigurationURLProvider: ConfigurationURLProviding { case .bloomFilterBinary: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom.bin")! case .bloomFilterSpec: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-bloom-spec.json")! case .bloomFilterExcludedDomains: return URL(string: "https://staticcdn.duckduckgo.com/https/https-mobile-v2-false-positives.json")! - case .privacyConfiguration: return customPrivacyConfiguration ?? URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/config/v3/macos-config.json")! + case .privacyConfiguration: return customPrivacyConfigurationUrl ?? URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/config/v3/macos-config.json")! case .surrogates: return URL(string: "https://staticcdn.duckduckgo.com/surrogates.txt")! case .trackerDataSet: return URL(string: "https://staticcdn.duckduckgo.com/trackerblocking/v5/current/macos-tds.json")! // In archived repo, to be refactored shortly (https://staticcdn.duckduckgo.com/useragents/social_ctp_configuration.json) diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index bd8440f874..a486bb4365 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -59,7 +59,7 @@ final class ConfigurationManager { static let queue: DispatchQueue = DispatchQueue(label: "Configuration Manager") @UserDefaultsWrapper(key: .configLastUpdated, defaultValue: .distantPast) - private var lastUpdateTime: Date + private(set) var lastUpdateTime: Date private var timerCancellable: AnyCancellable? private var lastRefreshCheckTime: Date = Date() diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index e7b0ae5490..b4e7e8a693 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -23,6 +23,7 @@ import Combine import OSLog // swiftlint:disable:this enforce_os_log_wrapper import SwiftUI import WebKit +import Configuration #if NETWORK_PROTECTION import NetworkProtection @@ -92,6 +93,8 @@ import Subscription // MARK: - Debug private var loggingMenu: NSMenu? + let customConfigurationUrlMenuItem = NSMenuItem(title: "Last Update Time", action: #selector(MainViewController.reloadConfigurationNow)) + let configurationDateAndTimeMenuItem = NSMenuItem(title: "Configuration URL", action: #selector(MainViewController.reloadConfigurationNow)) // MARK: - Help @@ -378,6 +381,7 @@ import Subscription updateBookmarksBarMenuItem() updateShortcutMenuItems() updateLoggingMenuItems() + updateRemoteConfigurationInfo() } // MARK: - Bookmarks @@ -546,6 +550,9 @@ import Subscription NSMenuItem(title: "Show Pop Up Window", action: #selector(MainViewController.showPopUpWindow)) } NSMenuItem(title: "Remote Configuration") { + customConfigurationUrlMenuItem + configurationDateAndTimeMenuItem + NSMenuItem.separator() NSMenuItem(title: "Reload Configuration Now", action: #selector(MainViewController.reloadConfigurationNow)) NSMenuItem(title: "Set custom configuration URL…", action: #selector(MainViewController.setCustomConfigurationURL)) NSMenuItem(title: "Reset configuration to default", action: #selector(MainViewController.resetConfigurationToDefault)) @@ -609,6 +616,12 @@ import Subscription } } + private func updateRemoteConfigurationInfo() { + let dateString = DateFormatter.localizedString(from: ConfigurationManager.shared.lastUpdateTime, dateStyle: .short, timeStyle: .medium) + configurationDateAndTimeMenuItem.title = "Last Update Time: \(dateString)" + customConfigurationUrlMenuItem.title = "Configuration URL: \(AppConfigurationURLProvider().url(for: .privacyConfiguration).absoluteString)" + } + @objc private func loggingMenuItemAction(_ sender: NSMenuItem) { guard let category = sender.identifier?.rawValue else { return } From c1984a99e903231a0e34386252efcde172e76a6e Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Mon, 30 Oct 2023 22:55:14 +0100 Subject: [PATCH 3/7] Removal of user script from selected tab added --- DuckDuckGo/Menus/MainMenu.swift | 3 +++ DuckDuckGo/Menus/MainMenuActions.swift | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index b4e7e8a693..12d691046f 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -557,6 +557,9 @@ import Subscription NSMenuItem(title: "Set custom configuration URL…", action: #selector(MainViewController.setCustomConfigurationURL)) NSMenuItem(title: "Reset configuration to default", action: #selector(MainViewController.resetConfigurationToDefault)) } + NSMenuItem(title: "User Scripts") { + NSMenuItem(title: "Remove user scripts from selected tab", action: #selector(MainViewController.removeUserScripts)) + } NSMenuItem(title: "Sync") .submenu(SyncDebugMenu()) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 97719fa932..a1ad816dd0 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -734,6 +734,12 @@ extension MainViewController { EmailManager().resetEmailProtectionInContextPrompt() } + @objc func removeUserScripts(_ sender: Any?) { + tabCollectionViewModel.selectedTab?.userContentController?.cleanUpBeforeClosing() + tabCollectionViewModel.selectedTab?.reload() + os_log("User scripts removed from the current tab", type: .info) + } + @objc func reloadConfigurationNow(_ sender: Any?) { ConfigurationManager.shared.forceRefresh() } From b49536311d979c7d1797d4c20eb8c401e96250cd Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Tue, 7 Nov 2023 10:08:04 +0100 Subject: [PATCH 4/7] Header menu items disabled --- DuckDuckGo/Menus/MainMenu.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 12d691046f..ef9c9fbb06 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -93,8 +93,8 @@ import Subscription // MARK: - Debug private var loggingMenu: NSMenu? - let customConfigurationUrlMenuItem = NSMenuItem(title: "Last Update Time", action: #selector(MainViewController.reloadConfigurationNow)) - let configurationDateAndTimeMenuItem = NSMenuItem(title: "Configuration URL", action: #selector(MainViewController.reloadConfigurationNow)) + let customConfigurationUrlMenuItem = NSMenuItem(title: "Last Update Time", action: nil) + let configurationDateAndTimeMenuItem = NSMenuItem(title: "Configuration URL", action: nil) // MARK: - Help From c081e3ae912ba4275e221a074e412b44d910fba8 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Tue, 7 Nov 2023 10:40:41 +0100 Subject: [PATCH 5/7] UX enhanced for setting configuration URL --- DuckDuckGo/Common/Extensions/NSAlertExtension.swift | 9 +++++++-- DuckDuckGo/Menus/MainMenuActions.swift | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 12f8d2e8bd..6c0bd356bd 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -201,13 +201,18 @@ extension NSAlert { return alert } - static func customConfigurationAlert() -> NSAlert { + static func customConfigurationAlert(configurationUrl: String) -> NSAlert { let alert = NSAlert() alert.messageText = "Set custom configuration URL:" alert.addButton(withTitle: UserText.ok) alert.addButton(withTitle: UserText.cancel) - alert.accessoryView = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24)) + let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24)) + textField.maximumNumberOfLines = 1 + textField.lineBreakMode = .byTruncatingTail + textField.stringValue = configurationUrl + alert.accessoryView = textField alert.window.initialFirstResponder = alert.accessoryView + textField.currentEditor()?.selectAll(nil) return alert } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index a1ad816dd0..4e48c90257 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -759,7 +759,8 @@ extension MainViewController { } @objc func setCustomConfigurationURL(_ sender: Any?) { - let alert = NSAlert.customConfigurationAlert() + let currentConfigurationURL = AppConfigurationURLProvider().url(for: .privacyConfiguration).absoluteString + let alert = NSAlert.customConfigurationAlert(configurationUrl: currentConfigurationURL) if alert.runModal() != .cancel { guard let textField = alert.accessoryView as? NSTextField, let newConfigurationUrl = URL(string: textField.stringValue) else { From 8740ffdf49b8773b1490fc43e924cd9397de795d Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Tue, 7 Nov 2023 15:58:23 +0100 Subject: [PATCH 6/7] SwiftLint --- DuckDuckGo/Menus/MainMenu.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index ee54ea8083..08439a3db4 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -534,6 +534,7 @@ import Subscription // MARK: - Debug + // swiftlint:disable:next function_body_length private func setupDebugMenu() -> NSMenu { let debugMenu = NSMenu(title: "Debug") { NSMenuItem(title: "Reset Data") { From 54f8f5be588856a372d66d9b676a7c3b19b232b6 Mon Sep 17 00:00:00 2001 From: Tomas Strba Date: Wed, 8 Nov 2023 13:44:55 +0100 Subject: [PATCH 7/7] Logging --- DuckDuckGo/Menus/MainMenuActions.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index dc0c825cf3..709c304cdb 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -732,10 +732,14 @@ extension MainViewController { } @objc func reloadConfigurationNow(_ sender: Any?) { + OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) + ConfigurationManager.shared.forceRefresh() } private func setConfigurationUrl(_ configurationUrl: URL?) { + OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) + var configurationProvider = AppConfigurationURLProvider(customPrivacyConfiguration: configurationUrl) if configurationUrl == nil { configurationProvider.resetToDefaultConfigurationUrl()