From fb20e204d0e9f3415913f3dbc2bafde2a9b2f605 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Thu, 7 Mar 2024 22:57:21 +0000 Subject: [PATCH 01/28] Change Feature.pushAction assert failure to a fatal error --- .../DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift index c596488888..7d33568aca 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift @@ -126,8 +126,7 @@ struct DataBrokerProtectionFeature: Subfeature { func pushAction(method: CCFSubscribeActionName, webView: WKWebView, params: Encodable) { guard let broker = broker else { - assertionFailure("Cannot continue without broker instance") - return + fatalError("Cannot continue without broker instance") } os_log("Pushing into WebView: %@ params %@", log: .action, method.rawValue, String(describing: params)) From b4e525dd8eb31fffe9511f1534d87e8c72ec4358 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 11 Mar 2024 19:48:49 +0000 Subject: [PATCH 02/28] Add error handling to Database --- .../DataBrokerProtectionDatabase.swift | 323 +++++++----------- 1 file changed, 120 insertions(+), 203 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 4007b2ebb0..2aae6b0221 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -20,30 +20,30 @@ import Foundation import Common protocol DataBrokerProtectionRepository { - func save(_ profile: DataBrokerProtectionProfile) async -> Bool - func fetchProfile() -> DataBrokerProtectionProfile? - func deleteProfileData() + func save(_ profile: DataBrokerProtectionProfile) async throws + func fetchProfile() throws -> DataBrokerProtectionProfile? + func deleteProfileData() throws - func fetchChildBrokers(for parentBroker: String) -> [DataBroker] + func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws - func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) -> BrokerProfileQueryData? - func fetchAllBrokerProfileQueryData() -> [BrokerProfileQueryData] - func fetchExtractedProfiles(for brokerId: Int64) -> [ExtractedProfile] + func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? + func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] + func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) - func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws + func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws - func add(_ historyEvent: HistoryEvent) - func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) -> HistoryEvent? - func hasMatches() -> Bool + func add(_ historyEvent: HistoryEvent) throws + func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? + func hasMatches() throws -> Bool - func fetchAttemptInformation(for extractedProfileId: Int64) -> AttemptInformation? - func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) + func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? + func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws } final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { @@ -57,52 +57,29 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { self.vault = vault } - func save(_ profile: DataBrokerProtectionProfile) async -> Bool { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - - if try vault.fetchProfile(with: Self.profileId) != nil { - try await updateProfile(profile, vault: vault) - } else { - try await saveNewProfile(profile, vault: vault) - } - - return true - } catch { - os_log("Database error: saveProfile, error: %{public}@", log: .error, error.localizedDescription) + func save(_ profile: DataBrokerProtectionProfile) async throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return false + if try vault.fetchProfile(with: Self.profileId) != nil { + try await updateProfile(profile, vault: vault) + } else { + try await saveNewProfile(profile, vault: vault) } } - public func fetchProfile() -> DataBrokerProtectionProfile? { - do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.fetchProfile(with: Self.profileId) - } catch { - os_log("Database error: fetchProfile, error: %{public}@", log: .error, error.localizedDescription) - return nil - } + public func fetchProfile() throws -> DataBrokerProtectionProfile? { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchProfile(with: Self.profileId) } - public func deleteProfileData() { - do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.deleteProfileData() - } catch { - os_log("Database error: removeProfileData, error: %{public}@", log: .error, error.localizedDescription) - return - } + public func deleteProfileData() throws { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.deleteProfileData() } - func fetchChildBrokers(for parentBroker: String) -> [DataBroker] { - do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.fetchChildBrokers(for: parentBroker) - } catch { - os_log("Database error: fetchChildBrokers, error: %{public}@", log: .error, error.localizedDescription) - return [DataBroker]() - } + func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchChildBrokers(for: parentBroker) } func save(_ extractedProfile: ExtractedProfile, brokerId: Int64, profileQueryId: Int64) throws -> Int64 { @@ -111,147 +88,101 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) } - func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) -> BrokerProfileQueryData? { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - if let broker = try vault.fetchBroker(with: brokerId), - let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), - let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) { - - let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - - return BrokerProfileQueryData( - dataBroker: broker, - profileQuery: profileQuery, - scanOperationData: scanOperation, - optOutOperationsData: optOutOperations - ) - } else { - // We should throw here. The caller probably needs to know that some of the - // models he was looking for where not found. This will be worked on the error handling task/project. - return nil - } - } catch { - os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + guard let broker = try vault.fetchBroker(with: brokerId), + let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), + let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { + // TODO perhaps this should error if we query for things that don't exist? return nil } - } - func fetchExtractedProfiles(for brokerId: Int64) -> [ExtractedProfile] { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - return try vault.fetchExtractedProfiles(for: brokerId) - } catch { - os_log("Database error: fetchExtractedProfiles for scan, error: %{public}@", log: .error, error.localizedDescription) - return [ExtractedProfile]() - } + return BrokerProfileQueryData( + dataBroker: broker, + profileQuery: profileQuery, + scanOperationData: scanOperation, + optOutOperationsData: optOutOperations + ) } - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchExtractedProfiles(for: brokerId) + } - try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) - } catch { - os_log("Database error: updatePreferredRunDate for scan, error: %{public}@", log: .error, error.localizedDescription) - } + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updatePreferredRunDate( - date, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId - ) - } catch { - os_log("Database error: updatePreferredRunDate for optOut, error: %{public}@", log: .error, error.localizedDescription) - } + try vault.updatePreferredRunDate( + date, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId) } - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - - try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) - } catch { - os_log("Database error: updateLastRunDate for scan, error: %{public}@", log: .error, error.localizedDescription) - } + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updateLastRunDate( - date, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId - ) - } catch { - os_log("Database error: updateLastRunDate for optOut, error: %{public}@", log: .error, error.localizedDescription) - } + try vault.updateLastRunDate( + date, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId + ) } - func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - - try vault.updateRemovedDate(for: extractedProfileId, with: date) - } catch { - os_log("Database error: updateRemoveDate, error: %{public}@", log: .error, error.localizedDescription) - } + func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updateRemovedDate(for: extractedProfileId, with: date) } - func add(_ historyEvent: HistoryEvent) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + func add(_ historyEvent: HistoryEvent) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - if let extractedProfileId = historyEvent.extractedProfileId { - try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) - } else { - try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) - } - } catch { - os_log("Database error: addHistoryEvent, error: %{public}@", log: .error, error.localizedDescription) + if let extractedProfileId = historyEvent.extractedProfileId { + try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) + } else { + try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) } } - func fetchAllBrokerProfileQueryData() -> [BrokerProfileQueryData] { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - let brokers = try vault.fetchAllBrokers() - let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) - var brokerProfileQueryDataList = [BrokerProfileQueryData]() - - for broker in brokers { - for profileQuery in profileQueries { - if let brokerId = broker.id, let profileQueryId = profileQuery.id { - guard let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { continue } - let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - - let brokerProfileQueryData = BrokerProfileQueryData( - dataBroker: broker, - profileQuery: profileQuery, - scanOperationData: scanOperation, - optOutOperationsData: optOutOperations - ) - - brokerProfileQueryDataList.append(brokerProfileQueryData) - } + func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let brokers = try vault.fetchAllBrokers() + let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) + var brokerProfileQueryDataList = [BrokerProfileQueryData]() + + for broker in brokers { + for profileQuery in profileQueries { + if let brokerId = broker.id, let profileQueryId = profileQuery.id { + guard let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { continue } + let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) + + let brokerProfileQueryData = BrokerProfileQueryData( + dataBroker: broker, + profileQuery: profileQuery, + scanOperationData: scanOperation, + optOutOperationsData: optOutOperations + ) + + brokerProfileQueryDataList.append(brokerProfileQueryData) } } - - return brokerProfileQueryDataList - } catch { - os_log("Database error: fetchAllBrokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) - return [BrokerProfileQueryData]() } + + return brokerProfileQueryDataList } func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws { @@ -264,29 +195,19 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { preferredRunDate: optOut.preferredRunDate) } - func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) -> HistoryEvent? { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) + func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) - return events.max(by: { $0.date < $1.date }) - } catch { - os_log("Database error: fetchLastEvent, error: %{public}@", log: .error, error.localizedDescription) - return nil - } + return events.max(by: { $0.date < $1.date }) } - func hasMatches() -> Bool { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.hasMatches() - } catch { - os_log("Database error: wereThereAnyMatches, error: %{public}@", log: .error, error.localizedDescription) - return false - } + func hasMatches() throws -> Bool { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.hasMatches() } - func fetchAttemptInformation(for extractedProfileId: Int64) -> AttemptInformation? { + func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? { do { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) return try vault.fetchAttemptInformation(for: extractedProfileId) @@ -296,17 +217,13 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { } } - func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.save(extractedProfileId: extractedProfileId, - attemptUUID: attemptUUID, - dataBroker: dataBroker, - lastStageDate: lastStageDate, - startTime: startTime) - } catch { - os_log("Database error: addAttempt, error: %{public}@", log: .error, error.localizedDescription) - } + func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.save(extractedProfileId: extractedProfileId, + attemptUUID: attemptUUID, + dataBroker: dataBroker, + lastStageDate: lastStageDate, + startTime: startTime) } } @@ -335,7 +252,7 @@ extension DataBrokerProtectionDatabase { let newProfileQueries = profile.profileQueries _ = try vault.save(profile: profile) - if let brokers = FileResources().fetchBrokerFromResourceFiles() { + if let brokers = try FileResources().fetchBrokerFromResourceFiles() { var brokerIDs = [Int64]() for broker in brokers { @@ -357,11 +274,11 @@ extension DataBrokerProtectionDatabase { let newProfileQueries = profile.profileQueries - let databaseBrokerProfileQueryData = fetchAllBrokerProfileQueryData() + let databaseBrokerProfileQueryData = try fetchAllBrokerProfileQueryData() let databaseProfileQueries = databaseBrokerProfileQueryData.map { $0.profileQuery } // The queries we need to create are the one that exist on the new ones but not in the database - let profileQueriesToCreate = Set(newProfileQueries).subtracting(Set(databaseProfileQueries)) + var profileQueriesToCreate = Set(newProfileQueries).subtracting(Set(databaseProfileQueries)) // The queries that need update exist in both the new and the database // We assume updated queries will be not deprecated @@ -432,7 +349,7 @@ extension DataBrokerProtectionDatabase { if !profile.deprecated { for brokerID in brokerIDs where !profile.deprecated { - updatePreferredRunDate(Date(), brokerId: brokerID, profileQueryId: profileQueryID) + try updatePreferredRunDate(Date(), brokerId: brokerID, profileQueryId: profileQueryID) } } } From 7e9b11c243fdea50c1707d481603231c270fb96d Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 11 Mar 2024 19:49:01 +0000 Subject: [PATCH 03/28] Add error handling to DataManager --- .../DataBrokerProtectionDataManager.swift | 67 ++++++++++--------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index 52eabbae92..73a0e4919c 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -24,19 +24,19 @@ public protocol DataBrokerProtectionDataManaging { var delegate: DataBrokerProtectionDataManagerDelegate? { get set } init(fakeBrokerFlag: DataBrokerDebugFlag) - func saveProfile(_ profile: DataBrokerProtectionProfile) async -> Bool - func fetchProfile(ignoresCache: Bool) -> DataBrokerProtectionProfile? - func fetchBrokerProfileQueryData(ignoresCache: Bool) async -> [BrokerProfileQueryData] - func hasMatches() -> Bool + func saveProfile(_ profile: DataBrokerProtectionProfile) async throws + func fetchProfile(ignoresCache: Bool) throws -> DataBrokerProtectionProfile? + func fetchBrokerProfileQueryData(ignoresCache: Bool) async throws -> [BrokerProfileQueryData] + func hasMatches() throws -> Bool } extension DataBrokerProtectionDataManaging { - func fetchProfile() -> DataBrokerProtectionProfile? { - fetchProfile(ignoresCache: false) + func fetchProfile() throws -> DataBrokerProtectionProfile? { + try fetchProfile(ignoresCache: false) } - func fetchBrokerProfileQueryData() async -> [BrokerProfileQueryData] { - await fetchBrokerProfileQueryData(ignoresCache: false) + func fetchBrokerProfileQueryData() async throws -> [BrokerProfileQueryData] { + try await fetchBrokerProfileQueryData(ignoresCache: false) } } @@ -58,21 +58,26 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { cache.delegate = self } - public func saveProfile(_ profile: DataBrokerProtectionProfile) async -> Bool { - let result = await database.save(profile) + public func saveProfile(_ profile: DataBrokerProtectionProfile) async throws { + do { + try await database.save(profile) + } catch { + // We should still invalidate the cache if the save fails + // TODO might be a good place to send a pixel + cache.invalidate() + throw error + } cache.invalidate() cache.profile = profile - - return result } - public func fetchProfile(ignoresCache: Bool = false) -> DataBrokerProtectionProfile? { + public func fetchProfile(ignoresCache: Bool = false) throws -> DataBrokerProtectionProfile? { if !ignoresCache, cache.profile != nil { os_log("Returning cached profile", log: .dataBrokerProtection) return cache.profile } - if let profile = database.fetchProfile() { + if let profile = try database.fetchProfile() { cache.profile = profile return profile } else { @@ -81,42 +86,40 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { } } - public func fetchBrokerProfileQueryData(ignoresCache: Bool = false) async -> [BrokerProfileQueryData] { + public func fetchBrokerProfileQueryData(ignoresCache: Bool = false) async throws -> [BrokerProfileQueryData] { if !ignoresCache, !cache.brokerProfileQueryData.isEmpty { os_log("Returning cached brokerProfileQueryData", log: .dataBrokerProtection) return cache.brokerProfileQueryData } - let queryData = database.fetchAllBrokerProfileQueryData() + let queryData = try database.fetchAllBrokerProfileQueryData() cache.brokerProfileQueryData = queryData return queryData } - public func hasMatches() -> Bool { - return database.hasMatches() + public func hasMatches() throws -> Bool { + return try database.hasMatches() } } extension DataBrokerProtectionDataManager: InMemoryDataCacheDelegate { - public func flushCache(profile: DataBrokerProtectionProfile?) async -> Bool { - guard let profile = profile else { return false } - let result = await saveProfile(profile) + public func flushCache(profile: DataBrokerProtectionProfile?) async throws { + guard let profile = profile else { return } + try await saveProfile(profile) delegate?.dataBrokerProtectionDataManagerDidUpdateData() - - return result } - public func removeAllData() { - database.deleteProfileData() + public func removeAllData() throws { + try database.deleteProfileData() delegate?.dataBrokerProtectionDataManagerDidDeleteData() } } public protocol InMemoryDataCacheDelegate: AnyObject { - func flushCache(profile: DataBrokerProtectionProfile?) async -> Bool - func removeAllData() + func flushCache(profile: DataBrokerProtectionProfile?) async throws + func removeAllData() throws } public final class InMemoryDataCache { @@ -138,10 +141,8 @@ public final class InMemoryDataCache { } extension InMemoryDataCache: DBPUICommunicationDelegate { - func saveProfile() async -> Bool { - _ = await delegate?.flushCache(profile: profile) - - return true + func saveProfile() async throws { + try await delegate?.flushCache(profile: profile) } private func indexForName(matching name: DBPUIUserProfileName, in profile: DataBrokerProtectionProfile) -> Int? { @@ -177,9 +178,9 @@ extension InMemoryDataCache: DBPUICommunicationDelegate { return DBPUIUserProfile(names: names, birthYear: profile.birthYear, addresses: addresses) } - func deleteProfileData() { + func deleteProfileData() throws { profile = emptyProfile - delegate?.removeAllData() + try delegate?.removeAllData() } func addNameToCurrentUserProfile(_ name: DBPUIUserProfileName) -> Bool { From f582ffe4e691400e0b32f6474ef9e64f4c192d33 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 11 Mar 2024 19:49:33 +0000 Subject: [PATCH 04/28] Add error handling to BrokerUpdater --- .../DataBrokerProtectionBrokerUpdater.swift | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index f3203334e2..ef0df8bc17 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -20,38 +20,40 @@ import Foundation import Common protocol ResourcesRepository { - func fetchBrokerFromResourceFiles() -> [DataBroker]? + func fetchBrokerFromResourceFiles() throws -> [DataBroker]? } final class FileResources: ResourcesRepository { + enum FileResourcesError: Error { + case bundleResourceURLNil + } + private let fileManager: FileManager init(fileManager: FileManager = .default) { self.fileManager = fileManager } - func fetchBrokerFromResourceFiles() -> [DataBroker]? { + func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { - return nil + //TODO haven't decided the best course of action here. Something has gone horribly wrong. Pixel? Assert? + // I don't think we have the means to fire a pixel here :Sob: + // "This property may not contain a path for non-standard bundle formats or for some older bundle formats" + throw FileResourcesError.bundleResourceURLNil } - do { - let fileURLs = try fileManager.contentsOfDirectory( - at: resourceURL, - includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) + let fileURLs = try fileManager.contentsOfDirectory( + at: resourceURL, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) - let brokerJSONFiles = fileURLs.filter { - $0.isJSON && !$0.hasFakePrefix - } - - return brokerJSONFiles.map(DataBroker.initFromResource(_:)) - } catch { - os_log("Error fetching brokers JSON files from resources", log: .dataBrokerProtection) - return nil + let brokerJSONFiles = fileURLs.filter { + $0.isJSON && !$0.hasFakePrefix } + + return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) } } @@ -118,7 +120,7 @@ public struct DataBrokerProtectionBrokerUpdater { } public func updateBrokers() { - guard let brokers = resources.fetchBrokerFromResourceFiles() else { return } + guard let brokers = try? resources.fetchBrokerFromResourceFiles() else { return } //TODO not this for broker in brokers { do { From 16b5efef4f2fdb127eb16df45dfef08085f4de29 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 11 Mar 2024 19:50:08 +0000 Subject: [PATCH 05/28] Change UICommunicationLayer methods to throw errors instead of returning a bool --- .../UI/DBPUICommunicationLayer.swift | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index 9f77b8a675..016129e10b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -23,9 +23,9 @@ import UserScript import Common protocol DBPUICommunicationDelegate: AnyObject { - func saveProfile() async -> Bool + func saveProfile() async throws func getUserProfile() -> DBPUIUserProfile? - func deleteProfileData() + func deleteProfileData() throws func addNameToCurrentUserProfile(_ name: DBPUIUserProfileName) -> Bool func setNameAtIndexInCurrentUserProfile(_ payload: DBPUINameAtIndex) -> Bool func removeNameAtIndexFromUserProfile(_ index: DBPUIIndex) -> Bool @@ -127,9 +127,12 @@ struct DBPUICommunicationLayer: Subfeature { func saveProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { os_log("Web UI requested to save the profile", log: .dataBrokerProtection) - let success = await delegate?.saveProfile() - - return DBPUIStandardResponse(version: Constants.version, success: success ?? false) + do { + try await delegate?.saveProfile() + return DBPUIStandardResponse(version: Constants.version, success: true) + } catch { + return DBPUIStandardResponse(version: Constants.version, success: false) + } } func getCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { @@ -141,8 +144,12 @@ struct DBPUICommunicationLayer: Subfeature { } func deleteUserProfileData(params: Any, original: WKScriptMessage) async throws -> Encodable? { - delegate?.deleteProfileData() - return DBPUIStandardResponse(version: Constants.version, success: true) + do { + try delegate?.deleteProfileData() + return DBPUIStandardResponse(version: Constants.version, success: true) + } catch { + return DBPUIStandardResponse(version: Constants.version, success: false) + } } func addNameToCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { From 499b46180cb005ef40a6b425338b94148e400b89 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 11 Mar 2024 19:50:46 +0000 Subject: [PATCH 06/28] Update classes to handle DB errors (either temporarily or permanently) to allow merging --- DuckDuckGo/DBP/DBPHomeViewController.swift | 10 +++-- .../DBP/DataBrokerProtectionAppEvents.swift | 4 +- .../DataBrokerProtectionFeatureDisabler.swift | 6 ++- .../SubscriptionFeatureAvailability.swift | 2 +- ...ataBrokerProtectionBackgroundManager.swift | 2 +- .../DataBrokerDatabaseBrowserViewModel.swift | 5 ++- .../DataBrokerRunCustomJSONViewModel.swift | 2 +- .../DataBrokerForceOptOutViewModel.swift | 11 +++++- .../Model/DBPUIViewModel.swift | 2 +- .../Model/DataBroker.swift | 8 ++-- ...taBrokerProfileQueryOperationManager.swift | 32 ++++++++-------- .../OperationPreferredDateUpdater.swift | 38 ++++++++++++------- .../MismatchCalculatorUseCase.swift | 5 ++- .../Operations/ScanOperation.swift | 2 +- ...DataBrokerProtectionEngagementPixels.swift | 2 +- .../DataBrokerProtectionProcessor.swift | 21 ++++++---- .../DataBrokerProtectionScheduler.swift | 3 +- .../DataBrokerProtectionViewController.swift | 2 +- 18 files changed, 96 insertions(+), 61 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index e590867c10..a5b9a03c99 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -83,9 +83,13 @@ final class DBPHomeViewController: NSViewController { attachDataBrokerContainerView() } - if dataBrokerProtectionManager.dataManager.fetchProfile() != nil { - let dbpDateStore = DefaultWaitlistActivationDateStore(source: .dbp) - dbpDateStore.updateLastActiveDate() + do { + if try dataBrokerProtectionManager.dataManager.fetchProfile() != nil { + let dbpDateStore = DefaultWaitlistActivationDateStore(source: .dbp) + dbpDateStore.updateLastActiveDate() + } + } catch { + os_log("Database error during DBPHomeViewController.viewDidLoad", log: .dataBrokerProtection) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift index 11292e7ac3..fe949f0edc 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionAppEvents.swift @@ -45,8 +45,8 @@ struct DataBrokerProtectionAppEvents { // If we don't have profileQueries it means there's no user profile saved in our DB // In this case, let's disable the agent and delete any left-over data because there's nothing for it to do - let profileQueries = await DataBrokerProtectionManager.shared.dataManager.fetchBrokerProfileQueryData(ignoresCache: true) - if profileQueries.count > 0 { + if let profileQueries = try? await DataBrokerProtectionManager.shared.dataManager.fetchBrokerProfileQueryData(ignoresCache: true), + profileQueries.count > 0 { restartBackgroundAgent(loginItemsManager: loginItemsManager) } else { featureVisibility.disableAndDeleteForWaitlistUsers() diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift index ddee393084..1afb5f7eeb 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift @@ -46,7 +46,11 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling scheduler.disableLoginItem() - dataManager.removeAllData() + do { + try dataManager.removeAllData() + } catch { + os_log("Database error during DataBrokerProtectionFeatureDisabler.disableAndDelete", log: .dataBrokerProtection) + } DataBrokerProtectionLoginItemPixels.fire(pixel: .dataBrokerDisableAndDeleteDaily, frequency: .dailyOnly) NotificationCenter.default.post(name: .dbpWasDisabled, object: nil) diff --git a/DuckDuckGo/Subscription/SubscriptionFeatureAvailability.swift b/DuckDuckGo/Subscription/SubscriptionFeatureAvailability.swift index 6599bd0ee9..d0aab53af3 100644 --- a/DuckDuckGo/Subscription/SubscriptionFeatureAvailability.swift +++ b/DuckDuckGo/Subscription/SubscriptionFeatureAvailability.swift @@ -61,7 +61,7 @@ struct DefaultSubscriptionFeatureAvailability: SubscriptionFeatureAvailability { private var isDBPActivated: Bool { #if DBP - return DataBrokerProtectionManager.shared.dataManager.fetchProfile(ignoresCache: true) != nil + return (try? DataBrokerProtectionManager.shared.dataManager.fetchProfile(ignoresCache: true)) != nil #else return false #endif diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift index 787a859f90..a10fb1fd97 100644 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift +++ b/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift @@ -79,7 +79,7 @@ public final class DataBrokerProtectionBackgroundManager { pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossible) // If there's no saved profile we don't need to start the scheduler - if dataManager.fetchProfile() != nil { + if (try? dataManager.fetchProfile()) != nil { scheduler.runQueuedOperations(showWebView: false) { [weak self] error in guard error == nil else { // Ideally we'd fire a pixel here, however at the moment the scheduler never ever returns an error diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift index 514957192a..02d5ef5bc8 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift @@ -48,7 +48,10 @@ final class DataBrokerDatabaseBrowserViewModel: ObservableObject { guard let dataManager = self.dataManager else { return } Task { - let data = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + guard let data = try? await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) else { + assertionFailure("DataManager error during DataBrokerDatavaseBrowserViewModel.updateTables") + return + } let profileBrokers = data.map { $0.dataBroker } let dataBrokers = Array(Set(profileBrokers)).sorted { $0.id ?? 0 < $1.id ?? 0 } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift index 9109f0baed..22c9c09fbc 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift @@ -76,7 +76,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject { captchaService: CaptchaService()) let fileResources = FileResources() - self.brokers = fileResources.fetchBrokerFromResourceFiles() ?? [DataBroker]() + self.brokers = (try? fileResources.fetchBrokerFromResourceFiles()) ?? [DataBroker]() // TODO not this } func runJSON(jsonString: String) { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift index 3668052d2f..f81761e347 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift @@ -30,7 +30,10 @@ final class DataBrokerForceOptOutViewModel: ObservableObject { private func loadNotRemovedOptOutData() { Task { @MainActor in - let brokerProfileData = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + guard let brokerProfileData = try? await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) else { + assertionFailure() + return + } self.optOutData = brokerProfileData .flatMap { profileData in profileData.optOutOperationsData.map { ($0, profileData.dataBroker.name) } @@ -70,6 +73,10 @@ struct OptOutViewData: Identifiable { private extension DataBrokerProtectionDataManager { func setAsRemoved(_ extractedProfileID: Int64) { - self.database.updateRemovedDate(Date(), on: extractedProfileID) + do { + try self.database.updateRemovedDate(Date(), on: extractedProfileID) + } catch { + assertionFailure() + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index ef1db86240..352cb93be8 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -77,6 +77,6 @@ extension DBPUIViewModel: DBPUIScanOps { } func updateCacheWithCurrentScans() async { - _ = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + _ = try? await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index e1a17f590b..e717d3f8b2 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -167,13 +167,11 @@ struct DataBroker: Codable, Sendable { return optOutType == .parentSiteOptOut } - static func initFromResource(_ url: URL) -> DataBroker { - // swiftlint:disable:next force_try - let data = try! Data(contentsOf: url) + static func initFromResource(_ url: URL) throws -> DataBroker { + let data = try Data(contentsOf: url) let jsonDecoder = JSONDecoder() jsonDecoder.dateDecodingStrategy = .millisecondsSince1970 - // swiftlint:disable:next force_try - let broker = try! jsonDecoder.decode(DataBroker.self, from: data) + let broker = try jsonDecoder.decode(DataBroker.self, from: data) return broker } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift index 73f08604f6..8bcebb945b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift @@ -112,7 +112,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } defer { - database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId) + try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId) os_log("Finished scan operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) notificationCenter.post(name: DataBrokerProtectionNotifications.didFinishScan, object: brokerProfileQueryData.dataBroker.name) } @@ -121,7 +121,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { do { let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .scanStarted) - database.add(event) + try database.add(event) let extractedProfiles = try await runner.scan(brokerProfileQueryData, stageCalculator: stageCalculator, showWebView: showWebView, shouldRunNextStep: shouldRunNextStep) os_log("Extracted profiles: %@", log: .dataBrokerProtection, extractedProfiles) @@ -129,19 +129,19 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { if !extractedProfiles.isEmpty { stageCalculator.fireScanSuccess(matchesFound: extractedProfiles.count) let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .matchesFound(count: extractedProfiles.count)) - database.add(event) + try database.add(event) for extractedProfile in extractedProfiles { // We check if the profile exists in the database. - let extractedProfilesForBroker = database.fetchExtractedProfiles(for: brokerId) + let extractedProfilesForBroker = try database.fetchExtractedProfiles(for: brokerId) let doesProfileExistsInDatabase = extractedProfilesForBroker.contains { $0.identifier == extractedProfile.identifier } // If the profile exists we do not create a new opt-out operation if doesProfileExistsInDatabase, let alreadyInDatabaseProfile = extractedProfilesForBroker.first(where: { $0.identifier == extractedProfile.identifier }), let id = alreadyInDatabaseProfile.id { // If it was removed in the past but was found again when scanning, it means it appearead again, so we reset the remove date. if alreadyInDatabaseProfile.removedDate != nil { - database.updateRemovedDate(nil, on: id) + try database.updateRemovedDate(nil, on: id) } os_log("Extracted profile already exists in database: %@", log: .dataBrokerProtection, id.description) @@ -170,7 +170,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } else { stageCalculator.fireScanFailed() let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .noMatchFound) - database.add(event) + try database.add(event) } // Check for removed profiles @@ -185,8 +185,8 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { for removedProfile in removedProfiles { if let extractedProfileId = removedProfile.id { let event = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutConfirmed) - database.add(event) - database.updateRemovedDate(Date(), on: extractedProfileId) + try database.add(event) + try database.updateRemovedDate(Date(), on: extractedProfileId) shouldSendProfileRemovedNotification = true try updateOperationDataDates( origin: .scan, @@ -199,7 +199,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { os_log("Profile removed from optOutsData: %@", log: .dataBrokerProtection, String(describing: removedProfile)) - if let attempt = database.fetchAttemptInformation(for: extractedProfileId), let attemptUUID = UUID(uuidString: attempt.attemptId) { + if let attempt = try database.fetchAttemptInformation(for: extractedProfileId), let attemptUUID = UUID(uuidString: attempt.attemptId) { let now = Date() let calculateDurationSinceLastStage = now.timeIntervalSince(attempt.lastStageDate) * 1000 let calculateDurationSinceStart = now.timeIntervalSince(attempt.startDate) * 1000 @@ -237,9 +237,9 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } private func sendProfileRemovedNotificationIfNecessary(userNotificationService: DataBrokerProtectionUserNotificationService, database: DataBrokerProtectionRepository) { - let savedExtractedProfiles = database.fetchAllBrokerProfileQueryData().flatMap { $0.extractedProfiles } - guard savedExtractedProfiles.count > 0 else { + guard let savedExtractedProfiles = try? database.fetchAllBrokerProfileQueryData().flatMap({ $0.extractedProfiles }), + savedExtractedProfiles.count > 0 else { return } @@ -286,7 +286,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { defer { os_log("Finished opt-out operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) - database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) do { try updateOperationDataDates( origin: .optOut, @@ -311,7 +311,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } do { - database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutStarted)) + try database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutStarted)) try await runner.optOut(profileQuery: brokerProfileQueryData, extractedProfile: extractedProfile, @@ -323,12 +323,12 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { updater.updateChildrenBrokerForParentBroker(brokerProfileQueryData.dataBroker, profileQueryId: profileQueryId) - database.addAttempt(extractedProfileId: extractedProfileId, + try database.addAttempt(extractedProfileId: extractedProfileId, attemptUUID: stageDurationCalculator.attemptId, dataBroker: stageDurationCalculator.dataBroker, lastStageDate: stageDurationCalculator.lastStateTime, startTime: stageDurationCalculator.startTime) - database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)) + try database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)) } catch { stageDurationCalculator.fireOptOutFailure() handleOperationError( @@ -383,7 +383,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } } - database.add(event) + try? database.add(event) do { try updateOperationDataDates( diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift index 1b27e2025d..b0a1e00f84 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift @@ -47,8 +47,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws { - guard let brokerProfileQuery = database.brokerProfileQueryData(for: brokerId, - and: profileQueryId) else { return } + guard let brokerProfileQuery = try database.brokerProfileQueryData(for: brokerId, + and: profileQueryId) else { return } try updateScanOperationDataDates(origin: origin, brokerId: brokerId, @@ -71,15 +71,20 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { /// 1, This method fetches scan operations with the profileQueryId and with child sites of parentBrokerId /// 2. Then for each one it updates the preferredRunDate of the scan to its confirm scan func updateChildrenBrokerForParentBroker(_ parentBroker: DataBroker, profileQueryId: Int64) { - let childBrokers = database.fetchChildBrokers(for: parentBroker.name) - - childBrokers.forEach { childBroker in - if let childBrokerId = childBroker.id { - let confirmOptOutScanDate = Date().addingTimeInterval(childBroker.schedulingConfig.confirmOptOutScan.hoursToSeconds) - database.updatePreferredRunDate(confirmOptOutScanDate, - brokerId: childBrokerId, - profileQueryId: profileQueryId) + do { + // Temporary do/catch to allow merging error handling work in stages + let childBrokers = try database.fetchChildBrokers(for: parentBroker.name) + + try childBrokers.forEach { childBroker in + if let childBrokerId = childBroker.id { + let confirmOptOutScanDate = Date().addingTimeInterval(childBroker.schedulingConfig.confirmOptOutScan.hoursToSeconds) + try database.updatePreferredRunDate(confirmOptOutScanDate, + brokerId: childBrokerId, + profileQueryId: profileQueryId) + } } + } catch { + os_log("Database error during updateChildrenBrokerForParentBroker", log: .dataBrokerProtection) } } @@ -147,10 +152,15 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64?) { - if let extractedProfileId = extractedProfileId { - database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) - } else { - database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + do { + // Temporary do/catch to allow merging error handling work in stages + if let extractedProfileId = extractedProfileId { + try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + } else { + try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + } + } catch { + os_log("Database error during updatePreferredRunDate", log: .dataBrokerProtection) } os_log("Updating preferredRunDate on operation with brokerId %{public}@ and profileQueryId %{public}@", log: .dataBrokerProtection, brokerId.description, profileQueryId.description) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift index ad3e963fe3..209cbb30b1 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift @@ -41,7 +41,10 @@ struct MismatchCalculatorUseCase { let pixelHandler: EventMapping func calculateMismatches() { - let brokerProfileQueryData = database.fetchAllBrokerProfileQueryData() + guard let brokerProfileQueryData = try? database.fetchAllBrokerProfileQueryData() else { + os_log("Database error during MismatchCalculatorUseCase.calculateMismatches", log: .dataBrokerProtection) + return + } let parentBrokerProfileQueryData = brokerProfileQueryData.filter { $0.dataBroker.parent == nil } for parent in parentBrokerProfileQueryData { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift index ac26510a64..bedb36dd99 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift @@ -57,7 +57,7 @@ final class ScanOperation: DataBrokerOperation { self.shouldRunNextStep = shouldRunNextStep } - func run(inputValue: Void, + func run(inputValue: InputValue, webViewHandler: WebViewHandler? = nil, actionsHandler: ActionsHandler? = nil, stageCalculator: StageDurationCalculator, // We do not need it for scans - for now. diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift index f00192e769..e825e0eb33 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift @@ -113,7 +113,7 @@ final class DataBrokerProtectionEngagementPixels { } func fireEngagementPixel(currentDate: Date = Date()) { - guard database.fetchProfile() != nil else { + guard (try? database.fetchProfile()) != nil else { os_log("No profile. We do not fire any pixel because we do not consider it an engaged user.", log: .dataBrokerProtection) return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift index 0eddaad17e..6d5f8602ed 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift @@ -116,14 +116,19 @@ final class DataBrokerProtectionProcessor { // This will fire the DAU/WAU/MAU pixels, engagementPixels.fireEngagementPixel() - let brokersProfileData = database.fetchAllBrokerProfileQueryData() - let dataBrokerOperationCollections = createDataBrokerOperationCollections(from: brokersProfileData, - operationType: operationType, - priorityDate: priorityDate, - showWebView: showWebView) - - for collection in dataBrokerOperationCollections { - operationQueue.addOperation(collection) + do { + // Temporary do/catch to allow merging error handling work in stages + let brokersProfileData = try database.fetchAllBrokerProfileQueryData() + let dataBrokerOperationCollections = createDataBrokerOperationCollections(from: brokersProfileData, + operationType: operationType, + priorityDate: priorityDate, + showWebView: showWebView) + + for collection in dataBrokerOperationCollections { + operationQueue.addOperation(collection) + } + } catch { + os_log("Database error during Processor.runOperations", log: .dataBrokerProtection) } operationQueue.addBarrierBlock { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift index 7ad31ed349..8825842cf8 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift @@ -177,7 +177,8 @@ public final class DefaultDataBrokerProtectionScheduler: DataBrokerProtectionSch self.userNotificationService.sendFirstScanCompletedNotification() - if self.dataManager.hasMatches() { + if let hasMatches = try? self.dataManager.hasMatches(), + hasMatches { self.userNotificationService.scheduleCheckInNotificationIfPossible() } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift index ce258c3565..c525617c8d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift @@ -51,7 +51,7 @@ final public class DataBrokerProtectionViewController: NSViewController { webView: webView) Task { - _ = dataManager.fetchProfile(ignoresCache: true) + _ = try? dataManager.fetchProfile(ignoresCache: true) } super.init(nibName: nil, bundle: nil) From e4469ceeb7d2045af122b5d3b60090b32878e4ba Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 18:55:02 +0000 Subject: [PATCH 07/28] Add os logging back to Database --- .../DataBrokerProtectionDatabase.swift | 276 ++++++++++++------ 1 file changed, 183 insertions(+), 93 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 2aae6b0221..873d3e9ca0 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -58,153 +58,238 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { } func save(_ profile: DataBrokerProtectionProfile) async throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - if try vault.fetchProfile(with: Self.profileId) != nil { - try await updateProfile(profile, vault: vault) - } else { - try await saveNewProfile(profile, vault: vault) + if try vault.fetchProfile(with: Self.profileId) != nil { + try await updateProfile(profile, vault: vault) + } else { + try await saveNewProfile(profile, vault: vault) + } + } catch { + os_log("Database error: save profile, error: %{public}@", log: .error, error.localizedDescription) + throw error } } public func fetchProfile() throws -> DataBrokerProtectionProfile? { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.fetchProfile(with: Self.profileId) + do { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchProfile(with: Self.profileId) + } catch { + os_log("Database error: fetchProfile, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } public func deleteProfileData() throws { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.deleteProfileData() + do { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.deleteProfileData() + } catch { + os_log("Database error: deleteProfileData, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.fetchChildBrokers(for: parentBroker) + do { + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchChildBrokers(for: parentBroker) + } catch { + os_log("Database error: fetchChildBrokers, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func save(_ extractedProfile: ExtractedProfile, brokerId: Int64, profileQueryId: Int64) throws -> Int64 { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) + return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) + } catch { + os_log("Database error: extractedProfile, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - guard let broker = try vault.fetchBroker(with: brokerId), - let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), - let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { - // TODO perhaps this should error if we query for things that don't exist? - return nil - } + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + guard let broker = try vault.fetchBroker(with: brokerId), + let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), + let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { + // TODO perhaps this should error if we query for things that don't exist? + return nil + } - let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) + let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - return BrokerProfileQueryData( - dataBroker: broker, - profileQuery: profileQuery, - scanOperationData: scanOperation, - optOutOperationsData: optOutOperations - ) + return BrokerProfileQueryData( + dataBroker: broker, + profileQuery: profileQuery, + scanOperationData: scanOperation, + optOutOperationsData: optOutOperations + ) + } catch { + os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.fetchExtractedProfiles(for: brokerId) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.fetchExtractedProfiles(for: brokerId) + } catch { + os_log("Database error: fetchExtractedProfiles, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + } catch { + os_log("Database error: updatePreferredRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updatePreferredRunDate( - date, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId) + try vault.updatePreferredRunDate( + date, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId) + } catch { + os_log("Database error: updatePreferredRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + } catch { + os_log("Database error: updateLastRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - - try vault.updateLastRunDate( - date, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId - ) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + + try vault.updateLastRunDate( + date, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId + ) + } catch { + os_log("Database error: updateLastRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.updateRemovedDate(for: extractedProfileId, with: date) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.updateRemovedDate(for: extractedProfileId, with: date) + } catch { + os_log("Database error: updateRemovedDate, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func add(_ historyEvent: HistoryEvent) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - if let extractedProfileId = historyEvent.extractedProfileId { - try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) - } else { - try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) + if let extractedProfileId = historyEvent.extractedProfileId { + try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) + } else { + try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) + } + } catch { + os_log("Database error: add historyEvent, error: %{public}@", log: .error, error.localizedDescription) + throw error } } func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - let brokers = try vault.fetchAllBrokers() - let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) - var brokerProfileQueryDataList = [BrokerProfileQueryData]() - - for broker in brokers { - for profileQuery in profileQueries { - if let brokerId = broker.id, let profileQueryId = profileQuery.id { - guard let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { continue } - let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - - let brokerProfileQueryData = BrokerProfileQueryData( - dataBroker: broker, - profileQuery: profileQuery, - scanOperationData: scanOperation, - optOutOperationsData: optOutOperations - ) - - brokerProfileQueryDataList.append(brokerProfileQueryData) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let brokers = try vault.fetchAllBrokers() + let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) + var brokerProfileQueryDataList = [BrokerProfileQueryData]() + + for broker in brokers { + for profileQuery in profileQueries { + if let brokerId = broker.id, let profileQueryId = profileQuery.id { + guard let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { continue } + let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) + + let brokerProfileQueryData = BrokerProfileQueryData( + dataBroker: broker, + profileQuery: profileQuery, + scanOperationData: scanOperation, + optOutOperationsData: optOutOperations + ) + + brokerProfileQueryDataList.append(brokerProfileQueryData) + } } } - } - return brokerProfileQueryDataList + return brokerProfileQueryDataList + } catch { + os_log("Database error: fetchAllBrokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.save(brokerId: optOut.brokerId, - profileQueryId: optOut.profileQueryId, - extractedProfile: extractedProfile, - lastRunDate: optOut.lastRunDate, - preferredRunDate: optOut.preferredRunDate) + try vault.save(brokerId: optOut.brokerId, + profileQueryId: optOut.profileQueryId, + extractedProfile: extractedProfile, + lastRunDate: optOut.lastRunDate, + preferredRunDate: optOut.preferredRunDate) + } catch { + os_log("Database error: saveOptOutOperation, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) - return events.max(by: { $0.date < $1.date }) + return events.max(by: { $0.date < $1.date }) + } catch { + os_log("Database error: fetchLastEvent, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func hasMatches() throws -> Bool { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.hasMatches() + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + return try vault.hasMatches() + } catch { + os_log("Database error: hasMatches, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? { @@ -213,17 +298,22 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return try vault.fetchAttemptInformation(for: extractedProfileId) } catch { os_log("Database error: fetchAttemptInformation, error: %{public}@", log: .error, error.localizedDescription) - return nil + throw error } } func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - try vault.save(extractedProfileId: extractedProfileId, - attemptUUID: attemptUUID, - dataBroker: dataBroker, - lastStageDate: lastStageDate, - startTime: startTime) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + try vault.save(extractedProfileId: extractedProfileId, + attemptUUID: attemptUUID, + dataBroker: dataBroker, + lastStageDate: lastStageDate, + startTime: startTime) + } catch { + os_log("Database error: addAttempt, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } } From edb95e9435a9dd3db1508203f2461e66bea16508 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 20:27:25 +0000 Subject: [PATCH 08/28] Add inert SecureVault error reporter to DBP database --- .../DataBrokerProtectionDatabase.swift | 45 ++++++++++--------- ...erProtectionSecureVaultErrorReporter.swift | 44 ++++++++++++++++++ 2 files changed, 69 insertions(+), 20 deletions(-) create mode 100644 LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 873d3e9ca0..bf9505cd7f 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -18,6 +18,7 @@ import Foundation import Common +import SecureStorage protocol DataBrokerProtectionRepository { func save(_ profile: DataBrokerProtectionProfile) async throws @@ -51,15 +52,19 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { private let fakeBrokerFlag: DataBrokerDebugFlag private let vault: (any DataBrokerProtectionSecureVault)? + private let secureVaultErrorReporter: SecureVaultErrorReporting? - init(fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker(), vault: (any DataBrokerProtectionSecureVault)? = nil) { + init(fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker(), + vault: (any DataBrokerProtectionSecureVault)? = nil, + secureVaultErrorReporter: SecureVaultErrorReporting? = DataBrokerProtectionSecureVaultErrorReporter.shared) { self.fakeBrokerFlag = fakeBrokerFlag self.vault = vault + self.secureVaultErrorReporter = secureVaultErrorReporter } func save(_ profile: DataBrokerProtectionProfile) async throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) if try vault.fetchProfile(with: Self.profileId) != nil { try await updateProfile(profile, vault: vault) @@ -74,7 +79,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { public func fetchProfile() throws -> DataBrokerProtectionProfile? { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchProfile(with: Self.profileId) } catch { os_log("Database error: fetchProfile, error: %{public}@", log: .error, error.localizedDescription) @@ -84,7 +89,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { public func deleteProfileData() throws { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.deleteProfileData() } catch { os_log("Database error: deleteProfileData, error: %{public}@", log: .error, error.localizedDescription) @@ -94,7 +99,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchChildBrokers(for: parentBroker) } catch { os_log("Database error: fetchChildBrokers, error: %{public}@", log: .error, error.localizedDescription) @@ -104,7 +109,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func save(_ extractedProfile: ExtractedProfile, brokerId: Int64, profileQueryId: Int64) throws -> Int64 { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) } catch { @@ -115,7 +120,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) guard let broker = try vault.fetchBroker(with: brokerId), let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { @@ -139,7 +144,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchExtractedProfiles(for: brokerId) } catch { os_log("Database error: fetchExtractedProfiles, error: %{public}@", log: .error, error.localizedDescription) @@ -149,7 +154,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { os_log("Database error: updatePreferredRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) @@ -159,7 +164,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updatePreferredRunDate( date, @@ -174,7 +179,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { os_log("Database error: updateLastRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) @@ -184,7 +189,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateLastRunDate( date, @@ -200,7 +205,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateRemovedDate(for: extractedProfileId, with: date) } catch { os_log("Database error: updateRemovedDate, error: %{public}@", log: .error, error.localizedDescription) @@ -210,7 +215,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func add(_ historyEvent: HistoryEvent) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) if let extractedProfileId = historyEvent.extractedProfileId { try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) @@ -225,7 +230,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) let brokers = try vault.fetchAllBrokers() let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) var brokerProfileQueryDataList = [BrokerProfileQueryData]() @@ -257,7 +262,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.save(brokerId: optOut.brokerId, profileQueryId: optOut.profileQueryId, @@ -272,7 +277,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) return events.max(by: { $0.date < $1.date }) @@ -284,7 +289,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func hasMatches() throws -> Bool { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.hasMatches() } catch { os_log("Database error: hasMatches, error: %{public}@", log: .error, error.localizedDescription) @@ -294,7 +299,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchAttemptInformation(for: extractedProfileId) } catch { os_log("Database error: fetchAttemptInformation, error: %{public}@", log: .error, error.localizedDescription) @@ -304,7 +309,7 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.save(extractedProfileId: extractedProfileId, attemptUUID: attemptUUID, dataBroker: dataBroker, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift new file mode 100644 index 0000000000..7b08f19d42 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift @@ -0,0 +1,44 @@ +// +// DataBrokerProtectionSecureVaultErrorReporter.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +import Foundation +import BrowserServicesKit +import SecureStorage +import PixelKit +import Common + +final class DataBrokerProtectionSecureVaultErrorReporter: SecureVaultErrorReporting { + static let shared = DataBrokerProtectionSecureVaultErrorReporter(pixelHandler: DataBrokerProtectionPixelsHandler()) + + let pixelHandler: EventMapping + private init(pixelHandler: EventMapping) { + self.pixelHandler = pixelHandler + } + + func secureVaultInitFailed(_ error: SecureStorageError) { +// Actual pixels to follow once privacy triage is done +// switch error { +// case .initFailed, .failedToOpenDatabase: +// pixelHandler.fire(.debug(event: .secureVaultInitError, error: error)) +// default: +// Pixel.fire(.debug(event: .secureVaultError, error: error)) +// } + } + +} From 2a4d0cb68c001348160b309cd20f54b2db10350d Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 20:30:34 +0000 Subject: [PATCH 09/28] Fix rogue var --- .../Database/DataBrokerProtectionDatabase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index bf9505cd7f..7611d18b0d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -373,7 +373,7 @@ extension DataBrokerProtectionDatabase { let databaseProfileQueries = databaseBrokerProfileQueryData.map { $0.profileQuery } // The queries we need to create are the one that exist on the new ones but not in the database - var profileQueriesToCreate = Set(newProfileQueries).subtracting(Set(databaseProfileQueries)) + let profileQueriesToCreate = Set(newProfileQueries).subtracting(Set(databaseProfileQueries)) // The queries that need update exist in both the new and the database // We assume updated queries will be not deprecated From fa24c7ea32fa79e5a5c83b39a3116210f832cdb3 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 20:34:32 +0000 Subject: [PATCH 10/28] Remove TODO --- .../DebugUI/DataBrokerRunCustomJSONViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift index 22c9c09fbc..5e3f3fae5a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift @@ -76,7 +76,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject { captchaService: CaptchaService()) let fileResources = FileResources() - self.brokers = (try? fileResources.fetchBrokerFromResourceFiles()) ?? [DataBroker]() // TODO not this + self.brokers = (try? fileResources.fetchBrokerFromResourceFiles()) ?? [DataBroker]() } func runJSON(jsonString: String) { From 331988581e22c9dafc9b37a56c00bdc98c82e570 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 20:48:27 +0000 Subject: [PATCH 11/28] Add various os_log ing for errors --- .../Model/DBPUIViewModel.swift | 7 ++++++- .../DataBrokerProtection/Model/DataBroker.swift | 16 +++++++++++----- .../DataBrokerProtectionBrokerUpdater.swift | 14 ++++++++++---- .../UI/DBPUICommunicationLayer.swift | 2 ++ 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index 352cb93be8..5b055355f0 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -20,6 +20,7 @@ import Foundation import Combine import WebKit import BrowserServicesKit +import Common protocol DBPUIScanOps: AnyObject { func startScan() -> Bool @@ -77,6 +78,10 @@ extension DBPUIViewModel: DBPUIScanOps { } func updateCacheWithCurrentScans() async { - _ = try? await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + do { + _ = try await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + } catch { + os_log("DBPUIViewModel updateCacheWithCurrentScans, error: %{public}@", log: .error, error.localizedDescription) + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index e717d3f8b2..e6945bc220 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -17,6 +17,7 @@ // import Foundation +import Common struct DataBrokerScheduleConfig: Codable { let retryError: Int @@ -168,11 +169,16 @@ struct DataBroker: Codable, Sendable { } static func initFromResource(_ url: URL) throws -> DataBroker { - let data = try Data(contentsOf: url) - let jsonDecoder = JSONDecoder() - jsonDecoder.dateDecodingStrategy = .millisecondsSince1970 - let broker = try jsonDecoder.decode(DataBroker.self, from: data) - return broker + do { + let data = try Data(contentsOf: url) + let jsonDecoder = JSONDecoder() + jsonDecoder.dateDecodingStrategy = .millisecondsSince1970 + let broker = try jsonDecoder.decode(DataBroker.self, from: data) + return broker + } catch { + os_log("DataBroker initFromResource, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index ef0df8bc17..4797bbbb90 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -37,9 +37,8 @@ final class FileResources: ResourcesRepository { func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { - //TODO haven't decided the best course of action here. Something has gone horribly wrong. Pixel? Assert? - // I don't think we have the means to fire a pixel here :Sob: - // "This property may not contain a path for non-standard bundle formats or for some older bundle formats" + assertionFailure() + os_log("DataBrokerProtectionUpdater FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) throw FileResourcesError.bundleResourceURLNil } @@ -120,7 +119,14 @@ public struct DataBrokerProtectionBrokerUpdater { } public func updateBrokers() { - guard let brokers = try? resources.fetchBrokerFromResourceFiles() else { return } //TODO not this + let brokers: [DataBroker]? + do { + brokers = try resources.fetchBrokerFromResourceFiles() + } catch { + os_log("DataBrokerProtectionBrokerUpdater updateBrokers, error: %{public}@", log: .error, error.localizedDescription) + return + } + guard let brokers = brokers else { return } for broker in brokers { do { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index 016129e10b..f99df1e614 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -131,6 +131,7 @@ struct DBPUICommunicationLayer: Subfeature { try await delegate?.saveProfile() return DBPUIStandardResponse(version: Constants.version, success: true) } catch { + os_log("DBPUICommunicationLayer saveProfile, error: %{public}@", log: .error, error.localizedDescription) return DBPUIStandardResponse(version: Constants.version, success: false) } } @@ -148,6 +149,7 @@ struct DBPUICommunicationLayer: Subfeature { try delegate?.deleteProfileData() return DBPUIStandardResponse(version: Constants.version, success: true) } catch { + os_log("DBPUICommunicationLayer deleteUserProfileData, error: %{public}@", log: .error, error.localizedDescription) return DBPUIStandardResponse(version: Constants.version, success: false) } } From 6fb5b8a640e342c2486e6abc5fcfb539da4b775c Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 20:52:08 +0000 Subject: [PATCH 12/28] Add custom error for if data is not in DB --- .../CCF/DataBrokerProtectionErrors.swift | 3 +++ .../Database/DataBrokerProtectionDatabase.swift | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift index 63ebe88d77..d48373aeb0 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift @@ -38,6 +38,7 @@ public enum DataBrokerProtectionError: Error, Equatable, Codable { case solvingCaptchaWithCallbackError case cantCalculatePreferredRunDate case httpError(code: Int) + case dataNotInDatabase static func parse(params: Any) -> DataBrokerProtectionError { let errorDataResult = try? JSONSerialization.data(withJSONObject: params) @@ -88,6 +89,8 @@ extension DataBrokerProtectionError { return "cantCalculatePreferredRunDate" case .httpError: return "httpError" + case .dataNotInDatabase: + return "dataNotInDatabase" } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 7611d18b0d..3f8ef335f5 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -124,8 +124,9 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { guard let broker = try vault.fetchBroker(with: brokerId), let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { - // TODO perhaps this should error if we query for things that don't exist? - return nil + let error = DataBrokerProtectionError.dataNotInDatabase + os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + throw error } let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) From 40f3dfff2ad8a8ea80a7284ba77e2b3c5dce1714 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:14:53 +0000 Subject: [PATCH 13/28] Fix various linting issues --- .../Database/DataBrokerProtectionDataManager.swift | 1 - .../DataBrokerProtectionSecureVaultErrorReporter.swift | 2 -- .../DataBrokerProtectionProfileTests.swift | 6 +++--- .../Tests/DataBrokerProtectionTests/Mocks.swift | 4 +--- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index 73a0e4919c..b7c731e20d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -63,7 +63,6 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { try await database.save(profile) } catch { // We should still invalidate the cache if the save fails - // TODO might be a good place to send a pixel cache.invalidate() throw error } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift index 7b08f19d42..34694e848d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift @@ -16,7 +16,6 @@ // limitations under the License. // - import Foundation import BrowserServicesKit import SecureStorage @@ -40,5 +39,4 @@ final class DataBrokerProtectionSecureVaultErrorReporter: SecureVaultErrorReport // Pixel.fire(.debug(event: .secureVaultError, error: error)) // } } - } diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift index 616e1a5fa9..cc61e72212 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift @@ -175,7 +175,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _=await database.save(profile) + _ = try? await database.save(profile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) XCTAssertFalse(vault.wasDeleteProfileQueryCalled) @@ -217,7 +217,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _=await database.save(newProfile) + _ = try? await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertTrue(vault.wasUpdateProfileQueryCalled) @@ -259,7 +259,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _ = await database.save(newProfile) + _ = try? await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 06839d4278..cd899b6f89 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -675,10 +675,8 @@ final class MockDatabase: DataBrokerProtectionRepository { callsList.filter { $0 }.count > 0 // If one value is true. The database was called } - func save(_ profile: DataBrokerProtectionProfile) -> Bool { + func save(_ profile: DataBrokerProtectionProfile) throws { wasSaveProfileCalled = true - - return true } func fetchProfile() -> DataBrokerProtectionProfile? { From 70a518ad7a507f47e208802a66d7363d5f537127 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:28:44 +0000 Subject: [PATCH 14/28] Fix weird db file issue --- .../DataBrokerProtectionDatabase.swift | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 37dc97f2d1..ecccb6631e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -481,43 +481,3 @@ extension DataBrokerProtectionDatabase { } } } - } - - // Create - if !profileQueriesToCreate.isEmpty { - try initializeDatabaseForProfile( - profileId: Self.profileId, - vault: vault, - brokerIDs: brokerIDs, - profileQueries: Array(profileQueriesToCreate) - ) - } - } - - private func deleteProfileQueries(_ profileQueries: [ProfileQuery], - profileID: Int64, - vault: any DataBrokerProtectionSecureVault) throws { - - for profile in profileQueries { - try vault.delete(profileQuery: profile, profileId: profileID) - } - } - - private func updateProfileQueries(_ profileQueries: [ProfileQuery], - profileID: Int64, - brokerIDs: [Int64], - vault: any DataBrokerProtectionSecureVault) throws { - - for profile in profileQueries { - let profileQueryID = try vault.update(profile, - brokerIDs: brokerIDs, - profileId: profileID) - - if !profile.deprecated { - for brokerID in brokerIDs where !profile.deprecated { - try updatePreferredRunDate(Date(), brokerId: brokerID, profileQueryId: profileQueryID) - } - } - } - } -} From 8dc1c2eb868713cc75511a1f82f3ec9fa4295d81 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:40:25 +0000 Subject: [PATCH 15/28] Add appropriate tries to new methods from merge --- .../Operations/DataBrokerOperationsCollection.swift | 11 ++++++++++- .../DataBrokerProfileQueryOperationManager.swift | 6 +++--- .../OperationRetriesCalculatorUseCase.swift | 8 ++++---- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift index d77c7c7d6b..9bdbf236c9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift @@ -126,8 +126,17 @@ final class DataBrokerOperationsCollection: Operation { return filteredAndSortedOperationsData } + // swiftlint:disable:next function_body_length private func runOperation() async { - let allBrokerProfileQueryData = database.fetchAllBrokerProfileQueryData() + let allBrokerProfileQueryData: [BrokerProfileQueryData] + + do { + allBrokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() + } catch { + os_log("DataBrokerOperationsCollection error: runOperation, error: %{public}@", log: .error, error.localizedDescription) + return + } + let brokerProfileQueriesData = allBrokerProfileQueryData.filter { $0.dataBroker.id == dataBrokerID } let filteredAndSortedOperationsData = filterAndSortOperationsData(brokerProfileQueriesData: brokerProfileQueriesData, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift index fa46504a01..d1c34903b6 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift @@ -320,7 +320,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { showWebView: showWebView, shouldRunNextStep: shouldRunNextStep) - let tries = retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + let tries = try retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) stageDurationCalculator.fireOptOutValidate() stageDurationCalculator.fireOptOutSubmitSuccess(tries: tries) @@ -335,8 +335,8 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { startTime: stageDurationCalculator.startTime) try database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)) } catch { - let tries = retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) - stageDurationCalculator.fireOptOutFailure(tries: tries) + let tries = try? retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + stageDurationCalculator.fireOptOutFailure(tries: tries ?? -1) handleOperationError( origin: .optOut, brokerId: brokerId, diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift index cca75f16b2..8035391f64 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift @@ -20,14 +20,14 @@ import Foundation struct OperationRetriesCalculatorUseCase { - func calculateForScan(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64) -> Int { - let events = database.fetchScanHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId) + func calculateForScan(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64) throws -> Int { + let events = try database.fetchScanHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId) return events.filter { $0.type == .scanStarted }.count } - func calculateForOptOut(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) -> Int { - let events = database.fetchOptOutHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + func calculateForOptOut(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws -> Int { + let events = try database.fetchOptOutHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) return events.filter { $0.type == .optOutStarted }.count } From 8a9f5266d53d44686ea0689bad64cbeb3df62968 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:45:41 +0000 Subject: [PATCH 16/28] Add throwing to fileResources --- .../DataBrokerProtectionBrokerUpdater.swift | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index 4797bbbb90..be527ab91b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -38,21 +38,26 @@ final class FileResources: ResourcesRepository { func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { assertionFailure() - os_log("DataBrokerProtectionUpdater FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) + os_log("DataBrokerProtectionUpdater error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) throw FileResourcesError.bundleResourceURLNil } - let fileURLs = try fileManager.contentsOfDirectory( - at: resourceURL, - includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) + do { + let fileURLs = try fileManager.contentsOfDirectory( + at: resourceURL, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) + + let brokerJSONFiles = fileURLs.filter { + $0.isJSON && !$0.hasFakePrefix + } - let brokerJSONFiles = fileURLs.filter { - $0.isJSON && !$0.hasFakePrefix + return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) + } catch { + os_log("DataBrokerProtectionUpdater error FileResources error: fetchBrokerFromResourceFiles, error: %{public}@", log: .error, error.localizedDescription) + throw error } - - return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) } } From 0022fc85915be4e7fbf365baa536fac855274fb6 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:52:46 +0000 Subject: [PATCH 17/28] Refine os_log messages --- .../DataBrokerProtection/Model/DBPUIViewModel.swift | 2 +- .../Sources/DataBrokerProtection/Model/DataBroker.swift | 2 +- .../Operations/DataBrokerProtectionBrokerUpdater.swift | 4 ++-- .../Operations/OperationPreferredDateUpdater.swift | 4 ++-- .../MismatchCalculatorUseCase.swift | 8 ++++++-- .../Scheduler/DataBrokerProtectionProcessor.swift | 2 +- 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index 5b055355f0..37d5b71c94 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -81,7 +81,7 @@ extension DBPUIViewModel: DBPUIScanOps { do { _ = try await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) } catch { - os_log("DBPUIViewModel updateCacheWithCurrentScans, error: %{public}@", log: .error, error.localizedDescription) + os_log("DBPUIViewModel error: updateCacheWithCurrentScans, error: %{public}@", log: .error, error.localizedDescription) } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index 11ae939a20..41a5293846 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -181,7 +181,7 @@ struct DataBroker: Codable, Sendable { let broker = try jsonDecoder.decode(DataBroker.self, from: data) return broker } catch { - os_log("DataBroker initFromResource, error: %{public}@", log: .error, error.localizedDescription) + os_log("DataBroker error: initFromResource, error: %{public}@", log: .error, error.localizedDescription) throw error } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index be527ab91b..7f6749e786 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -38,7 +38,7 @@ final class FileResources: ResourcesRepository { func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { assertionFailure() - os_log("DataBrokerProtectionUpdater error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) + os_log("DataBrokerProtectionUpdater: error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) throw FileResourcesError.bundleResourceURLNil } @@ -55,7 +55,7 @@ final class FileResources: ResourcesRepository { return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) } catch { - os_log("DataBrokerProtectionUpdater error FileResources error: fetchBrokerFromResourceFiles, error: %{public}@", log: .error, error.localizedDescription) + os_log("DataBrokerProtectionUpdater: error FileResources error: fetchBrokerFromResourceFiles, error: %{public}@", log: .error, error.localizedDescription) throw error } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift index b0a1e00f84..d4a832482b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift @@ -84,7 +84,7 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } } } catch { - os_log("Database error during updateChildrenBrokerForParentBroker", log: .dataBrokerProtection) + os_log("OperationPreferredDateUpdaterUseCase error: updateChildrenBrokerForParentBroker, error: %{public}@", log: .error, error.localizedDescription) } } @@ -160,7 +160,7 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } } catch { - os_log("Database error during updatePreferredRunDate", log: .dataBrokerProtection) + os_log("OperationPreferredDateUpdaterUseCase error: updatePreferredRunDate, error: %{public}@", log: .error, error.localizedDescription) } os_log("Updating preferredRunDate on operation with brokerId %{public}@ and profileQueryId %{public}@", log: .dataBrokerProtection, brokerId.description, profileQueryId.description) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift index 209cbb30b1..19cc9cccbd 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift @@ -41,10 +41,14 @@ struct MismatchCalculatorUseCase { let pixelHandler: EventMapping func calculateMismatches() { - guard let brokerProfileQueryData = try? database.fetchAllBrokerProfileQueryData() else { - os_log("Database error during MismatchCalculatorUseCase.calculateMismatches", log: .dataBrokerProtection) + let brokerProfileQueryData: [BrokerProfileQueryData] + do { + brokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() + } catch { + os_log("MismatchCalculatorUseCase error: calculateMismatches, error: %{public}@", log: .error, error.localizedDescription) return } + let parentBrokerProfileQueryData = brokerProfileQueryData.filter { $0.dataBroker.parent == nil } for parent in parentBrokerProfileQueryData { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift index 106bac342e..91b8cb5790 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift @@ -128,7 +128,7 @@ final class DataBrokerProtectionProcessor { operationQueue.addOperation(collection) } } catch { - os_log("Database error during Processor.runOperations", log: .dataBrokerProtection) + os_log("DataBrokerProtectionProcessor error: runOperations, error: %{public}@", log: .error, error.localizedDescription) } operationQueue.addBarrierBlock { From 827f0afe9b8ed092c3cd38106de272a971370b51 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Wed, 13 Mar 2024 21:54:37 +0000 Subject: [PATCH 18/28] Refine more os log messages --- DuckDuckGo/DBP/DBPHomeViewController.swift | 2 +- DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index a5b9a03c99..af30a4d7ce 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -89,7 +89,7 @@ final class DBPHomeViewController: NSViewController { dbpDateStore.updateLastActiveDate() } } catch { - os_log("Database error during DBPHomeViewController.viewDidLoad", log: .dataBrokerProtection) + os_log("DBPHomeViewController error: viewDidLoad, error: %{public}@", log: .error, error.localizedDescription) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift index 1afb5f7eeb..bf31c199b2 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift @@ -49,7 +49,7 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling do { try dataManager.removeAllData() } catch { - os_log("Database error during DataBrokerProtectionFeatureDisabler.disableAndDelete", log: .dataBrokerProtection) + os_log("DataBrokerProtectionFeatureDisabler error: disableAndDelete, error: %{public}@", log: .error, error.localizedDescription) } DataBrokerProtectionLoginItemPixels.fire(pixel: .dataBrokerDisableAndDeleteDaily, frequency: .dailyOnly) From 8b6bc30d8b2b5d5c48cd91a774c77266135af957 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Thu, 14 Mar 2024 13:26:35 +0000 Subject: [PATCH 19/28] Make DBP tests have same swift lint rules as other tests --- .../DataBrokerProtection/Tests/.swiftlint.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml diff --git a/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml b/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml new file mode 100644 index 0000000000..bf8a5655d9 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml @@ -0,0 +1,17 @@ +disabled_rules: + - file_length + - unused_closure_parameter + - type_name + - force_cast + - force_try + - function_body_length + - cyclomatic_complexity + - identifier_name + - blanket_disable_command + - type_body_length + - explicit_non_final_class + - enforce_os_log_wrapper + +large_tuple: + warning: 6 + error: 10 From df2e53fe6ab1b9b731b88d8428cc274be93db1ec Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Thu, 14 Mar 2024 13:26:49 +0000 Subject: [PATCH 20/28] Change DBP tests to use try! instead of try? --- .../DataBrokerProtectionProfileTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift index cc61e72212..85a9e62aa3 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift @@ -175,7 +175,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _ = try? await database.save(profile) + _ = try! await database.save(profile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) XCTAssertFalse(vault.wasDeleteProfileQueryCalled) @@ -217,7 +217,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _ = try? await database.save(newProfile) + _ = try! await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertTrue(vault.wasUpdateProfileQueryCalled) @@ -259,7 +259,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _ = try? await database.save(newProfile) + _ = try! await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) From 5cacc1fba68c8e947b92c12628dc7b09e97b7a0d Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Tue, 26 Mar 2024 18:32:09 +0000 Subject: [PATCH 21/28] Restore DBPFeature assertionFailure --- .../DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift index 7d33568aca..c596488888 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionFeature.swift @@ -126,7 +126,8 @@ struct DataBrokerProtectionFeature: Subfeature { func pushAction(method: CCFSubscribeActionName, webView: WKWebView, params: Encodable) { guard let broker = broker else { - fatalError("Cannot continue without broker instance") + assertionFailure("Cannot continue without broker instance") + return } os_log("Pushing into WebView: %@ params %@", log: .action, method.rawValue, String(describing: params)) From 676a402e4dff07a055368e18d28c9be90486384e Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Tue, 2 Apr 2024 15:46:41 +0100 Subject: [PATCH 22/28] Expand dbp error handling iteration 2: further up the stack + pixels (#2484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1199230911884351/1206910398550646/f Tech Design URL: CC: **Description**: Adds additional error handling higher up the stack, as well as firing pixels for DBP errors. This should not be merged yet because privacy triage isn't complete Note that this depends on iteration 1, and is set with that as the base **Steps to test this PR**: The main thing to test is error pixels are fired as expected. There are two general cases to to test: 1. The secure vault error pixels, both from the background agent and from the main app 2. The new "generalError" pixel (again both from the background agent and from the main app). Pay special attention to making sure it always has at least the following params: error code, error domain, and the origin function. Special care is needed here with the origin function, because if one uses pixel kit debug events it clobbers the original params --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --------- Co-authored-by: Dominik Kapusta Co-authored-by: Fernando Bunn Co-authored-by: Dax the Duck Co-authored-by: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Co-authored-by: Diego Rey Mendez Co-authored-by: Pete Smith Co-authored-by: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Co-authored-by: Anh Do <18567+quanganhdo@users.noreply.github.com> Co-authored-by: Juan Manuel Pereira Co-authored-by: Christopher Brind Co-authored-by: Michal Smaga Co-authored-by: bwaresiak Co-authored-by: Mariusz Śpiewak Co-authored-by: Daniel Bernal Co-authored-by: muodov Co-authored-by: Sam Symons Co-authored-by: Alessandro Boron Co-authored-by: Federico Cappelli Co-authored-by: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Co-authored-by: Graeme Arthur Co-authored-by: amddg44 Co-authored-by: Brad Slayter Co-authored-by: Halle <378795+Halle@users.noreply.github.com> Co-authored-by: Diego Rey Mendez --- .../asana-create-action-item/action.yml | 1 + .github/actions/asana-log-message/action.yml | 1 + .github/workflows/bump_internal_release.yml | 26 +- .github/workflows/publish_dmg_release.yml | 14 +- .github/workflows/release.yml | 15 +- .../NetworkProtection/DuckDuckGoVPN.xcconfig | 8 +- .../DuckDuckGoVPNAppStore.xcconfig | 8 +- Configuration/AppStore.xcconfig | 2 +- Configuration/BuildNumber.xcconfig | 2 +- Configuration/Common.xcconfig | 2 +- Configuration/DeveloperID.xcconfig | 2 +- .../NetworkProtectionSystemExtension.xcconfig | 10 +- Configuration/Global.xcconfig | 2 +- .../Tests/UnitTestsAppStore.xcconfig | 2 +- Configuration/Version.xcconfig | 2 +- DuckDuckGo.xcodeproj/project.pbxproj | 747 +++++++-- .../xcshareddata/swiftpm/Package.resolved | 28 +- ...kDuckGo Privacy Browser App Store.xcscheme | 13 + .../DuckDuckGo Privacy Browser.xcscheme | 3 + DuckDuckGo/Application/AppDelegate.swift | 48 +- DuckDuckGo/Application/URLEventHandler.swift | 9 +- .../Colors/AlertGreen.colorset/Contents.json | 20 + .../Contents.json | 2 +- .../Images/Accessibility.imageset/Icon 18.pdf | Bin 0 -> 6134 bytes .../AddBookmark.imageset/AddBookmark.svg | 8 + .../Images/AddBookmark.imageset/Contents.json | 2 +- .../icon-16-bookmark-add.pdf | Bin 3871 -> 0 bytes .../Images/AddFolder.imageset/AddFolder.svg | 8 + .../Images/AddFolder.imageset/Contents.json | 2 +- .../AddFolder.imageset/icon-16-folder-add.pdf | Bin 3057 -> 0 bytes .../BookmarksFolder.svg | 10 + .../BookmarksFolder.imageset/Contents.json | 12 + .../Chevron-Medium-Right-16.pdf} | Bin 1160 -> 1153 bytes .../Contents.json | 15 + .../Contents.json | 12 + .../CookieProtectionIcon.imageset/Icon 13.pdf | Bin 0 -> 3835 bytes .../Contents.json | 12 + .../EmailProtectionIcon.imageset/Icon 12.pdf | Bin 0 -> 6306 bytes .../FireSettings.imageset/Contents.json | 12 + .../Images/FireSettings.imageset/Icon 17.pdf | Bin 0 -> 33137 bytes .../Images/GeneralIcon.imageset/Contents.json | 12 + .../Images/GeneralIcon.imageset/Icon 16.pdf | Bin 0 -> 7715 bytes .../Images/HomePage/Contents.json | 6 + .../HomePage/Rocket.imageset/rocket.pdf | Bin 6315 -> 0 bytes .../Images/ITR-Icon.imageset/ITR-Icon.pdf | Bin 4129 -> 3919 bytes .../Gift-96.imageset/Contents.json | 5 +- .../Gift-96.imageset/Gift-96.pdf | Bin 9684 -> 0 bytes .../Gift-96.imageset/Gift-New-96x96.pdf | Bin 0 -> 7383 bytes .../Contents.json | 0 .../DownloadsPreferences.pdf | Bin .../Images/Privacy.imageset/Contents.json | 2 +- .../Privacy.imageset/Privacy (Color).pdf | Bin 2879 -> 0 bytes .../Images/Privacy.imageset/Privacy.pdf | Bin 0 -> 4997 bytes .../PrivateSearchIcon.imageset/Contents.json | 12 + .../PrivateSearchIcon.imageset/Icon 10.pdf | Bin 0 -> 3482 bytes .../Images/QandA-128.imageset/Contents.json | 12 + .../Images/QandA-128.imageset/QandA-128.svg | 9 + .../SubscriptionIcon.pdf | Bin 2646 -> 4808 bytes .../Contents.json | 2 +- .../Images/Trash.imageset/Trash.svg | 9 + .../Contents.json | 12 + .../Icon 11.pdf | Bin 0 -> 4673 bytes .../Autoconsent/AutoconsentUserScript.swift | 6 +- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- .../ContentOverlayViewController.swift | 4 +- .../Bookmarks/Extensions/Bookmarks+Tab.swift | 41 + DuckDuckGo/Bookmarks/Model/Bookmark.swift | 105 +- .../Bookmarks/Model/BookmarkFolderInfo.swift | 32 + DuckDuckGo/Bookmarks/Model/BookmarkList.swift | 32 +- .../Bookmarks/Model/BookmarkManager.swift | 25 + DuckDuckGo/Bookmarks/Model/BookmarkNode.swift | 21 +- .../Model/BookmarkOutlineViewDataSource.swift | 20 +- .../Model/BookmarkSidebarTreeController.swift | 10 +- .../Bookmarks/Model/PasteboardFolder.swift | 25 +- DuckDuckGo/Bookmarks/Model/PseudoFolder.swift | 2 +- .../Bookmarks/Services/BookmarkStore.swift | 1 + .../Services/BookmarkStoreMock.swift | 34 + .../Bookmarks/Services/ContextualMenu.swift | 225 ++- .../Services/LocalBookmarkStore.swift | 100 +- .../Services/MenuItemSelectors.swift | 14 +- .../View/AddBookmarkFolderModalView.swift | 67 - .../View/AddBookmarkFolderPopoverView.swift | 76 +- .../Bookmarks/View/AddBookmarkModalView.swift | 77 - .../View/AddBookmarkPopoverView.swift | 103 +- .../Bookmarks/View/BookmarkFolderPicker.swift | 2 +- .../View/BookmarkListViewController.swift | 110 +- ...okmarkManagementDetailViewController.swift | 426 ++--- ...kmarkManagementSidebarViewController.swift | 59 +- .../View/BookmarkOutlineCellView.swift | 95 +- .../View/BookmarkTableCellView.swift | 252 +-- .../Bookmarks/View/BookmarkTableRowView.swift | 8 +- .../Dialog/AddEditBookmarkDialogView.swift | 123 ++ .../AddEditBookmarkFolderDialogView.swift | 107 ++ .../Dialog/AddEditBookmarkFolderView.swift | 132 ++ .../View/Dialog/AddEditBookmarkView.swift | 113 ++ .../Dialog/BookmarkDialogButtonsView.swift | 186 +++ .../Dialog/BookmarkDialogContainerView.swift | 50 + .../BookmarkDialogFolderManagementView.swift | 76 + .../BookmarkDialogStackedContentView.swift | 111 ++ .../View/Dialog/BookmarkFavoriteView.swift | 47 + .../Dialog/BookmarksDialogViewFactory.swift | 93 ++ .../AddBookmarkFolderModalViewModel.swift | 79 - .../AddBookmarkFolderPopoverViewModel.swift | 23 +- .../ViewModel/AddBookmarkModalViewModel.swift | 127 -- .../AddBookmarkPopoverViewModel.swift | 31 +- ...itBookmarkDialogCoordinatorViewModel.swift | 74 + .../AddEditBookmarkDialogViewModel.swift | 216 +++ ...AddEditBookmarkFolderDialogViewModel.swift | 181 ++ .../ViewModel/BookmarksDialogViewModel.swift | 35 + .../View/BookmarksBarCollectionViewItem.swift | 108 +- .../View/BookmarksBarMenuFactory.swift | 7 + .../View/BookmarksBarViewController.swift | 99 +- .../View/BookmarksBarViewModel.swift | 16 +- .../Common/Extensions/BundleExtension.swift | 23 +- .../Common/Extensions/NSAlertExtension.swift | 12 +- .../Common/Extensions/NSMenuExtension.swift | 10 + .../Extensions/NSMenuItemExtension.swift | 6 + .../Common/Extensions/URLExtension.swift | 11 +- .../UserText+NetworkProtection.swift | 192 ++- DuckDuckGo/Common/Localizables/UserText.swift | 64 +- DuckDuckGo/Common/Logging/Logging.swift | 2 +- .../Utilities/UserDefaultsWrapper.swift | 9 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- .../ContentBlocker/ContentBlocking.swift | 2 +- .../ScriptSourceProviding.swift | 10 +- DuckDuckGo/ContentBlocker/macos-config.json | 494 ++++-- DuckDuckGo/DBP/DBPHomeViewController.swift | 18 +- .../DBP/DataBrokerProtectionAppEvents.swift | 12 +- .../DBP/DataBrokerProtectionDebugMenu.swift | 38 +- .../DataBrokerProtectionFeatureDisabler.swift | 2 +- ...ataBrokerProtectionFeatureVisibility.swift | 55 +- ...taBrokerProtectionLoginItemScheduler.swift | 9 +- .../DBP/DataBrokerProtectionManager.swift | 2 +- ...erProtectionSubscriptionEventHandler.swift | 2 +- .../Chromium/ChromiumKeychainPrompt.swift | 4 + .../Logins/Chromium/ChromiumLoginReader.swift | 6 +- .../Model/DataImportViewModel.swift | 2 +- DuckDuckGo/DuckDuckGo.entitlements | 3 +- DuckDuckGo/DuckDuckGoAppStore.entitlements | 11 +- DuckDuckGo/DuckDuckGoAppStoreCI.entitlements | 2 + DuckDuckGo/DuckDuckGoDebug.entitlements | 3 +- DuckDuckGo/Email/EmailUrlExtensions.swift | 5 + .../FindInPage/FindInPageViewController.swift | 8 + DuckDuckGo/Fire/View/FireViewController.swift | 3 +- .../Model/HomePageContinueSetUpModel.swift | 165 +- .../Model/HomePageFavoritesModel.swift | 15 +- DuckDuckGo/HomePage/View/FavoritesView.swift | 3 + .../View/HomePageViewController.swift | 40 +- DuckDuckGo/Info.plist | 2 +- DuckDuckGo/InfoPlist.xcstrings | 18 +- DuckDuckGo/Localizable.xcstrings | 1487 +++++++++++++---- DuckDuckGo/LoginItems/LoginItemsManager.swift | 10 +- .../MainWindow/MainViewController.swift | 59 +- DuckDuckGo/Menus/HistoryMenu.swift | 35 +- DuckDuckGo/Menus/MainMenu.swift | 79 +- DuckDuckGo/Menus/MainMenuActions.swift | 81 +- DuckDuckGo/NavigationBar/PinningManager.swift | 3 +- .../View/AddressBarTextField.swift | 13 +- .../View/AddressBarViewController.swift | 1 + .../NavigationBar/View/MoreOptionsMenu.swift | 72 +- .../View/NavigationBar.storyboard | 2 +- .../View/NavigationBarPopovers.swift | 72 +- .../View/NavigationBarViewController.swift | 71 +- .../View/NetPPopoverManagerMock.swift | 82 + .../AppLauncher.swift | 8 +- .../NetworkProtectionPixelEvent.swift | 219 ++- .../EventMapping+NetworkProtectionError.swift | 15 +- ...rkProtection+ConvenienceInitializers.swift | 41 +- .../NetworkProtectionAppEvents.swift | 44 +- .../NetworkProtectionDebugMenu.swift | 23 +- .../NetworkProtectionDebugUtilities.swift | 5 +- .../NetworkProtectionNavBarButtonModel.swift | 58 +- ...etworkProtectionNavBarPopoverManager.swift | 66 +- .../NetworkProtectionOnboardingMenu.swift | 2 +- ...NetworkProtectionSimulateFailureMenu.swift | 8 +- .../NetworkProtectionTunnelController.swift | 41 +- ...tionWaitlistFeatureFlagOverridesMenu.swift | 6 +- .../VPNLocation/VPNLocationView.swift | 15 - .../VPNLocation/VPNLocationViewModel.swift | 12 - ...NetworkProtectionIPCTunnelController.swift | 44 +- .../NetworkProtectionRemoteMessaging.swift | 8 +- ...rkProtectionSubscriptionEventHandler.swift | 72 +- ...rkProtectionUNNotificationsPresenter.swift | 13 +- ...UserText+NetworkProtectionExtensions.swift | 24 +- .../MacPacketTunnelProvider.swift | 79 +- ...ore+SubscriptionTokenKeychainStorage.swift | 45 + ...NetworkProtectionAppExtension.entitlements | 2 +- DuckDuckGo/Preferences/Model/AboutModel.swift | 7 +- .../Model/AccessibilityPreferences.swift | 69 + .../Model/AppearancePreferences.swift | 47 +- .../CookiePopupProtectionPreferences.swift | 52 + ...el.swift => DataClearingPreferences.swift} | 47 +- .../Model/DefaultBrowserPreferences.swift | 28 + .../Model/PreferencesSection.swift | 119 +- .../Model/PreferencesSidebarModel.swift | 26 +- .../Model/PrivacyProtectionStatus.swift | 77 + .../Model/PrivacySecurityPreferences.swift | 41 - .../Preferences/Model/SearchPreferences.swift | 64 + .../Preferences/Model/SyncPreferences.swift | 28 + .../Model/VPNPreferencesModel.swift | 2 +- .../WebTrackingProtectionPreferences.swift | 52 + .../View/PreferencesAboutView.swift | 4 +- .../View/PreferencesAccessibilityView.swift | 54 + .../View/PreferencesAppearanceView.swift | 19 - ...PreferencesCookiePopupProtectionView.swift | 55 + .../View/PreferencesDataClearingView.swift | 54 + .../View/PreferencesDefaultBrowserView.swift | 69 + .../View/PreferencesDownloadsView.swift | 65 - .../View/PreferencesEmailProtectionView.swift | 81 + .../View/PreferencesGeneralView.swift | 61 +- .../View/PreferencesPrivacyView.swift | 88 - .../View/PreferencesPrivateSearchView.swift | 55 + .../View/PreferencesRootView.swift | 140 +- .../Preferences/View/PreferencesSidebar.swift | 99 +- .../View/PreferencesSyncView.swift | 52 + ...PreferencesWebTrackingProtectionView.swift | 61 + .../View/PrivacyDashboardViewController.swift | 58 +- .../View/AutofillPopoverPresenter.swift | 84 + .../View/PasswordManagementPopover.swift | 19 - .../View/SaveCredentialsViewController.swift | 2 +- DuckDuckGo/Statistics/DailyPixel.swift | 4 + DuckDuckGo/Statistics/Pixel.swift | 3 + DuckDuckGo/Statistics/PixelEvent.swift | 40 +- DuckDuckGo/Statistics/PixelParameters.swift | 20 +- ...tureAvailability+DefaultInitializer.swift} | 11 +- .../Model/SuggestionContainer.swift | 6 +- .../View/SuggestionViewController.swift | 1 + .../SuggestionContainerViewModel.swift | 2 +- .../ViewModel/SuggestionViewModel.swift | 2 +- DuckDuckGo/Sync/SyncBookmarksAdapter.swift | 3 +- DuckDuckGo/Sync/SyncCredentialsAdapter.swift | 1 + DuckDuckGo/Sync/SyncDebugMenu.swift | 25 + DuckDuckGo/Sync/SyncSettingsAdapter.swift | 1 + DuckDuckGo/Tab/Model/Tab.swift | 46 +- .../Tab/Model/UserContentUpdating.swift | 6 +- .../TabExtensions/AutofillTabExtension.swift | 3 +- .../NavigationProtectionTabExtension.swift | 3 +- ...ntityTheftRestorationPagesUserScript.swift | 2 +- .../SubscriptionAppStoreRestorer.swift | 69 + .../SubscriptionErrorReporter.swift | 85 + .../SubscriptionPagesUserScript.swift | 304 ++-- DuckDuckGo/Tab/UserScripts/UserScripts.swift | 7 +- .../Tab/View/BrowserTabViewController.swift | 31 +- DuckDuckGo/Tab/ViewModel/TabViewModel.swift | 12 +- .../VPNMetadataCollector.swift | 34 +- .../NetworkProtectionFeatureDisabler.swift | 56 +- .../NetworkProtectionFeatureVisibility.swift | 125 +- .../Waitlist/UserDefaults+vpnLegacyUser.swift | 40 + .../WaitlistTermsAndConditionsView.swift | 2 +- .../WaitlistThankYouPromptPresenter.swift | 138 ++ .../Waitlist/Views/WaitlistThankYouView.swift | 229 +++ .../WaitlistViewControllerPresenter.swift | 6 + DuckDuckGo/Waitlist/Waitlist.swift | 33 +- .../View/WindowControllersManager.swift | 2 +- DuckDuckGo/Windows/View/WindowsManager.swift | 11 +- ...ataBrokerProtectionBackgroundManager.swift | 41 +- .../DuckDuckGoDBPBackgroundAgent.entitlements | 19 +- ...kDuckGoDBPBackgroundAgentAppDelegate.swift | 2 +- ...kGoDBPBackgroundAgentAppStore.entitlements | 27 +- .../IPCServiceManager.swift | 27 +- .../DuckDuckGoNotifications.entitlements | 1 + .../DuckDuckGoNotificationsAppDelegate.swift | 12 + DuckDuckGoNotifications/Logging.swift | 6 +- DuckDuckGoVPN/DuckDuckGoVPN.entitlements | 1 + DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift | 126 +- .../DuckDuckGoVPNAppStore.entitlements | 7 +- DuckDuckGoVPN/DuckDuckGoVPNDebug.entitlements | 1 + .../NetworkExtensionController.swift | 11 +- ...tion+VPNAgentConvenienceInitializers.swift | 33 + DuckDuckGoVPN/NetworkProtectionBouncer.swift | 25 +- DuckDuckGoVPN/UserText.swift | 1 + DuckDuckGoVPN/VPNAppEventsHandler.swift | 8 +- DuckDuckGoVPN/VPNUninstaller.swift | 60 + .../AutoconsentIntegrationTests.swift | 17 +- .../Common/TestsURLExtension.swift | 5 +- .../HTTPSUpgradeIntegrationTests.swift | 4 +- .../History/HistoryIntegrationTests.swift | 6 +- ...NavigationProtectionIntegrationTests.swift | 10 +- .../PrivacyDashboardIntegrationTests.swift | 4 +- .../InputFilesChecker/InputFilesChecker.swift | 2 - .../DataBrokerProtection/Package.swift | 2 +- .../Bundle/BundleExtension.swift | 8 +- .../CCF/DataBrokerProtectionFeature.swift | 4 +- .../CCF/WebViewHandler.swift | 110 +- .../DataBrokerProtectionDataManager.swift | 7 +- .../DataBrokerProtectionDatabase.swift | 25 + ...erProtectionSecureVaultErrorReporter.swift | 16 +- .../DataBrokerDatabaseBrowserViewModel.swift | 2 +- .../DebugUI/DataBrokerRunCustomJSONView.swift | 60 +- .../DataBrokerRunCustomJSONViewModel.swift | 433 ++++- .../DebugUI/DebugScanOperation.swift | 198 +++ .../DataBrokerForceOptOutViewModel.swift | 3 +- .../IPC/DataBrokerProtectionIPCClient.swift | 33 +- .../DataBrokerProtectionIPCScheduler.swift | 9 +- .../IPC/DataBrokerProtectionIPCServer.swift | 27 +- .../Model/Actions/Expectaction.swift | 3 +- .../Model/DBPUICommunicationModel.swift | 6 +- .../Model/DBPUIViewModel.swift | 2 + .../Model/DataBrokerProtectionProfile.swift | 2 +- .../Model/HistoryEvent.swift | 10 + .../Model/ProfileQuery.swift | 15 + .../Operations/DataBrokerOperation.swift | 105 +- .../DataBrokerOperationRunner.swift | 6 +- .../DataBrokerOperationsCollection.swift | 19 +- ...taBrokerProfileQueryOperationManager.swift | 9 +- .../DataBrokerProtectionBrokerUpdater.swift | 7 +- .../OperationPreferredDateCalculator.swift | 5 +- .../OperationPreferredDateUpdater.swift | 26 +- .../Operations/OptOutOperation.swift | 15 +- .../Operations/ScanOperation.swift | 14 +- .../DataBrokerProtectionEventPixels.swift | 165 ++ .../Pixels/DataBrokerProtectionPixels.swift | 61 +- ...kerProtectionStageDurationCalculator.swift | 25 +- .../Resources/JSON/people-wizard.com.json | 10 +- .../Resources/JSON/peopleswhizr.com.json | 8 +- .../Resources/JSON/peopleswiz.com.json | 8 +- .../Resources/JSON/peopleswizard.com.json | 8 +- .../Resources/JSON/peoplewhiz.com.json | 32 +- .../Resources/JSON/peoplewhiz.net.json | 10 +- .../Resources/JSON/peoplewhized.com.json | 10 +- .../Resources/JSON/peoplewhized.net.json | 10 +- .../Resources/JSON/peoplewhizr.com.json | 10 +- .../Resources/JSON/peoplewhizr.net.json | 10 +- .../Resources/JSON/peoplewiz.com.json | 8 +- .../Resources/JSON/peoplewizard.net.json | 10 +- .../Resources/JSON/peoplewizr.com.json | 8 +- .../Resources/JSON/spokeo.com.json | 35 +- .../DataBrokerProtectionNoOpScheduler.swift | 6 +- .../DataBrokerProtectionProcessor.swift | 61 +- .../DataBrokerProtectionScheduler.swift | 101 +- .../Services/CaptchaService.swift | 30 +- .../Services/EmailService.swift | 24 +- .../DataBrokerProtection/UI/UIMapper.swift | 118 +- .../DataBrokerProtectionBundleExtension.swift | 10 +- .../CaptchaServiceTests.swift | 18 +- .../DataBrokerOperationActionTests.swift | 144 +- ...kerProfileQueryOperationManagerTests.swift | 19 +- ...DataBrokerProtectionEventPixelsTests.swift | 396 +++++ .../DataBrokerProtectionFeatureTests.swift | 2 +- .../DataBrokerProtectionProfileTests.swift | 9 +- ...otectionStageDurationCalculatorTests.swift | 141 ++ .../EmailServiceTests.swift | 12 +- .../MapperToUITests.swift | 14 +- .../DataBrokerProtectionTests/Mocks.swift | 108 +- .../OperationPreferredDateUpdaterTests.swift | 4 +- .../Sources/LoginItems/LoginItem.swift | 4 + .../NetworkProtectionMac/Package.swift | 9 +- LocalPackages/NetworkProtectionMac/README.md | 6 +- .../TransparentProxyProviderPixel.swift | 2 +- .../AppLaunching/AppLaunching.swift | 40 + ...serDefault+ShowVPNUninstalledMessage.swift | 44 + ...NetworkProtectionExpiredEntitlements.swift | 44 + .../UserText+NetworkProtectionUI.swift | 21 +- .../Menu/NavigationBarIconProvider.swift | 2 +- .../Menu/StatusBarMenu.swift | 11 +- .../NetworkProtectionPopover.swift | 8 +- .../Pixels/VPNPrivacyProPixel.swift | 52 + .../Icons/VPN-128.imageset/Contents.json | 2 +- .../Network-Protetion-VPN-128.pdf | Bin 0 -> 14844 bytes .../Icons/VPN-128.imageset/VPN-128.pdf | Bin 11566 -> 0 bytes .../VPN-Disabled-128.imageset/Contents.json | 2 +- .../Network-Protetion-VPN-Disabled-128.pdf | Bin 0 -> 14855 bytes .../VPN-Disabled-128.pdf | Bin 11174 -> 0 bytes .../NetworkProtectionStatusView.swift | 9 +- .../NetworkProtectionStatusViewModel.swift | 41 +- .../SubscriptionExpiredView.swift | 74 + .../TunnelControllerViewModel.swift | 10 +- .../AppLaunching/MockAppLauncher.swift | 30 + .../NetworkProtectionStatusBarMenuTests.swift | 8 +- .../Pixels/VPNPrivacyProPixelTests.swift | 72 + .../TunnelControllerViewModelTests.swift | 35 +- .../PixelKit/PixelKit+Parameters.swift | 10 +- .../PixelKit/Sources/PixelKit/PixelKit.swift | 4 +- .../Sources/PixelKit/PixelKitEvent.swift | 4 +- .../Sources/PixelKit/PixelKitEventV2.swift | 3 +- .../PixelFireExpectations.swift | 28 +- .../ValidatePixel.swift | 2 +- .../XCTestCase+PixelKit.swift | 55 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- .../DebugMenu/SubscriptionDebugMenu.swift | 12 +- .../SubscriptionUI/NSAlert+Subscription.swift | 8 - .../PreferencesSubscriptionModel.swift | 172 +- .../PreferencesSubscriptionView.swift | 351 ++-- .../Contents.json | 12 + .../subscription-expired-icon.pdf | Bin 0 -> 2374 bytes .../Contents.json | 12 + .../Info-Color-16.pdf | Bin 0 -> 1745 bytes .../ActivateSubscriptionAccessModel.swift | 5 +- .../Model/ShareSubscriptionAccessModel.swift | 2 +- .../SubscriptionAccessView.swift | 1 + .../Sources/SubscriptionUI/UserText.swift | 55 +- .../Sources/PreferencesViews/Constants.swift | 1 + .../SwiftUIExtensions/ButtonStyles.swift | 28 + .../{ => Dialogs}/Dialog.swift | 0 .../Dialogs/TieredDialogView.swift | 70 + .../TwoColumnsListView.swift | 62 + .../View+ConditionalModifiers.swift | 47 + .../SyncUI/Resources/Localizable.xcstrings | 72 + .../ViewModels/ManagementViewModel.swift | 3 + .../ManagementView/SyncEnabledView.swift | 47 + .../Sources/SyncUI/internal/UserText.swift | 41 + .../SystemExtensionManager.swift | 31 +- .../Sources/XPCHelper/XPCServer.swift | 20 +- .../InfoPlist.xcstrings | 186 +++ ...rotectionAgentNotificationsPresenter.swift | 8 +- ...otectionSystemExtension_Debug.entitlements | 3 +- ...ectionSystemExtension_Release.entitlements | 3 +- SyncE2EUITests/CriticalPathsTests.swift | 10 +- UITests/AutocompleteTests.swift | 213 +++ UITests/BrowsingHistoryTests.swift | 168 ++ UITests/Common/UITests.swift | 51 + UITests/Common/XCUIElementExtension.swift | 42 + UITests/FindInPageTests.swift | 539 ++++++ UITests/TabBarTests.swift | 29 +- .../AutoconsentMessageProtocolTests.swift | 4 +- .../Extensions/Bookmarks+TabTests.swift | 61 + .../BookmarksBarMenuFactoryTests.swift | 59 + .../Model/BaseBookmarkEntityTests.swift | 247 +++ .../Bookmarks/Model/BookmarkListTests.swift | 59 +- .../Bookmarks/Model/BookmarkNodeTests.swift | 170 ++ .../BookmarkOutlineViewDataSourceTests.swift | 58 +- .../BookmarkSidebarTreeControllerTests.swift | 34 +- UnitTests/Bookmarks/Model/BookmarkTests.swift | 2 +- .../Bookmarks/Model/ContextualMenuTests.swift | 267 +++ .../Model/LocalBookmarkManagerTests.swift | 21 + .../Services/LocalBookmarkStoreTests.swift | 158 +- ...kmarkDialogCoordinatorViewModelTests.swift | 170 ++ .../AddEditBookmarkDialogViewModelTests.swift | 745 +++++++++ ...itBookmarkFolderDialogViewModelTests.swift | 425 +++++ .../BookmarksBarViewModelTests.swift | 214 +++ .../ContentBlockingUpdatingTests.swift | 6 +- .../DataBrokerProtectionVisibilityTests.swift | 149 ++ UnitTests/DataExport/MockSecureVault.swift | 20 + .../DataImport/ChromiumLoginReaderTests.swift | 34 + .../TestChromeData/Legacy Excluded/Login Data | Bin 0 -> 40960 bytes .../TestChromeData/Legacy/Login Data | Bin 40960 -> 40960 bytes .../TestChromeData/v32 Excluded/Login Data | Bin 0 -> 40960 bytes .../v32 Excluded/Login Data for Account | Bin 0 -> 40960 bytes .../TestChromeData/v32/Login Data | Bin 40960 -> 40960 bytes .../Geolocation/CLLocationManagerMock.swift | 17 + .../Geolocation/GeolocationServiceTests.swift | 14 +- .../HomePage/ContinueSetUpModelTests.swift | 172 +- .../HomePage/Mocks/MockBookmarkManager.swift | 4 + UnitTests/Menus/MoreOptionsMenuTests.swift | 65 +- .../Mocks/MockAutofillPopoverPresenter.swift | 53 + .../View/NavigationBarPopoversTests.swift | 104 ++ .../NetworkProtectionPixelEventTests.swift | 325 ++++ ...etworkProtectionRemoteMessagingTests.swift | 16 +- .../AccessibilityPreferencesTests.swift | 44 + .../AppearancePreferencesTests.swift | 26 - .../AutofillPreferencesModelTests.swift | 20 +- ...ookiePopupProtectionPreferencesTests.swift | 44 + .../DataClearingPreferencesTests.swift | 43 + .../PreferencesSidebarModelTests.swift | 6 +- .../PrivacyProtectionStatusTests.swift | 67 + .../Preferences/SearchPreferencesTests.swift | 44 + ...ebTrackingProtectionPreferencesTests.swift | 44 + .../BrokenSiteReportingReferenceTests.swift | 4 + .../Model/SuggestionContainerTests.swift | 2 +- .../Model/SuggestionLoadingMock.swift | 2 +- .../SuggestionContainerViewModelTests.swift | 2 +- .../ViewModel/SuggestionViewModelTests.swift | 2 +- UnitTests/Sync/SyncPreferencesTests.swift | 3 + .../Tab/ViewModel/TabViewModelTests.swift | 6 +- UnitTests/Tab/WebViewTests.swift | 4 +- ...wModelTests+WithoutPinnedTabsManager.swift | 4 +- .../TabCollectionViewModelTests.swift | 4 +- .../VPNFeedbackFormViewModelTests.swift | 3 +- .../WebsiteBreakageReportTests.swift | 8 + .../VPNProxyExtension.entitlements | 16 +- fastlane/metadata/en-US/description.txt | 52 +- fastlane/metadata/en-US/keywords.txt | 2 +- package-lock.json | 8 +- package.json | 2 +- 474 files changed, 18305 insertions(+), 4525 deletions(-) create mode 100644 DuckDuckGo/Assets.xcassets/Colors/AlertGreen.colorset/Contents.json rename DuckDuckGo/Assets.xcassets/Images/{HomePage/Rocket.imageset => Accessibility.imageset}/Contents.json (78%) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Icon 18.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/AddBookmark.svg delete mode 100644 DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/icon-16-bookmark-add.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/AddFolder.svg delete mode 100644 DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/icon-16-folder-add.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/BookmarksFolder.imageset/BookmarksFolder.svg create mode 100644 DuckDuckGo/Assets.xcassets/Images/BookmarksFolder.imageset/Contents.json rename DuckDuckGo/Assets.xcassets/Images/{Chevron-Next-16.imageset/Chevron-Next-16.pdf => Chevron-Medium-Right-16.imageset/Chevron-Medium-Right-16.pdf} (53%) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Icon 13.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Icon 12.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Icon 17.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Icon 16.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/HomePage/Contents.json delete mode 100644 DuckDuckGo/Assets.xcassets/Images/HomePage/Rocket.imageset/rocket.pdf delete mode 100644 DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-96.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-New-96x96.pdf rename DuckDuckGo/Assets.xcassets/Images/{DownloadsPreferences.imageset => OtherPlatformsPreferences.imageset}/Contents.json (100%) rename DuckDuckGo/Assets.xcassets/Images/{DownloadsPreferences.imageset => OtherPlatformsPreferences.imageset}/DownloadsPreferences.pdf (100%) delete mode 100644 DuckDuckGo/Assets.xcassets/Images/Privacy.imageset/Privacy (Color).pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/Privacy.imageset/Privacy.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Icon 10.pdf create mode 100644 DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/QandA-128.svg rename DuckDuckGo/Assets.xcassets/Images/{Chevron-Next-16.imageset => Trash.imageset}/Contents.json (82%) create mode 100644 DuckDuckGo/Assets.xcassets/Images/Trash.imageset/Trash.svg create mode 100644 DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Icon 11.pdf create mode 100644 DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift create mode 100644 DuckDuckGo/Bookmarks/Model/BookmarkFolderInfo.swift delete mode 100644 DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift delete mode 100644 DuckDuckGo/Bookmarks/View/AddBookmarkModalView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderDialogView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogButtonsView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogContainerView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogFolderManagementView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogStackedContentView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarkFavoriteView.swift create mode 100644 DuckDuckGo/Bookmarks/View/Dialog/BookmarksDialogViewFactory.swift delete mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderModalViewModel.swift delete mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddBookmarkModalViewModel.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogCoordinatorViewModel.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogViewModel.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkFolderDialogViewModel.swift create mode 100644 DuckDuckGo/Bookmarks/ViewModel/BookmarksDialogViewModel.swift create mode 100644 DuckDuckGo/NavigationBar/View/NetPPopoverManagerMock.swift create mode 100644 DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift create mode 100644 DuckDuckGo/Preferences/Model/AccessibilityPreferences.swift create mode 100644 DuckDuckGo/Preferences/Model/CookiePopupProtectionPreferences.swift rename DuckDuckGo/Preferences/Model/{PrivacyPreferencesModel.swift => DataClearingPreferences.swift} (51%) create mode 100644 DuckDuckGo/Preferences/Model/PrivacyProtectionStatus.swift delete mode 100644 DuckDuckGo/Preferences/Model/PrivacySecurityPreferences.swift create mode 100644 DuckDuckGo/Preferences/Model/SearchPreferences.swift create mode 100644 DuckDuckGo/Preferences/Model/WebTrackingProtectionPreferences.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesAccessibilityView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesCookiePopupProtectionView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesDataClearingView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesDefaultBrowserView.swift delete mode 100644 DuckDuckGo/Preferences/View/PreferencesDownloadsView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesEmailProtectionView.swift delete mode 100644 DuckDuckGo/Preferences/View/PreferencesPrivacyView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesPrivateSearchView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesSyncView.swift create mode 100644 DuckDuckGo/Preferences/View/PreferencesWebTrackingProtectionView.swift create mode 100644 DuckDuckGo/SecureVault/View/AutofillPopoverPresenter.swift rename DuckDuckGo/Subscription/{AccountManagerExtension.swift => DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift} (67%) create mode 100644 DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionAppStoreRestorer.swift create mode 100644 DuckDuckGo/Tab/UserScripts/Subscription/SubscriptionErrorReporter.swift rename DuckDuckGo/Tab/UserScripts/{ => Subscription}/SubscriptionPagesUserScript.swift (64%) create mode 100644 DuckDuckGo/Waitlist/UserDefaults+vpnLegacyUser.swift create mode 100644 DuckDuckGo/Waitlist/Views/WaitlistThankYouPromptPresenter.swift create mode 100644 DuckDuckGo/Waitlist/Views/WaitlistThankYouView.swift create mode 100644 DuckDuckGoVPN/NetworkProtection+VPNAgentConvenienceInitializers.swift create mode 100644 DuckDuckGoVPN/VPNUninstaller.swift create mode 100644 LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DebugScanOperation.swift create mode 100644 LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift create mode 100644 LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionEventPixelsTests.swift create mode 100644 LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStageDurationCalculatorTests.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/AppLaunching/AppLaunching.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserDefault+ShowVPNUninstalledMessage.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Extensions/UserDefaults+NetworkProtectionExpiredEntitlements.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Pixels/VPNPrivacyProPixel.swift create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/VPN-128.imageset/Network-Protetion-VPN-128.pdf delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/VPN-128.imageset/VPN-128.pdf create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/VPN-Disabled-128.imageset/Network-Protetion-VPN-Disabled-128.pdf delete mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/VPN-Disabled-128.imageset/VPN-Disabled-128.pdf create mode 100644 LocalPackages/NetworkProtectionMac/Sources/NetworkProtectionUI/Views/SubscriptionExpiredView/SubscriptionExpiredView.swift create mode 100644 LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/AppLaunching/MockAppLauncher.swift create mode 100644 LocalPackages/NetworkProtectionMac/Tests/NetworkProtectionUITests/Pixels/VPNPrivacyProPixelTests.swift create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Resources/Subscription.xcassets/Icons/subscription-expired-icon.imageset/Contents.json create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Resources/Subscription.xcassets/Icons/subscription-expired-icon.imageset/subscription-expired-icon.pdf create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Resources/Subscription.xcassets/Icons/subscription-pending-icon.imageset/Contents.json create mode 100644 LocalPackages/SubscriptionUI/Sources/SubscriptionUI/Resources/Subscription.xcassets/Icons/subscription-pending-icon.imageset/Info-Color-16.pdf rename LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/{ => Dialogs}/Dialog.swift (100%) create mode 100644 LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/Dialogs/TieredDialogView.swift create mode 100644 LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TwoColumnsListView.swift create mode 100644 LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/View+ConditionalModifiers.swift create mode 100644 NetworkProtectionAppExtension/InfoPlist.xcstrings create mode 100644 UITests/AutocompleteTests.swift create mode 100644 UITests/BrowsingHistoryTests.swift create mode 100644 UITests/Common/UITests.swift create mode 100644 UITests/Common/XCUIElementExtension.swift create mode 100644 UITests/FindInPageTests.swift create mode 100644 UnitTests/Bookmarks/Extensions/Bookmarks+TabTests.swift create mode 100644 UnitTests/Bookmarks/Factory/BookmarksBarMenuFactoryTests.swift create mode 100644 UnitTests/Bookmarks/Model/BaseBookmarkEntityTests.swift create mode 100644 UnitTests/Bookmarks/Model/ContextualMenuTests.swift create mode 100644 UnitTests/Bookmarks/ViewModels/AddEditBookmarkDialogCoordinatorViewModelTests.swift create mode 100644 UnitTests/Bookmarks/ViewModels/AddEditBookmarkDialogViewModelTests.swift create mode 100644 UnitTests/Bookmarks/ViewModels/AddEditBookmarkFolderDialogViewModelTests.swift create mode 100644 UnitTests/DBP/DataBrokerProtectionVisibilityTests.swift create mode 100644 UnitTests/DataImport/DataImportResources/TestChromeData/Legacy Excluded/Login Data create mode 100644 UnitTests/DataImport/DataImportResources/TestChromeData/v32 Excluded/Login Data create mode 100644 UnitTests/DataImport/DataImportResources/TestChromeData/v32 Excluded/Login Data for Account create mode 100644 UnitTests/NavigationBar/Mocks/MockAutofillPopoverPresenter.swift create mode 100644 UnitTests/NavigationBar/View/NavigationBarPopoversTests.swift create mode 100644 UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift create mode 100644 UnitTests/Preferences/AccessibilityPreferencesTests.swift create mode 100644 UnitTests/Preferences/CookiePopupProtectionPreferencesTests.swift create mode 100644 UnitTests/Preferences/DataClearingPreferencesTests.swift create mode 100644 UnitTests/Preferences/PrivacyProtectionStatusTests.swift create mode 100644 UnitTests/Preferences/SearchPreferencesTests.swift create mode 100644 UnitTests/Preferences/WebTrackingProtectionPreferencesTests.swift diff --git a/.github/actions/asana-create-action-item/action.yml b/.github/actions/asana-create-action-item/action.yml index e235d9d511..7f4ee3f3c2 100644 --- a/.github/actions/asana-create-action-item/action.yml +++ b/.github/actions/asana-create-action-item/action.yml @@ -44,6 +44,7 @@ runs: task-url: ${{ inputs.release-task-url }} - id: get-asana-user-id + if: github.event_name != 'schedule' uses: duckduckgo/apple-infra/actions/asana-get-user-id-for-github-handle@main with: access-token: ${{ inputs.access-token }} diff --git a/.github/actions/asana-log-message/action.yml b/.github/actions/asana-log-message/action.yml index 7ab78fdd4b..288fd832ba 100644 --- a/.github/actions/asana-log-message/action.yml +++ b/.github/actions/asana-log-message/action.yml @@ -30,6 +30,7 @@ runs: task-url: ${{ inputs.task-url }} - id: get-asana-user-id + if: github.event_name != 'schedule' uses: duckduckgo/apple-infra/actions/asana-get-user-id-for-github-handle@main with: access-token: ${{ inputs.access-token }} diff --git a/.github/workflows/bump_internal_release.yml b/.github/workflows/bump_internal_release.yml index 43e0f88084..4d0d9f440a 100644 --- a/.github/workflows/bump_internal_release.yml +++ b/.github/workflows/bump_internal_release.yml @@ -2,7 +2,7 @@ name: Bump Internal Release on: schedule: - - cron: '0 7 * * 2-5' # Run at 07:00 UTC, Tuesday through Friday + - cron: '0 5 * * 2-5' # Run at 05:00 UTC, Tuesday through Friday workflow_dispatch: inputs: asana-task-url: @@ -13,6 +13,10 @@ on: description: "Base branch (defaults to main, only override for testing)" required: false type: string + skip-appstore: + description: "Skip App Store release and only make a DMG build" + default: false + type: boolean jobs: @@ -29,6 +33,7 @@ jobs: skip-release: ${{ steps.check-for-changes.outputs.skip-release }} asana-task-url: ${{ steps.set-parameters.outputs.asana-task-url }} release-branch: ${{ steps.set-parameters.outputs.release-branch }} + skip-appstore: ${{ steps.set-parameters.outputs.skip-appstore }} steps: @@ -63,13 +68,21 @@ jobs: echo "skip-release=false" >> $GITHUB_OUTPUT else latest_tag="$(git describe --tags --abbrev=0)" - changed_files="$(git diff --name-only "$latest_tag".."origin/${release_branch}" | grep -v -E '.github|scripts')" + latest_tag_sha="$(git rev-parse "$latest_tag")" + release_branch_sha="$(git rev-parse "origin/${release_branch}")" - if [[ ${#changed_files} == 0 ]]; then - echo "::warning::No changes to the release branch (or only scripts and workflows). Skipping automatic release." + if [[ "${latest_tag_sha}" == "${release_branch_sha}" ]]; then + echo "::warning::Release branch's HEAD is already tagged. Skipping automatic release." echo "skip-release=true" >> $GITHUB_OUTPUT else - echo "skip-release=false" >> $GITHUB_OUTPUT + changed_files="$(git diff --name-only "$latest_tag".."origin/${release_branch}")" + if grep -q -v -e '.github' -e 'scripts' <<< "$changed_files"; then + echo "::warning::New code changes found in the release branch since the last release. Will bump internal release now." + echo "skip-release=false" >> $GITHUB_OUTPUT + else + echo "::warning::No changes to the release branch (or only changes to scripts and workflows). Skipping automatic release." + echo "skip-release=true" >> $GITHUB_OUTPUT + fi fi fi @@ -86,6 +99,7 @@ jobs: ASANA_TASK_URL: ${{ steps.find-asana-task.outputs.task-url || github.event.inputs.asana-task-url }} RELEASE_BRANCH: ${{ steps.find-asana-task.outputs.release-branch || github.ref_name }} TASK_ID: ${{ steps.find-asana-task.outputs.task-id || steps.task-id.outputs.task-id }} + SKIP_APPSTORE: ${{ github.event.inputs.skip-appstore || false }} # make sure this is set to false on scheduled runs run: | if [[ "${RELEASE_BRANCH}" == "main" ]]; then echo "::error::Workflow run from main branch and release branch wasn't found. Please re-run the workflow and specify a release branch." @@ -94,6 +108,7 @@ jobs: echo "release-branch=${RELEASE_BRANCH}" >> $GITHUB_OUTPUT echo "task-id=${TASK_ID}" >> $GITHUB_OUTPUT echo "asana-task-url=${ASANA_TASK_URL}" >> $GITHUB_OUTPUT + echo "skip-appstore=${SKIP_APPSTORE}" >> $GITHUB_OUTPUT - name: Validate release notes env: @@ -178,6 +193,7 @@ jobs: with: asana-task-url: ${{ needs.validate_input_conditions.outputs.asana-task-url }} branch: ${{ needs.validate_input_conditions.outputs.release-branch }} + skip-appstore: ${{ needs.validate_input_conditions.outputs.skip-appstore == 'true' }} secrets: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} P12_PASSWORD: ${{ secrets.P12_PASSWORD }} diff --git a/.github/workflows/publish_dmg_release.yml b/.github/workflows/publish_dmg_release.yml index fa4d06d4ce..8daecea7be 100644 --- a/.github/workflows/publish_dmg_release.yml +++ b/.github/workflows/publish_dmg_release.yml @@ -110,12 +110,24 @@ jobs: fi echo "release-version=${TAG//-/.}" >> $GITHUB_OUTPUT + # Always check out main first, because the release branch might have been deleted (for public releases) - name: Check out the code uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch all history and tags in order to extract Asana task URLs from git log submodules: recursive - ref: ${{ inputs.branch || github.ref_name }} + ref: main + + - name: Check out the branch if it exists + env: + branch: ${{ inputs.branch || github.ref_name }} + run: | + if [[ -z "${branch}" ]] || git ls-remote --exit-code --heads origin "${branch}"; then + echo "::notice::Checking out ${branch} branch." + git checkout "${branch}" + else + echo "::notice::Branch ${branch} doesn't exist on the remote repository, staying on main." + fi - name: Select Xcode run: sudo xcode-select -s /Applications/Xcode_$(<.xcode-version).app/Contents/Developer diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 276e195e31..96ed1647c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,10 @@ on: description: "Asana release task URL" required: true type: string + skip-appstore: + description: "Skip App Store release and only make a DMG build" + default: false + type: boolean workflow_call: inputs: asana-task-url: @@ -17,6 +21,10 @@ on: description: "Branch name" required: false type: string + skip-appstore: + description: "Skip App Store release and only make a DMG build" + default: false + type: boolean secrets: BUILD_CERTIFICATE_BASE64: required: true @@ -77,7 +85,7 @@ jobs: with: release-type: release create-dmg: true - asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} + asana-task-url: ${{ inputs.asana-task-url }} branch: ${{ inputs.branch }} secrets: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} @@ -106,10 +114,13 @@ jobs: appstore-release: name: Prepare AppStore Release + + if: inputs.skip-appstore != 'true' + uses: ./.github/workflows/build_appstore.yml with: destination: appstore - asana-task-url: ${{ github.event.inputs.asana-task-url || inputs.asana-task-url }} + asana-task-url: ${{ inputs.asana-task-url }} branch: ${{ inputs.branch }} secrets: SSH_PRIVATE_KEY_FASTLANE_MATCH: ${{ secrets.SSH_PRIVATE_KEY_FASTLANE_MATCH }} diff --git a/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig b/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig index 662be4ddeb..b3b7f7ef35 100644 --- a/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig +++ b/Configuration/App/NetworkProtection/DuckDuckGoVPN.xcconfig @@ -49,10 +49,10 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = macOS NetP VPN App - Review (XPC) PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = macOS NetP VPN App - Release (XPC) -FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION +FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_PROTECTION SUBSCRIPTION SWIFT_OBJC_BRIDGING_HEADER = SKIP_INSTALL = YES diff --git a/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig b/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig index afdb43aa1d..cad39a4d7e 100644 --- a/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig +++ b/Configuration/App/NetworkProtection/DuckDuckGoVPNAppStore.xcconfig @@ -50,10 +50,10 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = match AppStore com.duckduckgo.mobile.ios.vpn.agent macos PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = match AppStore com.duckduckgo.mobile.ios.vpn.agent.review macos -FEATURE_FLAGS[arch=*][sdk=*] = NETWORK_PROTECTION -FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETWORK_PROTECTION -FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETWORK_PROTECTION -FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETWORK_PROTECTION +FEATURE_FLAGS[arch=*][sdk=*] = NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETWORK_PROTECTION SUBSCRIPTION ENABLE_APP_SANDBOX = YES SWIFT_OBJC_BRIDGING_HEADER = diff --git a/Configuration/AppStore.xcconfig b/Configuration/AppStore.xcconfig index 7095b66b0b..1fbb567afd 100644 --- a/Configuration/AppStore.xcconfig +++ b/Configuration/AppStore.xcconfig @@ -41,7 +41,7 @@ NETP_APP_GROUP[config=Review][sdk=macos*] = $(NETP_BASE_APP_GROUP).review NETP_APP_GROUP[config=Debug][sdk=macos*] = $(NETP_BASE_APP_GROUP).debug NETP_APP_GROUP[config=Release][sdk=macos*] = $(NETP_BASE_APP_GROUP) -SUBSCRIPTION_BASE_APP_GROUP = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.subscription +SUBSCRIPTION_BASE_APP_GROUP = $(DEVELOPMENT_TEAM).$(MAIN_BUNDLE_IDENTIFIER_PREFIX).subscription SUBSCRIPTION_APP_GROUP[config=CI][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).debug SUBSCRIPTION_APP_GROUP[config=Review][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).review SUBSCRIPTION_APP_GROUP[config=Debug][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).debug diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 7bd323987a..de781aa25c 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 141 +CURRENT_PROJECT_VERSION = 149 diff --git a/Configuration/Common.xcconfig b/Configuration/Common.xcconfig index 5c38d4e8e4..392d63d532 100644 --- a/Configuration/Common.xcconfig +++ b/Configuration/Common.xcconfig @@ -21,7 +21,7 @@ COMBINE_HIDPI_IMAGES = YES DEVELOPMENT_TEAM = HKE973VLUW DEVELOPMENT_TEAM[config=CI][sdk=*] = -FEATURE_FLAGS = FEEDBACK DBP NETWORK_PROTECTION +FEATURE_FLAGS = FEEDBACK DBP NETWORK_PROTECTION SUBSCRIPTION GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = DEBUG=1 CI=1 $(inherited) GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = DEBUG=1 $(inherited) diff --git a/Configuration/DeveloperID.xcconfig b/Configuration/DeveloperID.xcconfig index e5d9ed5daa..0dc8de0485 100644 --- a/Configuration/DeveloperID.xcconfig +++ b/Configuration/DeveloperID.xcconfig @@ -45,7 +45,7 @@ NETP_APP_GROUP[config=Review][sdk=*] = $(NETP_BASE_APP_GROUP).review NETP_APP_GROUP[config=Debug][sdk=*] = $(NETP_BASE_APP_GROUP).debug NETP_APP_GROUP[config=Release][sdk=*] = $(NETP_BASE_APP_GROUP) -SUBSCRIPTION_BASE_APP_GROUP = $(DEVELOPMENT_TEAM).com.duckduckgo.macos.browser.subscription +SUBSCRIPTION_BASE_APP_GROUP = $(DEVELOPMENT_TEAM).$(MAIN_BUNDLE_IDENTIFIER_PREFIX).subscription SUBSCRIPTION_APP_GROUP[config=CI][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).debug SUBSCRIPTION_APP_GROUP[config=Review][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).review SUBSCRIPTION_APP_GROUP[config=Debug][sdk=*] = $(SUBSCRIPTION_BASE_APP_GROUP).debug diff --git a/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig b/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig index c6b581e569..325f9024b7 100644 --- a/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig +++ b/Configuration/Extensions/NetworkProtection/NetworkProtectionSystemExtension.xcconfig @@ -29,12 +29,12 @@ CODE_SIGN_IDENTITY[config=Debug][sdk=macosx*] = Apple Development GENERATE_INFOPLIST_FILE = YES INFOPLIST_FILE = NetworkProtectionSystemExtension/Info.plist INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023 DuckDuckGo. All rights reserved. -INFOPLIST_KEY_NSSystemExtensionUsageDescription = Network Protection +INFOPLIST_KEY_NSSystemExtensionUsageDescription = DuckDuckGo VPN -FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION -FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION +FEATURE_FLAGS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION SUBSCRIPTION +FEATURE_FLAGS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION NETWORK_EXTENSION NETWORK_PROTECTION SUBSCRIPTION PRODUCT_BUNDLE_IDENTIFIER[sdk=*] = $(SYSEX_BUNDLE_ID) PRODUCT_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(SYSEX_BUNDLE_ID) diff --git a/Configuration/Global.xcconfig b/Configuration/Global.xcconfig index 39c875ee65..37ab4747ad 100644 --- a/Configuration/Global.xcconfig +++ b/Configuration/Global.xcconfig @@ -91,7 +91,7 @@ SWIFT_COMPILATION_MODE = wholemodule SWIFT_COMPILATION_MODE[config=CI][arch=*][sdk=*] = SWIFT_COMPILATION_MODE[config=Debug][arch=*][sdk=*] = -// This is temporarily set back to its default value, as a part of merging Network Protection. There are a small number of warnings introduced in +// This is temporarily set back to its default value, as a part of merging the VPN. There are a small number of warnings introduced in // that feature, and more time is needed to address them. To avoid bothering other developers, this is being disabled and a task to fix it will be // prioritized. SWIFT_STRICT_CONCURRENCY = minimal; diff --git a/Configuration/Tests/UnitTestsAppStore.xcconfig b/Configuration/Tests/UnitTestsAppStore.xcconfig index a885a868ed..b31177d987 100644 --- a/Configuration/Tests/UnitTestsAppStore.xcconfig +++ b/Configuration/Tests/UnitTestsAppStore.xcconfig @@ -16,7 +16,7 @@ #include "UnitTests.xcconfig" #include "../AppStore.xcconfig" -FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION DBP +FEATURE_FLAGS = FEEDBACK NETWORK_PROTECTION DBP SUBSCRIPTION PRODUCT_BUNDLE_IDENTIFIER = com.duckduckgo.mobile.ios.DuckDuckGoTests diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index da3c8e7045..b42954dcf7 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 1.79.0 +MARKETING_VERSION = 1.81.0 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ebc0323dd8..7e472b8d99 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -17,6 +17,15 @@ 14505A08256084EF00272CC6 /* UserAgent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14505A07256084EF00272CC6 /* UserAgent.swift */; }; 1456D6E124EFCBC300775049 /* TabBarCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1456D6E024EFCBC300775049 /* TabBarCollectionView.swift */; }; 14D9B8FB24F7E089000D4D13 /* AddressBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D9B8F924F7E089000D4D13 /* AddressBarViewController.swift */; }; + 1D01A3D02B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3CF2B88CEC600FE8150 /* PreferencesAccessibilityView.swift */; }; + 1D01A3D12B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3CF2B88CEC600FE8150 /* PreferencesAccessibilityView.swift */; }; + 1D01A3D22B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3CF2B88CEC600FE8150 /* PreferencesAccessibilityView.swift */; }; + 1D01A3D42B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D32B88CF7700FE8150 /* AccessibilityPreferences.swift */; }; + 1D01A3D52B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D32B88CF7700FE8150 /* AccessibilityPreferences.swift */; }; + 1D01A3D62B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D32B88CF7700FE8150 /* AccessibilityPreferences.swift */; }; + 1D01A3D82B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */; }; + 1D01A3D92B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */; }; + 1D01A3DA2B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */; }; 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 1D074B272909A433006E4AC3 /* PasswordManagerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */; }; 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; }; @@ -26,6 +35,12 @@ 1D1C36E429FAE8DA001FA40C /* FaviconManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C36E229FAE8DA001FA40C /* FaviconManagerTests.swift */; }; 1D1C36E629FB019C001FA40C /* HistoryTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C36E529FB019C001FA40C /* HistoryTabExtensionTests.swift */; }; 1D1C36E729FB019C001FA40C /* HistoryTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C36E529FB019C001FA40C /* HistoryTabExtensionTests.swift */; }; + 1D220BF82B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BF72B86192200F8BBC6 /* PreferencesEmailProtectionView.swift */; }; + 1D220BF92B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BF72B86192200F8BBC6 /* PreferencesEmailProtectionView.swift */; }; + 1D220BFA2B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BF72B86192200F8BBC6 /* PreferencesEmailProtectionView.swift */; }; + 1D220BFC2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BFB2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift */; }; + 1D220BFD2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BFB2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift */; }; + 1D220BFE2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D220BFB2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift */; }; 1D26EBAC2B74BECB0002A93F /* NSImageSendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */; }; 1D26EBAD2B74BECB0002A93F /* NSImageSendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */; }; 1D26EBAE2B74BECB0002A93F /* NSImageSendable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */; }; @@ -62,6 +77,7 @@ 1D77921A28FDC79800BE0210 /* FaviconStoringMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D77921928FDC79800BE0210 /* FaviconStoringMock.swift */; }; 1D8057C82A83CAEE00F4FED6 /* SupportedOsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8057C72A83CAEE00F4FED6 /* SupportedOsChecker.swift */; }; 1D8057C92A83CB3C00F4FED6 /* SupportedOsChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8057C72A83CAEE00F4FED6 /* SupportedOsChecker.swift */; }; + 1D85BCCA2BA982FC0065BA04 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 1D85BCC92BA982FC0065BA04 /* InfoPlist.xcstrings */; }; 1D8B7D6A2A38BF050045C6F6 /* FireproofDomainsStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */; }; 1D8B7D6B2A38BF060045C6F6 /* FireproofDomainsStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BBF1712744CE36004F850E /* FireproofDomainsStoreMock.swift */; }; 1D8C2FE52B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8C2FE42B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift */; }; @@ -75,6 +91,18 @@ 1D9A4E5A2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9A4E592B43213B00F449E2 /* TabSnapshotExtension.swift */; }; 1D9A4E5B2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9A4E592B43213B00F449E2 /* TabSnapshotExtension.swift */; }; 1D9A4E5C2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9A4E592B43213B00F449E2 /* TabSnapshotExtension.swift */; }; + 1D9FDEB72B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEB62B9B5D150040B78C /* SearchPreferencesTests.swift */; }; + 1D9FDEB82B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEB62B9B5D150040B78C /* SearchPreferencesTests.swift */; }; + 1D9FDEBA2B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEB92B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift */; }; + 1D9FDEBB2B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEB92B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift */; }; + 1D9FDEBD2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEBC2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift */; }; + 1D9FDEBE2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEBC2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift */; }; + 1D9FDEC02B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEBF2B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift */; }; + 1D9FDEC12B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEBF2B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift */; }; + 1D9FDEC32B9B63C90040B78C /* DataClearingPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEC22B9B63C90040B78C /* DataClearingPreferencesTests.swift */; }; + 1D9FDEC42B9B63C90040B78C /* DataClearingPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEC22B9B63C90040B78C /* DataClearingPreferencesTests.swift */; }; + 1D9FDEC62B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEC52B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift */; }; + 1D9FDEC72B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9FDEC52B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift */; }; 1DA6D0FD2A1FF9A100540406 /* HTTPCookie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA6D0FC2A1FF9A100540406 /* HTTPCookie.swift */; }; 1DA6D0FE2A1FF9A100540406 /* HTTPCookie.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA6D0FC2A1FF9A100540406 /* HTTPCookie.swift */; }; 1DA6D1022A1FFA3700540406 /* HTTPCookieTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DA6D0FF2A1FF9DC00540406 /* HTTPCookieTests.swift */; }; @@ -94,6 +122,27 @@ 1DC669722B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC6696F2B6CF0D700AA0645 /* TabSnapshotStore.swift */; }; 1DCFBC8A29ADF32B00313531 /* BurnerHomePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */; }; 1DCFBC8B29ADF32B00313531 /* BurnerHomePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */; }; + 1DDC84F72B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84F62B83558F00670238 /* PreferencesPrivateSearchView.swift */; }; + 1DDC84F82B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84F62B83558F00670238 /* PreferencesPrivateSearchView.swift */; }; + 1DDC84F92B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84F62B83558F00670238 /* PreferencesPrivateSearchView.swift */; }; + 1DDC84FB2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FA2B8356CE00670238 /* PreferencesDefaultBrowserView.swift */; }; + 1DDC84FC2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FA2B8356CE00670238 /* PreferencesDefaultBrowserView.swift */; }; + 1DDC84FD2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FA2B8356CE00670238 /* PreferencesDefaultBrowserView.swift */; }; + 1DDC84FF2B835BC000670238 /* SearchPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FE2B835BC000670238 /* SearchPreferences.swift */; }; + 1DDC85002B835BC000670238 /* SearchPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FE2B835BC000670238 /* SearchPreferences.swift */; }; + 1DDC85012B835BC000670238 /* SearchPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC84FE2B835BC000670238 /* SearchPreferences.swift */; }; + 1DDC85032B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC85022B83903E00670238 /* PreferencesWebTrackingProtectionView.swift */; }; + 1DDC85042B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC85022B83903E00670238 /* PreferencesWebTrackingProtectionView.swift */; }; + 1DDC85052B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDC85022B83903E00670238 /* PreferencesWebTrackingProtectionView.swift */; }; + 1DDD3EBC2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBB2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift */; }; + 1DDD3EBD2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBB2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift */; }; + 1DDD3EBE2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBB2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift */; }; + 1DDD3EC02B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBF2B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift */; }; + 1DDD3EC12B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBF2B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift */; }; + 1DDD3EC22B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EBF2B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift */; }; + 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EC32B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift */; }; + 1DDD3EC52B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EC32B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift */; }; + 1DDD3EC62B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDD3EC32B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift */; }; 1DDF076328F815AD00EDFBE3 /* BWCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF075D28F815AD00EDFBE3 /* BWCommunicator.swift */; }; 1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF075E28F815AD00EDFBE3 /* BWManager.swift */; }; 1DE03425298BC7F000CAB3D7 /* InternalUserDeciderStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D12F2E1298BC660009A65FD /* InternalUserDeciderStoreMock.swift */; }; @@ -116,9 +165,6 @@ 1E950E432912A10D0051A99B /* UserScript in Frameworks */ = {isa = PBXBuildFile; productRef = 1E950E422912A10D0051A99B /* UserScript */; }; 1EA7B8D32B7E078C000330A4 /* SubscriptionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 1EA7B8D22B7E078C000330A4 /* SubscriptionUI */; }; 1EA7B8D52B7E078C000330A4 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 1EA7B8D42B7E078C000330A4 /* Subscription */; }; - 1EA7B8D82B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA7B8D72B7E1283000330A4 /* SubscriptionFeatureAvailability.swift */; }; - 1EA7B8D92B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA7B8D72B7E1283000330A4 /* SubscriptionFeatureAvailability.swift */; }; - 1EA7B8DA2B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EA7B8D72B7E1283000330A4 /* SubscriptionFeatureAvailability.swift */; }; 1ED910D52B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; 1ED910D62B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; 1ED910D72B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */; }; @@ -161,6 +207,8 @@ 317295D52AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317295D12AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift */; }; 3184AC6D288F29D800C35E4B /* BadgeNotificationAnimationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3184AC6C288F29D800C35E4B /* BadgeNotificationAnimationModel.swift */; }; 3184AC6F288F2A1100C35E4B /* CookieNotificationAnimationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3184AC6E288F2A1100C35E4B /* CookieNotificationAnimationModel.swift */; }; + 31A2FD172BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A2FD162BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift */; }; + 31A2FD182BAB43BA00D0E741 /* DataBrokerProtectionVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A2FD162BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift */; }; 31A3A4E32B0C115F0021063C /* DataBrokerProtection in Frameworks */ = {isa = PBXBuildFile; productRef = 31A3A4E22B0C115F0021063C /* DataBrokerProtection */; }; 31AA6B972B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA6B962B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift */; }; 31AA6B982B960BA50025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31AA6B962B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift */; }; @@ -268,7 +316,6 @@ 3706FADC293F65D500E42796 /* FirefoxLoginReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93826B48A5100879451 /* FirefoxLoginReader.swift */; }; 3706FADD293F65D500E42796 /* AtbParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50382726A12400758A2B /* AtbParser.swift */; }; 3706FADE293F65D500E42796 /* PreferencesDuckPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F19A6428E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift */; }; - 3706FADF293F65D500E42796 /* AddBookmarkFolderModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */; }; 3706FAE0293F65D500E42796 /* BookmarkSidebarTreeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929426670D2A00AD2C21 /* BookmarkSidebarTreeController.swift */; }; 3706FAE1293F65D500E42796 /* HomePageFavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E8627BBB8F20038AD11 /* HomePageFavoritesModel.swift */; }; 3706FAE2293F65D500E42796 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4925B7B690006F6B06 /* SequenceExtensions.swift */; }; @@ -319,7 +366,7 @@ 3706FB1A293F65D500E42796 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; 3706FB1B293F65D500E42796 /* SafariDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CFD26FE191E001E4761 /* SafariDataImporter.swift */; }; 3706FB1D293F65D500E42796 /* StatisticsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50342726A11F00758A2B /* StatisticsLoader.swift */; }; - 3706FB1F293F65D500E42796 /* PrivacyPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */; }; + 3706FB1F293F65D500E42796 /* DataClearingPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* DataClearingPreferences.swift */; }; 3706FB20293F65D500E42796 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; }; 3706FB21293F65D500E42796 /* NavigationBarBadgeAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31CF3431288B0B1B0087244B /* NavigationBarBadgeAnimator.swift */; }; 3706FB22293F65D500E42796 /* NSTextViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858A798426A8BB5D00A75A42 /* NSTextViewExtension.swift */; }; @@ -387,7 +434,6 @@ 3706FB6D293F65D500E42796 /* SuggestionListCharacteristics.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAB8203B26B2DE0D00788AC3 /* SuggestionListCharacteristics.swift */; }; 3706FB6F293F65D500E42796 /* BookmarkListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */; }; 3706FB70293F65D500E42796 /* SecureVaultLoginImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF326B0002B00E14D75 /* SecureVaultLoginImporter.swift */; }; - 3706FB71293F65D500E42796 /* AddBookmarkModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */; }; 3706FB72293F65D500E42796 /* RecentlyClosedCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C1DD4285C780C0089850C /* RecentlyClosedCoordinator.swift */; }; 3706FB74293F65D500E42796 /* FaviconHostReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA6197C5276B3168008396F0 /* FaviconHostReference.swift */; }; 3706FB76293F65D500E42796 /* ASN1Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93A26B48ADF00879451 /* ASN1Parser.swift */; }; @@ -402,7 +448,6 @@ 3706FB83293F65D500E42796 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; }; 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9E9A5525A3AE8400D1959D /* NSWindowExtension.swift */; }; 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; - 3706FB86293F65D500E42796 /* PreferencesDownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */; }; 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; 3706FB88293F65D500E42796 /* PermissionAuthorizationQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BA526A7BEC80013B453 /* PermissionAuthorizationQuery.swift */; }; 3706FB89293F65D500E42796 /* BadgeAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3171D6B9288984D00068632A /* BadgeAnimationView.swift */; }; @@ -420,7 +465,6 @@ 3706FB95293F65D500E42796 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BAA26A7BF1D0013B453 /* PermissionType.swift */; }; 3706FB96293F65D500E42796 /* RecentlyClosedWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6881A28626C1900D54247 /* RecentlyClosedWindow.swift */; }; 3706FB97293F65D500E42796 /* ActionSpeech.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F29276A35FE00DC0649 /* ActionSpeech.swift */; }; - 3706FB99293F65D500E42796 /* PrivacySecurityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */; }; 3706FB9A293F65D500E42796 /* FireproofDomainsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6830962274CDEC7004B46BB /* FireproofDomainsStore.swift */; }; 3706FB9B293F65D500E42796 /* PrivacyDashboardPermissionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E7E2E932902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift */; }; 3706FB9C293F65D500E42796 /* TabCollectionViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA9FF95E24A1FB680039E328 /* TabCollectionViewModel.swift */; }; @@ -511,7 +555,7 @@ 3706FC01293F65D500E42796 /* ChromiumBookmarksReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CF926FE191E001E4761 /* ChromiumBookmarksReader.swift */; }; 3706FC02293F65D500E42796 /* Downloads.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */; }; 3706FC03293F65D500E42796 /* TabPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE8B10F258A456C00E81239 /* TabPreviewViewController.swift */; }; - 3706FC04293F65D500E42796 /* PreferencesPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesPrivacyView.swift */; }; + 3706FC04293F65D500E42796 /* PreferencesDataClearingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesDataClearingView.swift */; }; 3706FC05293F65D500E42796 /* NSPasteboardExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0135CD2729F1AA00D54834 /* NSPasteboardExtension.swift */; }; 3706FC06293F65D500E42796 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F30276A7DCA00DC0649 /* OnboardingViewModel.swift */; }; 3706FC07293F65D500E42796 /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B0425D6B1D800C7D2AA /* ScriptSourceProviding.swift */; }; @@ -1024,15 +1068,14 @@ 37BF3F22286F0A7A00BD9014 /* PinnedTabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF3F1F286F0A7A00BD9014 /* PinnedTabsView.swift */; }; 37CBCA9A2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CBCA992A8966E60050218F /* SyncSettingsAdapter.swift */; }; 37CBCA9B2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CBCA992A8966E60050218F /* SyncSettingsAdapter.swift */; }; - 37CC53EC27E8A4D10028713D /* PreferencesPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesPrivacyView.swift */; }; - 37CC53F027E8D1440028713D /* PreferencesDownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */; }; + 37CC53EC27E8A4D10028713D /* PreferencesDataClearingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesDataClearingView.swift */; }; 37CC53F427E8D4620028713D /* NSPathControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53F327E8D4620028713D /* NSPathControlView.swift */; }; 37CD54B527F1AC1300F1F7B9 /* PreferencesSidebarModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54B427F1AC1300F1F7B9 /* PreferencesSidebarModelTests.swift */; }; 37CD54B727F1B28A00F1F7B9 /* DefaultBrowserPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54B627F1B28A00F1F7B9 /* DefaultBrowserPreferencesTests.swift */; }; 37CD54B927F1F8AC00F1F7B9 /* AppearancePreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54B827F1F8AC00F1F7B9 /* AppearancePreferencesTests.swift */; }; 37CD54BB27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54BA27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift */; }; 37CD54BD27F2ECAE00F1F7B9 /* AutofillPreferencesModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54BC27F2ECAE00F1F7B9 /* AutofillPreferencesModelTests.swift */; }; - 37CD54C927F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */; }; + 37CD54C927F2FDD100F1F7B9 /* DataClearingPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* DataClearingPreferences.swift */; }; 37CD54CA27F2FDD100F1F7B9 /* AutofillPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C227F2FDD100F1F7B9 /* AutofillPreferencesModel.swift */; }; 37CD54CB27F2FDD100F1F7B9 /* DownloadsPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C327F2FDD100F1F7B9 /* DownloadsPreferences.swift */; }; 37CD54CC27F2FDD100F1F7B9 /* PreferencesSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C427F2FDD100F1F7B9 /* PreferencesSection.swift */; }; @@ -1062,7 +1105,6 @@ 4B02198A25E05FAC00ED7DEA /* FireproofDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02198125E05FAC00ED7DEA /* FireproofDomains.swift */; }; 4B02199C25E063DE00ED7DEA /* FireproofDomainsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B02199925E063DE00ED7DEA /* FireproofDomainsTests.swift */; }; 4B0219A825E0646500ED7DEA /* WebsiteDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0219A725E0646500ED7DEA /* WebsiteDataStoreTests.swift */; }; - 4B0511BD262CAA5A00F6079C /* PrivacySecurityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */; }; 4B0511C3262CAA5A00F6079C /* FireproofDomains.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */; }; 4B0511CA262CAA5A00F6079C /* FireproofDomainsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511B4262CAA5A00F6079C /* FireproofDomainsViewController.swift */; }; 4B0511E1262CAA8600F6079C /* NSOpenPanelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511DF262CAA8600F6079C /* NSOpenPanelExtensions.swift */; }; @@ -1203,6 +1245,9 @@ 4B4D60E22A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; }; 4B4D60E32A0C883A00BCD287 /* AppMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60E12A0C883A00BCD287 /* AppMain.swift */; }; 4B4F72EC266B2ED300814C60 /* CollectionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4F72EB266B2ED300814C60 /* CollectionExtension.swift */; }; + 4B520F632BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B520F622BA5573A006405C7 /* WaitlistThankYouView.swift */; }; + 4B520F642BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B520F622BA5573A006405C7 /* WaitlistThankYouView.swift */; }; + 4B520F652BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B520F622BA5573A006405C7 /* WaitlistThankYouView.swift */; }; 4B59023E26B35F3600489384 /* ChromiumLoginReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59023926B35F3600489384 /* ChromiumLoginReader.swift */; }; 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59023B26B35F3600489384 /* ChromiumDataImporter.swift */; }; 4B59024826B3673600489384 /* ThirdPartyBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59024726B3673600489384 /* ThirdPartyBrowser.swift */; }; @@ -1222,6 +1267,9 @@ 4B67854A2AA8DE75008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */; }; 4B67854B2AA8DE76008A5004 /* NetworkProtectionFeatureVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */; }; 4B68DDFF2ACBA14100FB0973 /* FileLineError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B696AFFA2AC5924800C93203 /* FileLineError.swift */; }; + 4B6B64842BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */; }; + 4B6B64852BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */; }; + 4B6B64862BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */; }; 4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */ = {isa = PBXBuildFile; fileRef = 4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */; }; 4B70C00227B0793D000386ED /* CrashReportTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B70C00027B0793D000386ED /* CrashReportTests.swift */; }; 4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DFF26B0003E00E14D75 /* DataImportMocks.swift */; }; @@ -1279,8 +1327,6 @@ 4B9292CF2667123700AD2C21 /* BookmarkManagementSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C72667123700AD2C21 /* BookmarkManagementSidebarViewController.swift */; }; 4B9292D02667123700AD2C21 /* BookmarkManagementSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C82667123700AD2C21 /* BookmarkManagementSplitViewController.swift */; }; 4B9292D12667123700AD2C21 /* BookmarkTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292C92667123700AD2C21 /* BookmarkTableRowView.swift */; }; - 4B9292D22667123700AD2C21 /* AddBookmarkFolderModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */; }; - 4B9292D32667123700AD2C21 /* AddBookmarkModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */; }; 4B9292D42667123700AD2C21 /* BookmarkListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */; }; 4B9292D52667123700AD2C21 /* BookmarkManagementDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */; }; 4B9292D92667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292D82667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift */; }; @@ -1391,7 +1437,6 @@ 4B9579B52AC7AE700062CA31 /* FirefoxLoginReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93826B48A5100879451 /* FirefoxLoginReader.swift */; }; 4B9579B62AC7AE700062CA31 /* AtbParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50382726A12400758A2B /* AtbParser.swift */; }; 4B9579B72AC7AE700062CA31 /* PreferencesDuckPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F19A6428E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift */; }; - 4B9579B82AC7AE700062CA31 /* AddBookmarkFolderModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */; }; 4B9579B92AC7AE700062CA31 /* BookmarkSidebarTreeController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92929426670D2A00AD2C21 /* BookmarkSidebarTreeController.swift */; }; 4B9579BA2AC7AE700062CA31 /* HomePageFavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85589E8627BBB8F20038AD11 /* HomePageFavoritesModel.swift */; }; 4B9579BB2AC7AE700062CA31 /* SequenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB88B4925B7B690006F6B06 /* SequenceExtensions.swift */; }; @@ -1465,7 +1510,7 @@ 4B957A032AC7AE700062CA31 /* LocalBookmarkStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 987799F829999973005D8EB6 /* LocalBookmarkStore.swift */; }; 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D02633528D8A9A9005CBB41 /* BWEncryption.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated -Wno-strict-prototypes"; }; }; 4B957A052AC7AE700062CA31 /* StatisticsLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69B50342726A11F00758A2B /* StatisticsLoader.swift */; }; - 4B957A072AC7AE700062CA31 /* PrivacyPreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */; }; + 4B957A072AC7AE700062CA31 /* DataClearingPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CD54C127F2FDD100F1F7B9 /* DataClearingPreferences.swift */; }; 4B957A082AC7AE700062CA31 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; }; 4B957A092AC7AE700062CA31 /* InternalUserDeciderStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D36E657298AA3BA00AA485D /* InternalUserDeciderStore.swift */; }; 4B957A0A2AC7AE700062CA31 /* NewWindowPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B634DBE4293C944700C3C99E /* NewWindowPolicy.swift */; }; @@ -1561,7 +1606,6 @@ 4B957A692AC7AE700062CA31 /* BookmarkListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */; }; 4B957A6A2AC7AE700062CA31 /* SecureVaultLoginImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723DF326B0002B00E14D75 /* SecureVaultLoginImporter.swift */; }; 4B957A6B2AC7AE700062CA31 /* WKProcessPoolExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B645D8F529FA95440024461F /* WKProcessPoolExtension.swift */; }; - 4B957A6C2AC7AE700062CA31 /* AddBookmarkModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */; }; 4B957A6D2AC7AE700062CA31 /* LoginItemsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D9AE86A2AA76CF90026E7DC /* LoginItemsManager.swift */; }; 4B957A6E2AC7AE700062CA31 /* PixelExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857E5AF42A79045800FC0FB4 /* PixelExperiment.swift */; }; 4B957A6F2AC7AE700062CA31 /* DuckPlayerTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6C416A6294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift */; }; @@ -1584,7 +1628,6 @@ 4B957A812AC7AE700062CA31 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 4B957A822AC7AE700062CA31 /* SyncDebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 370A34B02AB24E3700C77F7C /* SyncDebugMenu.swift */; }; 4B957A832AC7AE700062CA31 /* AddBookmarkPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */; }; - 4B957A842AC7AE700062CA31 /* PreferencesDownloadsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */; }; 4B957A852AC7AE700062CA31 /* QRSharingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F7127D29F6779000594A45 /* QRSharingService.swift */; }; 4B957A862AC7AE700062CA31 /* ProcessExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68C2FB127706E6A00BF2C7D /* ProcessExtension.swift */; }; 4B957A872AC7AE700062CA31 /* PermissionAuthorizationQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BA526A7BEC80013B453 /* PermissionAuthorizationQuery.swift */; }; @@ -1605,7 +1648,6 @@ 4B957A962AC7AE700062CA31 /* PermissionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BAA26A7BF1D0013B453 /* PermissionType.swift */; }; 4B957A982AC7AE700062CA31 /* RecentlyClosedWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC6881A28626C1900D54247 /* RecentlyClosedWindow.swift */; }; 4B957A992AC7AE700062CA31 /* ActionSpeech.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F29276A35FE00DC0649 /* ActionSpeech.swift */; }; - 4B957A9A2AC7AE700062CA31 /* PrivacySecurityPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */; }; 4B957A9B2AC7AE700062CA31 /* ModalSheetCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6BE9FA9293F7955006363C6 /* ModalSheetCancellable.swift */; }; 4B957A9C2AC7AE700062CA31 /* FireproofDomainsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6830962274CDEC7004B46BB /* FireproofDomainsStore.swift */; }; 4B957A9D2AC7AE700062CA31 /* NetworkProtectionSimulateFailureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */; }; @@ -1725,7 +1767,7 @@ 4B957B152AC7AE700062CA31 /* ChromiumBookmarksReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB99CF926FE191E001E4761 /* ChromiumBookmarksReader.swift */; }; 4B957B162AC7AE700062CA31 /* Downloads.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */; }; 4B957B172AC7AE700062CA31 /* TabPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE8B10F258A456C00E81239 /* TabPreviewViewController.swift */; }; - 4B957B182AC7AE700062CA31 /* PreferencesPrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesPrivacyView.swift */; }; + 4B957B182AC7AE700062CA31 /* PreferencesDataClearingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37CC53EB27E8A4D10028713D /* PreferencesDataClearingView.swift */; }; 4B957B192AC7AE700062CA31 /* NSPasteboardExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0135CD2729F1AA00D54834 /* NSPasteboardExtension.swift */; }; 4B957B1A2AC7AE700062CA31 /* OnboardingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85707F30276A7DCA00DC0649 /* OnboardingViewModel.swift */; }; 4B957B1B2AC7AE700062CA31 /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B0425D6B1D800C7D2AA /* ScriptSourceProviding.swift */; }; @@ -2068,6 +2110,11 @@ 4BBF09232830812900EE1418 /* FileSystemDSL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF09222830812900EE1418 /* FileSystemDSL.swift */; }; 4BBF0925283083EC00EE1418 /* FileSystemDSLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBF0924283083EC00EE1418 /* FileSystemDSLTests.swift */; }; 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC2621C293996410087A482 /* PixelEventTests.swift */; }; + 4BCBE4552BA7E16600FC75A1 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2F565B2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift */; }; + 4BCBE4562BA7E16900FC75A1 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; + 4BCBE4582BA7E17800FC75A1 /* SubscriptionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */; }; + 4BCBE45A2BA7E17800FC75A1 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE4592BA7E17800FC75A1 /* Subscription */; }; + 4BCBE45C2BA7E18500FC75A1 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCBE45B2BA7E18500FC75A1 /* Subscription */; }; 4BCF15D72ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */; }; 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D82ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift */; }; 4BCF15EC2ABB9AF80083F6DF /* NetworkProtectionRemoteMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */; }; @@ -2171,12 +2218,16 @@ 7B00997F2B6508C200FE7C31 /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */; }; 7B0099822B65C6B300FE7C31 /* MacTransparentProxyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */; }; 7B0694982B6E980F00FA4DBA /* VPNProxyLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */; }; + 7B09CBA92BA4BE8100CF245B /* NetworkProtectionPixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */; }; + 7B09CBAA2BA4BE8200CF245B /* NetworkProtectionPixelEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */; }; 7B1459542B7D437200047F2C /* BrowserWindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0099782B65013800FE7C31 /* BrowserWindowManager.swift */; }; 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */; }; 7B1459572B7D43E500047F2C /* NetworkProtectionProxy in Frameworks */ = {isa = PBXBuildFile; productRef = 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */; }; 7B1E819E27C8874900FF0E60 /* ContentOverlayPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */; }; 7B1E819F27C8874900FF0E60 /* ContentOverlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */; }; 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */; }; + 7B25856C2BA2F2D000D49F79 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; + 7B25856E2BA2F2ED00D49F79 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7B25856D2BA2F2ED00D49F79 /* NetworkProtectionUI */; }; 7B2DDCF82A93A8BB0039D884 /* NetworkProtectionAppEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B2DDCF72A93A8BB0039D884 /* NetworkProtectionAppEvents.swift */; }; 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; 7B2DDCFB2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD3AF5C2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift */; }; @@ -2185,13 +2236,18 @@ 7B31FD902AD1257B0086AA24 /* NetworkProtectionIPC in Frameworks */ = {isa = PBXBuildFile; productRef = 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */; }; 7B3618C22ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; }; 7B3618C52ADE77D3000D6154 /* NetworkProtectionNavBarPopoverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B3618C12ADE75C8000D6154 /* NetworkProtectionNavBarPopoverManager.swift */; }; + 7B37C7A52BAA32A50062546A /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = 7B37C7A42BAA32A50062546A /* Subscription */; }; 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */; }; 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B430EA02A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift */; }; 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4CE8E626F02134009134B1 /* TabBarTests.swift */; }; 7B5DD69A2AE51FFA001DE99C /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5DD6992AE51FFA001DE99C /* PixelKit */; }; 7B5F9A752AE2BE4E002AEBC0 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */; }; + 7B624F172BA25C1F00A6C544 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 7B624F162BA25C1F00A6C544 /* NetworkProtectionUI */; }; 7B7DFB202B7E736B009EA1A3 /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; }; 7B7DFB222B7E7473009EA1A3 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 7B7DFB212B7E7473009EA1A3 /* Networking */; }; + 7B7FCD0F2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7FCD0E2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift */; }; + 7B7FCD102BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7FCD0E2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift */; }; + 7B7FCD112BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7FCD0E2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift */; }; 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7B8C083B2AE1268E00F4C67F /* PixelKit */; }; 7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */; }; 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; @@ -2230,8 +2286,6 @@ 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; 7BA7CC582AD1203A0042E5CE /* UserText+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */; }; 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */; }; - 7BA7CC5A2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */; }; - 7BA7CC5B2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60692A0B29FA00BCD287 /* NetworkProtection+ConvenienceInitializers.swift */; }; 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */; }; 7BA7CC5D2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D60722A0B29FA00BCD287 /* EventMapping+NetworkProtectionError.swift */; }; 7BA7CC5F2AD1210C0042E5CE /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 7BA7CC5E2AD1210C0042E5CE /* Networking */; }; @@ -2240,6 +2294,9 @@ 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BAF9E4D2A8A3CCB002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */; }; 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 7BB108582A43375D000AB95F /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; + 7BBA7CE72BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; + 7BBA7CEA2BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */; }; 7BBD44282AD730A400D0A064 /* PixelKit in Frameworks */ = {isa = PBXBuildFile; productRef = 7BBD44272AD730A400D0A064 /* PixelKit */; }; 7BBD45B12A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */; }; @@ -2353,6 +2410,9 @@ 85D0327D2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D0327A2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift */; }; 85D33F1225C82EB3002B91A6 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D33F1125C82EB3002B91A6 /* ConfigurationManager.swift */; }; 85D438B6256E7C9E00F3BAF8 /* ContextMenuUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D438B5256E7C9E00F3BAF8 /* ContextMenuUserScript.swift */; }; + 85D44B862BA08D29001B4AB5 /* Suggestions in Frameworks */ = {isa = PBXBuildFile; productRef = 85D44B852BA08D29001B4AB5 /* Suggestions */; }; + 85D44B882BA08D30001B4AB5 /* Suggestions in Frameworks */ = {isa = PBXBuildFile; productRef = 85D44B872BA08D30001B4AB5 /* Suggestions */; }; + 85D44B8A2BA08D3B001B4AB5 /* Suggestions in Frameworks */ = {isa = PBXBuildFile; productRef = 85D44B892BA08D3B001B4AB5 /* Suggestions */; }; 85D885B026A590A90077C374 /* NSNotificationName+PasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D885AF26A590A90077C374 /* NSNotificationName+PasswordManager.swift */; }; 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85D885B226A5A9DE0077C374 /* NSAlert+PasswordManager.swift */; }; 85E2BBCE2B8F534000DBEC7A /* History in Frameworks */ = {isa = PBXBuildFile; productRef = 85E2BBCD2B8F534000DBEC7A /* History */; }; @@ -2409,14 +2469,76 @@ 9DB6E7242AA0DC5800A17F3C /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = 9DB6E7232AA0DC5800A17F3C /* LoginItems */; }; 9DC70B1A2AA1FA5B005A844B /* LoginItems in Frameworks */ = {isa = PBXBuildFile; productRef = 9DC70B192AA1FA5B005A844B /* LoginItems */; }; 9DEF97E12B06C4EE00764F03 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 9DEF97E02B06C4EE00764F03 /* Networking */; }; + 9F0A2CF82B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0A2CF72B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift */; }; + 9F0A2CF92B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F0A2CF72B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift */; }; 9F180D0F2B69C553000D695F /* Tab+WKUIDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F180D0E2B69C553000D695F /* Tab+WKUIDelegateTests.swift */; }; 9F180D102B69C553000D695F /* Tab+WKUIDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F180D0E2B69C553000D695F /* Tab+WKUIDelegateTests.swift */; }; 9F180D122B69C665000D695F /* DownloadsTabExtensionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */; }; 9F180D132B69C665000D695F /* DownloadsTabExtensionMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */; }; + 9F26060B2B85C20A00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */; }; + 9F26060C2B85C20B00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */; }; + 9F26060E2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */; }; + 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */; }; 9F3910622B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */; }; 9F3910632B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */; }; 9F3910692B68D87B00CB5112 /* ProgressExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910682B68D87B00CB5112 /* ProgressExtensionTests.swift */; }; 9F39106A2B68D87B00CB5112 /* ProgressExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910682B68D87B00CB5112 /* ProgressExtensionTests.swift */; }; + 9F514F912B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */; }; + 9F514F922B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */; }; + 9F514F932B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */; }; + 9F56CFA92B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFA82B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift */; }; + 9F56CFAA2B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFA82B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift */; }; + 9F56CFAB2B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFA82B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift */; }; + 9F56CFAD2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFAC2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift */; }; + 9F56CFAE2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFAC2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift */; }; + 9F56CFAF2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFAC2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift */; }; + 9F56CFB12B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFB02B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift */; }; + 9F56CFB22B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFB02B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift */; }; + 9F56CFB32B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F56CFB02B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift */; }; + 9F872D982B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D972B8DA9F800138637 /* Bookmarks+Tab.swift */; }; + 9F872D992B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D972B8DA9F800138637 /* Bookmarks+Tab.swift */; }; + 9F872D9A2B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D972B8DA9F800138637 /* Bookmarks+Tab.swift */; }; + 9F872D9D2B9058D000138637 /* Bookmarks+TabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D9C2B9058D000138637 /* Bookmarks+TabTests.swift */; }; + 9F872D9E2B9058D000138637 /* Bookmarks+TabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D9C2B9058D000138637 /* Bookmarks+TabTests.swift */; }; + 9F872DA02B90644800138637 /* ContextualMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D9F2B90644800138637 /* ContextualMenuTests.swift */; }; + 9F872DA12B90644800138637 /* ContextualMenuTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872D9F2B90644800138637 /* ContextualMenuTests.swift */; }; + 9F872DA32B90920F00138637 /* BookmarkFolderInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */; }; + 9F872DA42B90920F00138637 /* BookmarkFolderInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */; }; + 9F872DA52B90920F00138637 /* BookmarkFolderInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */; }; + 9F982F0D2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F982F0C2B8224BE00231028 /* AddEditBookmarkFolderDialogViewModel.swift */; }; + 9F982F0E2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F982F0C2B8224BE00231028 /* AddEditBookmarkFolderDialogViewModel.swift */; }; + 9F982F0F2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F982F0C2B8224BE00231028 /* AddEditBookmarkFolderDialogViewModel.swift */; }; + 9F982F132B822B7B00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F982F112B82268F00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift */; }; + 9F982F142B822C7400231028 /* AddEditBookmarkFolderDialogViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F982F112B82268F00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift */; }; + 9FA173DA2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173D92B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift */; }; + 9FA173DB2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173D92B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift */; }; + 9FA173DC2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173D92B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift */; }; + 9FA173DF2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173DE2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift */; }; + 9FA173E02B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173DE2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift */; }; + 9FA173E12B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173DE2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift */; }; + 9FA173E32B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E22B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift */; }; + 9FA173E42B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E22B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift */; }; + 9FA173E52B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E22B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift */; }; + 9FA173E72B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E62B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift */; }; + 9FA173E82B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E62B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift */; }; + 9FA173E92B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173E62B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift */; }; + 9FA173EB2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173EA2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift */; }; + 9FA173EC2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173EA2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift */; }; + 9FA173ED2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA173EA2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift */; }; + 9FA75A3E2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA75A3D2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift */; }; + 9FA75A3F2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FA75A3D2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift */; }; + 9FDA6C212B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA6C202B79A59D00E099A9 /* BookmarkFavoriteView.swift */; }; + 9FDA6C222B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA6C202B79A59D00E099A9 /* BookmarkFavoriteView.swift */; }; + 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FDA6C202B79A59D00E099A9 /* BookmarkFavoriteView.swift */; }; + 9FEE98652B846870002E44E8 /* AddEditBookmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98642B846870002E44E8 /* AddEditBookmarkView.swift */; }; + 9FEE98662B846870002E44E8 /* AddEditBookmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98642B846870002E44E8 /* AddEditBookmarkView.swift */; }; + 9FEE98672B846870002E44E8 /* AddEditBookmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98642B846870002E44E8 /* AddEditBookmarkView.swift */; }; + 9FEE98692B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98682B85B869002E44E8 /* BookmarksDialogViewModel.swift */; }; + 9FEE986A2B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98682B85B869002E44E8 /* BookmarksDialogViewModel.swift */; }; + 9FEE986B2B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE98682B85B869002E44E8 /* BookmarksDialogViewModel.swift */; }; + 9FEE986D2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE986C2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift */; }; + 9FEE986E2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE986C2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift */; }; + 9FEE986F2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FEE986C2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift */; }; AA06B6B72672AF8100F541C5 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = AA06B6B62672AF8100F541C5 /* Sparkle */; }; AA0877B826D5160D00B05660 /* SafariVersionReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0877B726D5160D00B05660 /* SafariVersionReaderTests.swift */; }; AA0877BA26D5161D00B05660 /* WebKitVersionProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0877B926D5161D00B05660 /* WebKitVersionProviderTests.swift */; }; @@ -2578,6 +2700,9 @@ B31055C427A1BA1D001AC618 /* AutoconsentUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31055BC27A1BA1D001AC618 /* AutoconsentUserScript.swift */; }; B31055C627A1BA1D001AC618 /* userscript.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055BE27A1BA1D001AC618 /* userscript.js */; }; B31055CB27A1BA1D001AC618 /* autoconsent-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */; }; + B60293E62BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60293E52BA19ECD0033186B /* NetPPopoverManagerMock.swift */; }; + B60293E72BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60293E52BA19ECD0033186B /* NetPPopoverManagerMock.swift */; }; + B60293E82BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B60293E52BA19ECD0033186B /* NetPPopoverManagerMock.swift */; }; B602E7CF2A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E7CE2A93A5FF00F12201 /* WKBackForwardListExtension.swift */; }; B602E7D02A93A5FF00F12201 /* WKBackForwardListExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E7CE2A93A5FF00F12201 /* WKBackForwardListExtension.swift */; }; B602E8162A1E2570006D261F /* URL+NetworkProtection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E8152A1E2570006D261F /* URL+NetworkProtection.swift */; }; @@ -3041,15 +3166,9 @@ B6F92BA32A691583002ABA6B /* UserDefaultsWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C6A29525CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift */; }; B6F92BAC2A6937B3002ABA6B /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; }; B6F92BAD2A6937B5002ABA6B /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; }; - B6F9BDD82B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */; }; - B6F9BDD92B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */; }; - B6F9BDDA2B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */; }; B6F9BDDC2B45B7EE00677B33 /* WebsiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */; }; B6F9BDDD2B45B7EE00677B33 /* WebsiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */; }; B6F9BDDE2B45B7EE00677B33 /* WebsiteInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */; }; - B6F9BDE02B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */; }; - B6F9BDE12B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */; }; - B6F9BDE22B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */; }; B6F9BDE42B45CD1900677B33 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDE32B45CD1900677B33 /* ModalView.swift */; }; B6F9BDE52B45CD1900677B33 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDE32B45CD1900677B33 /* ModalView.swift */; }; B6F9BDE62B45CD1900677B33 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDE32B45CD1900677B33 /* ModalView.swift */; }; @@ -3072,6 +3191,13 @@ C168B9AC2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168B9AB2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift */; }; C168B9AD2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168B9AB2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift */; }; C168B9AE2B31DC7F001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C168B9AB2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift */; }; + C17CA7AD2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA7AC2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift */; }; + C17CA7AE2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA7AC2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift */; }; + C17CA7B22B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */; }; + C17CA7B32B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */; }; + C1DAF3B52B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; }; + C1DAF3B62B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; }; + C1DAF3B72B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */; }; C1E961EB2B879E79001760E1 /* MockAutofillActionPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E961E72B879E4D001760E1 /* MockAutofillActionPresenter.swift */; }; C1E961ED2B879ED9001760E1 /* MockAutofillActionExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E961EC2B879ED9001760E1 /* MockAutofillActionExecutor.swift */; }; C1E961EF2B87AA29001760E1 /* AutofillActionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E961EE2B87AA29001760E1 /* AutofillActionBuilder.swift */; }; @@ -3099,27 +3225,32 @@ EAC80DE0271F6C0100BBF02D /* fb-sdk.js in Resources */ = {isa = PBXBuildFile; fileRef = EAC80DDF271F6C0100BBF02D /* fb-sdk.js */; }; EAE42800275D47FA00DAC26B /* ClickToLoadModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE427FF275D47FA00DAC26B /* ClickToLoadModel.swift */; }; EAFAD6CA2728BD1200F9DF00 /* clickToLoad.js in Resources */ = {isa = PBXBuildFile; fileRef = EAFAD6C92728BD1200F9DF00 /* clickToLoad.js */; }; - EE0629722B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */; }; - EE0629732B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */; }; - EE0629742B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */; }; - EE0629752B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */; }; - EE0629762B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */; }; + EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE02D4192BB4609900DBE6B3 /* UITests.swift */; }; + EE02D41C2BB460A600DBE6B3 /* BrowsingHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */; }; + EE02D4202BB460C000DBE6B3 /* BrowserServicesKit in Frameworks */ = {isa = PBXBuildFile; productRef = EE02D41F2BB460C000DBE6B3 /* BrowserServicesKit */; }; + EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8EDF2624923EC70071C2E8 /* StringExtension.swift */; }; + EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; + EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */; }; EE2F9C5B2B90F2FF00D45FC9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = EE2F9C5A2B90F2FF00D45FC9 /* Subscription */; }; EE339228291BDEFD009F62C1 /* JSAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE339227291BDEFD009F62C1 /* JSAlertController.swift */; }; + EE3424602BA0853900173B1B /* VPNUninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE34245D2BA0853900173B1B /* VPNUninstaller.swift */; }; + EE3424612BA0853900173B1B /* VPNUninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE34245D2BA0853900173B1B /* VPNUninstaller.swift */; }; + EE66418C2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; + EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; EE66666F2B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; EE6666702B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; EE6666712B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; EE7295E32A545B9A008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295E22A545B9A008C0991 /* NetworkProtection */; }; EE7295E72A545BBB008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295E62A545BBB008C0991 /* NetworkProtection */; }; EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295E82A545BC4008C0991 /* NetworkProtection */; }; - EE7295EB2A545BFC008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295EA2A545BFC008C0991 /* NetworkProtection */; }; EE7295ED2A545C0A008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295EC2A545C0A008C0991 /* NetworkProtection */; }; EE7295EF2A545C12008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295EE2A545C12008C0991 /* NetworkProtection */; }; EEA3EEB12B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3EEB02B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift */; }; EEA3EEB32B24EC0600E8333A /* VPNLocationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3EEB22B24EC0600E8333A /* VPNLocationViewModel.swift */; }; - EEAD7A7A2A1D3E20002A24E7 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; - EEAD7A7B2A1D3E20002A24E7 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; EEAD7A7C2A1D3E20002A24E7 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; + EEBCE6822BA444FA00B9DF00 /* XCUIElementExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEBCE6812BA444FA00B9DF00 /* XCUIElementExtension.swift */; }; + EEBCE6832BA463DD00B9DF00 /* NSImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B139AFC26B60BD800894F82 /* NSImageExtensions.swift */; }; + EEBCE6842BA4643200B9DF00 /* NSSizeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC5E4E325D6BA9C007F5990 /* NSSizeExtension.swift */; }; EEC111E4294D06020086524F /* JSAlert.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EEC111E3294D06020086524F /* JSAlert.storyboard */; }; EEC111E6294D06290086524F /* JSAlertViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC111E5294D06290086524F /* JSAlertViewModel.swift */; }; EEC4A65E2B277E8D00F7C0AA /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3EEB02B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift */; }; @@ -3144,8 +3275,17 @@ EEC8EB402982CD550065AA39 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; EECE10E529DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; }; EECE10E629DD77E60044D027 /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = EECE10E429DD77E60044D027 /* FeatureFlag.swift */; }; + EED735362BB46B6000F173D6 /* AutocompleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EED735352BB46B6000F173D6 /* AutocompleteTests.swift */; }; + EEDE50112BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */; }; + EEDE50122BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */; }; EEF12E6F2A2111880023E6BF /* MacPacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */; }; EEF53E182950CED5002D78F4 /* JSAlertViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */; }; + F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; + F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; + F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */; }; + F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; + F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; + F1B33DF82BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */; }; F1D43AEE2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; F1D43AEF2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; F1D43AF02B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */; }; @@ -3265,6 +3405,13 @@ remoteGlobalIDString = B6EC37E729B5DA2A001ACE79; remoteInfo = "tests-server"; }; + EE02D41D2BB460B500DBE6B3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = AA585D76248FD31100E9A3E2 /* Project object */; + proxyType = 1; + remoteGlobalIDString = B6EC37E729B5DA2A001ACE79; + remoteInfo = "tests-server"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -3326,6 +3473,9 @@ 14505A07256084EF00272CC6 /* UserAgent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAgent.swift; sourceTree = ""; }; 1456D6E024EFCBC300775049 /* TabBarCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCollectionView.swift; sourceTree = ""; }; 14D9B8F924F7E089000D4D13 /* AddressBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressBarViewController.swift; sourceTree = ""; }; + 1D01A3CF2B88CEC600FE8150 /* PreferencesAccessibilityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesAccessibilityView.swift; sourceTree = ""; }; + 1D01A3D32B88CF7700FE8150 /* AccessibilityPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityPreferences.swift; sourceTree = ""; }; + 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesSyncView.swift; sourceTree = ""; }; 1D02633428D8A9A9005CBB41 /* BWEncryption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BWEncryption.h; sourceTree = ""; }; 1D02633528D8A9A9005CBB41 /* BWEncryption.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BWEncryption.m; sourceTree = ""; }; 1D074B262909A433006E4AC3 /* PasswordManagerCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordManagerCoordinator.swift; sourceTree = ""; }; @@ -3333,6 +3483,8 @@ 1D1A33482A6FEB170080ACED /* BurnerMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnerMode.swift; sourceTree = ""; }; 1D1C36E229FAE8DA001FA40C /* FaviconManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconManagerTests.swift; sourceTree = ""; }; 1D1C36E529FB019C001FA40C /* HistoryTabExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryTabExtensionTests.swift; sourceTree = ""; }; + 1D220BF72B86192200F8BBC6 /* PreferencesEmailProtectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesEmailProtectionView.swift; sourceTree = ""; }; + 1D220BFB2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyProtectionStatus.swift; sourceTree = ""; }; 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImageSendable.swift; sourceTree = ""; }; 1D26EBAF2B74DB600002A93F /* TabSnapshotCleanupService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSnapshotCleanupService.swift; sourceTree = ""; }; 1D36E657298AA3BA00AA485D /* InternalUserDeciderStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalUserDeciderStore.swift; sourceTree = ""; }; @@ -3357,11 +3509,18 @@ 1D77921928FDC79800BE0210 /* FaviconStoringMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconStoringMock.swift; sourceTree = ""; }; 1D77921C28FFF27C00BE0210 /* RunningApplicationCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunningApplicationCheck.swift; sourceTree = ""; }; 1D8057C72A83CAEE00F4FED6 /* SupportedOsChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportedOsChecker.swift; sourceTree = ""; }; + 1D85BCC92BA982FC0065BA04 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; 1D8C2FE42B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSnapshotExtensionTests.swift; sourceTree = ""; }; 1D8C2FE92B70F5A7005E4BBD /* MockWebViewSnapshotRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockWebViewSnapshotRenderer.swift; sourceTree = ""; }; 1D8C2FEC2B70F5D0005E4BBD /* MockViewSnapshotRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockViewSnapshotRenderer.swift; sourceTree = ""; }; 1D8C2FEF2B70F751005E4BBD /* MockTabSnapshotStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockTabSnapshotStore.swift; sourceTree = ""; }; 1D9A4E592B43213B00F449E2 /* TabSnapshotExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSnapshotExtension.swift; sourceTree = ""; }; + 1D9FDEB62B9B5D150040B78C /* SearchPreferencesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPreferencesTests.swift; sourceTree = ""; }; + 1D9FDEB92B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebTrackingProtectionPreferencesTests.swift; sourceTree = ""; }; + 1D9FDEBC2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CookiePopupProtectionPreferencesTests.swift; sourceTree = ""; }; + 1D9FDEBF2B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessibilityPreferencesTests.swift; sourceTree = ""; }; + 1D9FDEC22B9B63C90040B78C /* DataClearingPreferencesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataClearingPreferencesTests.swift; sourceTree = ""; }; + 1D9FDEC52B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyProtectionStatusTests.swift; sourceTree = ""; }; 1DA6D0FC2A1FF9A100540406 /* HTTPCookie.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPCookie.swift; sourceTree = ""; }; 1DA6D0FF2A1FF9DC00540406 /* HTTPCookieTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPCookieTests.swift; sourceTree = ""; }; 1DB67F282B6FE4A6003DF243 /* WebViewSnapshotRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewSnapshotRenderer.swift; sourceTree = ""; }; @@ -3370,6 +3529,13 @@ 1DB9617F29F67F3E00CF5568 /* FaviconNullStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconNullStore.swift; sourceTree = ""; }; 1DC6696F2B6CF0D700AA0645 /* TabSnapshotStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSnapshotStore.swift; sourceTree = ""; }; 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnerHomePageView.swift; sourceTree = ""; }; + 1DDC84F62B83558F00670238 /* PreferencesPrivateSearchView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesPrivateSearchView.swift; sourceTree = ""; }; + 1DDC84FA2B8356CE00670238 /* PreferencesDefaultBrowserView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesDefaultBrowserView.swift; sourceTree = ""; }; + 1DDC84FE2B835BC000670238 /* SearchPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPreferences.swift; sourceTree = ""; }; + 1DDC85022B83903E00670238 /* PreferencesWebTrackingProtectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesWebTrackingProtectionView.swift; sourceTree = ""; }; + 1DDD3EBB2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebTrackingProtectionPreferences.swift; sourceTree = ""; }; + 1DDD3EBF2B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesCookiePopupProtectionView.swift; sourceTree = ""; }; + 1DDD3EC32B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CookiePopupProtectionPreferences.swift; sourceTree = ""; }; 1DDF075C28F815AD00EDFBE3 /* BWCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWCredential.swift; sourceTree = ""; }; 1DDF075D28F815AD00EDFBE3 /* BWCommunicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWCommunicator.swift; sourceTree = ""; }; 1DDF075E28F815AD00EDFBE3 /* BWManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWManager.swift; sourceTree = ""; }; @@ -3382,7 +3548,6 @@ 1E7E2E8F29029A2A00C01B54 /* ContentBlockingRulesUpdateObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockingRulesUpdateObserver.swift; sourceTree = ""; }; 1E7E2E932902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPermissionHandler.swift; sourceTree = ""; }; 1E862A882A9FC01200F84D4B /* SubscriptionUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = SubscriptionUI; sourceTree = ""; }; - 1EA7B8D72B7E1283000330A4 /* SubscriptionFeatureAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFeatureAvailability.swift; sourceTree = ""; }; 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; 310E79BE294A19A8007C49E8 /* FireproofingReferenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FireproofingReferenceTests.swift; sourceTree = ""; }; 311B262628E73E0A00FD181A /* TabShadowConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabShadowConfig.swift; sourceTree = ""; }; @@ -3402,6 +3567,7 @@ 3192EC872A4DCF21001E97A5 /* DBPHomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPHomeViewController.swift; sourceTree = ""; }; 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionFeatureDisabler.swift; sourceTree = ""; }; 3199C6FC2AF97367002A7BA1 /* DataBrokerProtectionAppEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionAppEvents.swift; sourceTree = ""; }; + 31A2FD162BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionVisibilityTests.swift; sourceTree = ""; }; 31AA6B962B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionLoginItemPixels.swift; sourceTree = ""; }; 31B4AF522901A4F20013585E /* NSEventExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEventExtension.swift; sourceTree = ""; }; 31C3CE0128EDC1E70002C24A /* CustomRoundedCornersShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomRoundedCornersShape.swift; sourceTree = ""; }; @@ -3494,15 +3660,14 @@ 37BF3F1E286F0A7A00BD9014 /* PinnedTabsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinnedTabsViewModel.swift; sourceTree = ""; }; 37BF3F1F286F0A7A00BD9014 /* PinnedTabsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PinnedTabsView.swift; sourceTree = ""; }; 37CBCA992A8966E60050218F /* SyncSettingsAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncSettingsAdapter.swift; sourceTree = ""; }; - 37CC53EB27E8A4D10028713D /* PreferencesPrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesPrivacyView.swift; sourceTree = ""; }; - 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesDownloadsView.swift; sourceTree = ""; }; + 37CC53EB27E8A4D10028713D /* PreferencesDataClearingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesDataClearingView.swift; sourceTree = ""; }; 37CC53F327E8D4620028713D /* NSPathControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSPathControlView.swift; sourceTree = ""; }; 37CD54B427F1AC1300F1F7B9 /* PreferencesSidebarModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesSidebarModelTests.swift; sourceTree = ""; }; 37CD54B627F1B28A00F1F7B9 /* DefaultBrowserPreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultBrowserPreferencesTests.swift; sourceTree = ""; }; 37CD54B827F1F8AC00F1F7B9 /* AppearancePreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePreferencesTests.swift; sourceTree = ""; }; 37CD54BA27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsPreferencesTests.swift; sourceTree = ""; }; 37CD54BC27F2ECAE00F1F7B9 /* AutofillPreferencesModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPreferencesModelTests.swift; sourceTree = ""; }; - 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacyPreferencesModel.swift; sourceTree = ""; }; + 37CD54C127F2FDD100F1F7B9 /* DataClearingPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataClearingPreferences.swift; sourceTree = ""; }; 37CD54C227F2FDD100F1F7B9 /* AutofillPreferencesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillPreferencesModel.swift; sourceTree = ""; }; 37CD54C327F2FDD100F1F7B9 /* DownloadsPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadsPreferences.swift; sourceTree = ""; }; 37CD54C427F2FDD100F1F7B9 /* PreferencesSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferencesSection.swift; sourceTree = ""; }; @@ -3531,7 +3696,6 @@ 4B02198125E05FAC00ED7DEA /* FireproofDomains.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireproofDomains.swift; sourceTree = ""; }; 4B02199925E063DE00ED7DEA /* FireproofDomainsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireproofDomainsTests.swift; sourceTree = ""; }; 4B0219A725E0646500ED7DEA /* WebsiteDataStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebsiteDataStoreTests.swift; sourceTree = ""; }; - 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrivacySecurityPreferences.swift; sourceTree = ""; }; 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = FireproofDomains.storyboard; sourceTree = ""; }; 4B0511B3262CAA5A00F6079C /* RoundedSelectionRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedSelectionRowView.swift; sourceTree = ""; }; 4B0511B4262CAA5A00F6079C /* FireproofDomainsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireproofDomainsViewController.swift; sourceTree = ""; }; @@ -3615,6 +3779,7 @@ 4B4D60D22A0C84F700BCD287 /* UserText+NetworkProtection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserText+NetworkProtection.swift"; sourceTree = ""; }; 4B4D60E12A0C883A00BCD287 /* AppMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMain.swift; sourceTree = ""; }; 4B4F72EB266B2ED300814C60 /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = ""; }; + 4B520F622BA5573A006405C7 /* WaitlistThankYouView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistThankYouView.swift; sourceTree = ""; }; 4B59023926B35F3600489384 /* ChromiumLoginReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromiumLoginReader.swift; sourceTree = ""; }; 4B59023B26B35F3600489384 /* ChromiumDataImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChromiumDataImporter.swift; sourceTree = ""; }; 4B59024726B3673600489384 /* ThirdPartyBrowser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyBrowser.swift; sourceTree = ""; }; @@ -3633,6 +3798,7 @@ 4B677454255DC18000025BD8 /* Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Bridging.h; sourceTree = ""; }; 4B67853E2AA7C726008A5004 /* DailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixel.swift; sourceTree = ""; }; 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFeatureDisabler.swift; sourceTree = ""; }; + 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistThankYouPromptPresenter.swift; sourceTree = ""; }; 4B70BFFF27B0793D000386ED /* DuckDuckGo-ExampleCrash.ips */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "DuckDuckGo-ExampleCrash.ips"; sourceTree = ""; }; 4B70C00027B0793D000386ED /* CrashReportTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReportTests.swift; sourceTree = ""; }; 4B723DEB26B0002B00E14D75 /* DataImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataImport.swift; sourceTree = ""; }; @@ -3688,8 +3854,6 @@ 4B9292C72667123700AD2C21 /* BookmarkManagementSidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManagementSidebarViewController.swift; sourceTree = ""; }; 4B9292C82667123700AD2C21 /* BookmarkManagementSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManagementSplitViewController.swift; sourceTree = ""; }; 4B9292C92667123700AD2C21 /* BookmarkTableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkTableRowView.swift; sourceTree = ""; }; - 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkFolderModalView.swift; sourceTree = ""; }; - 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddBookmarkModalView.swift; sourceTree = ""; }; 4B9292CC2667123700AD2C21 /* BookmarkListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkListViewController.swift; sourceTree = ""; }; 4B9292CD2667123700AD2C21 /* BookmarkManagementDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewController.swift; sourceTree = ""; }; 4B9292D82667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarkListTreeControllerDataSource.swift; sourceTree = ""; }; @@ -3823,6 +3987,7 @@ 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacTransparentProxyProvider.swift; sourceTree = ""; }; 7B05829D2A812AC000AC3F7C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNProxyLauncher.swift; sourceTree = ""; }; + 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionPixelEventTests.swift; sourceTree = ""; }; 7B1E819B27C8874900FF0E60 /* ContentOverlayPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayPopover.swift; sourceTree = ""; }; 7B1E819C27C8874900FF0E60 /* ContentOverlay.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ContentOverlay.storyboard; sourceTree = ""; }; 7B1E819D27C8874900FF0E60 /* ContentOverlayViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentOverlayViewController.swift; sourceTree = ""; }; @@ -3839,6 +4004,7 @@ 7B6EC5E42AE2D8AF004FE6DF /* DuckDuckGoDBPAgentAppStore.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DuckDuckGoDBPAgentAppStore.xcconfig; sourceTree = ""; }; 7B6EC5E52AE2D8AF004FE6DF /* DuckDuckGoDBPAgent.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DuckDuckGoDBPAgent.xcconfig; sourceTree = ""; }; 7B76E6852AD5D77600186A84 /* XPCHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = XPCHelper; sourceTree = ""; }; + 7B7FCD0E2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+vpnLegacyUser.swift"; sourceTree = ""; }; 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAppEventsHandler.swift; sourceTree = ""; }; 7B934C3D2A866CFF00FC8F9C /* NetworkProtectionOnboardingMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionOnboardingMenu.swift; sourceTree = ""; }; 7B934C402A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UserDefaults+NetworkProtectionShared.swift"; sourceTree = ""; }; @@ -3858,6 +4024,7 @@ 7BA7CC4D2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionIPCTunnelController.swift; sourceTree = ""; }; 7BB108572A43375D000AB95F /* PFMoveApplication.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PFMoveApplication.h; sourceTree = ""; }; 7BB108582A43375D000AB95F /* PFMoveApplication.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PFMoveApplication.m; sourceTree = ""; }; + 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift"; sourceTree = ""; }; 7BBD45B02A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionDebugUtilities.swift; sourceTree = ""; }; 7BD01C182AD8319C0088B32E /* IPCServiceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPCServiceManager.swift; sourceTree = ""; }; 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkExtensionController.swift; sourceTree = ""; }; @@ -3988,10 +4155,33 @@ 9D9AE9282AAA43EB0026E7DC /* DataBrokerProtectionBackgroundManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionBackgroundManager.swift; sourceTree = ""; }; 9D9AE92B2AAB84FF0026E7DC /* DBPMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPMocks.swift; sourceTree = ""; }; 9DB6E7222AA0DA7A00A17F3C /* LoginItems */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = LoginItems; sourceTree = ""; }; + 9F0A2CF72B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseBookmarkEntityTests.swift; sourceTree = ""; }; 9F180D0E2B69C553000D695F /* Tab+WKUIDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tab+WKUIDelegateTests.swift"; sourceTree = ""; }; 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTabExtensionMock.swift; sourceTree = ""; }; + 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogViewModelTests.swift; sourceTree = ""; }; + 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogCoordinatorViewModelTests.swift; sourceTree = ""; }; 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTabExtensionTests.swift; sourceTree = ""; }; 9F3910682B68D87B00CB5112 /* ProgressExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressExtensionTests.swift; sourceTree = ""; }; + 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkFolderDialogView.swift; sourceTree = ""; }; + 9F56CFA82B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkFolderView.swift; sourceTree = ""; }; + 9F56CFAC2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogViewModel.swift; sourceTree = ""; }; + 9F56CFB02B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDialogViewFactory.swift; sourceTree = ""; }; + 9F872D972B8DA9F800138637 /* Bookmarks+Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmarks+Tab.swift"; sourceTree = ""; }; + 9F872D9C2B9058D000138637 /* Bookmarks+TabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bookmarks+TabTests.swift"; sourceTree = ""; }; + 9F872D9F2B90644800138637 /* ContextualMenuTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualMenuTests.swift; sourceTree = ""; }; + 9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkFolderInfo.swift; sourceTree = ""; }; + 9F982F0C2B8224BE00231028 /* AddEditBookmarkFolderDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkFolderDialogViewModel.swift; sourceTree = ""; }; + 9F982F112B82268F00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkFolderDialogViewModelTests.swift; sourceTree = ""; }; + 9FA173D92B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDialogContainerView.swift; sourceTree = ""; }; + 9FA173DE2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDialogButtonsView.swift; sourceTree = ""; }; + 9FA173E22B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDialogFolderManagementView.swift; sourceTree = ""; }; + 9FA173E62B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkDialogStackedContentView.swift; sourceTree = ""; }; + 9FA173EA2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogView.swift; sourceTree = ""; }; + 9FA75A3D2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarMenuFactoryTests.swift; sourceTree = ""; }; + 9FDA6C202B79A59D00E099A9 /* BookmarkFavoriteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkFavoriteView.swift; sourceTree = ""; }; + 9FEE98642B846870002E44E8 /* AddEditBookmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkView.swift; sourceTree = ""; }; + 9FEE98682B85B869002E44E8 /* BookmarksDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDialogViewModel.swift; sourceTree = ""; }; + 9FEE986C2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogCoordinatorViewModel.swift; sourceTree = ""; }; AA0877B726D5160D00B05660 /* SafariVersionReaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariVersionReaderTests.swift; sourceTree = ""; }; AA0877B926D5161D00B05660 /* WebKitVersionProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitVersionProviderTests.swift; sourceTree = ""; }; AA0F3DB6261A566C0077F2D9 /* SuggestionLoadingMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionLoadingMock.swift; sourceTree = ""; }; @@ -4161,6 +4351,7 @@ B31055BC27A1BA1D001AC618 /* AutoconsentUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutoconsentUserScript.swift; sourceTree = ""; }; B31055BE27A1BA1D001AC618 /* userscript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = userscript.js; sourceTree = ""; }; B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "autoconsent-bundle.js"; sourceTree = ""; }; + B60293E52BA19ECD0033186B /* NetPPopoverManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetPPopoverManagerMock.swift; sourceTree = ""; }; B602E7CE2A93A5FF00F12201 /* WKBackForwardListExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKBackForwardListExtension.swift; sourceTree = ""; }; B602E8152A1E2570006D261F /* URL+NetworkProtection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+NetworkProtection.swift"; sourceTree = ""; }; B602E81C2A1E25B0006D261F /* NEOnDemandRuleExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NEOnDemandRuleExtension.swift; sourceTree = ""; }; @@ -4447,9 +4638,7 @@ B6F56566299A414300A04298 /* WKWebViewMockingExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewMockingExtension.swift; sourceTree = ""; }; B6F7127D29F6779000594A45 /* QRSharingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRSharingService.swift; sourceTree = ""; }; B6F7128029F681EB00594A45 /* QuickLookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuickLookUI.framework; path = System/Library/Frameworks/QuickLookUI.framework; sourceTree = SDKROOT; }; - B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBookmarkModalViewModel.swift; sourceTree = ""; }; B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteInfo.swift; sourceTree = ""; }; - B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBookmarkFolderModalViewModel.swift; sourceTree = ""; }; B6F9BDE32B45CD1900677B33 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = ""; }; B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PrivacyDashboard.storyboard; sourceTree = ""; }; B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; @@ -4460,6 +4649,9 @@ C13909F32B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillDeleteAllPasswordsExecutorTests.swift; sourceTree = ""; }; C13909FA2B861039001626ED /* AutofillActionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillActionPresenter.swift; sourceTree = ""; }; C168B9AB2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillNeverPromptWebsitesManager.swift; sourceTree = ""; }; + C17CA7AC2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarPopoversTests.swift; sourceTree = ""; }; + C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillPopoverPresenter.swift; sourceTree = ""; }; + C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillPopoverPresenter.swift; sourceTree = ""; }; C1E961E72B879E4D001760E1 /* MockAutofillActionPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillActionPresenter.swift; sourceTree = ""; }; C1E961EC2B879ED9001760E1 /* MockAutofillActionExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAutofillActionExecutor.swift; sourceTree = ""; }; C1E961EE2B87AA29001760E1 /* AutofillActionBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillActionBuilder.swift; sourceTree = ""; }; @@ -4478,20 +4670,29 @@ EAC80DDF271F6C0100BBF02D /* fb-sdk.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "fb-sdk.js"; sourceTree = ""; }; EAE427FF275D47FA00DAC26B /* ClickToLoadModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClickToLoadModel.swift; sourceTree = ""; }; EAFAD6C92728BD1200F9DF00 /* clickToLoad.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = clickToLoad.js; sourceTree = ""; }; - EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManagerExtension.swift; sourceTree = ""; }; + EE02D4192BB4609900DBE6B3 /* UITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITests.swift; sourceTree = ""; }; + EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowsingHistoryTests.swift; sourceTree = ""; }; + EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindInPageTests.swift; sourceTree = ""; }; EE339227291BDEFD009F62C1 /* JSAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSAlertController.swift; sourceTree = ""; }; + EE34245D2BA0853900173B1B /* VPNUninstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUninstaller.swift; sourceTree = ""; }; + EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift"; sourceTree = ""; }; EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationsHostingViewController.swift; sourceTree = ""; }; EEA3EEB02B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNCountryLabelsModel.swift; sourceTree = ""; }; EEA3EEB22B24EC0600E8333A /* VPNLocationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNLocationViewModel.swift; sourceTree = ""; }; EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLauncher.swift; sourceTree = ""; }; + EEBCE6812BA444FA00B9DF00 /* XCUIElementExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCUIElementExtension.swift; sourceTree = ""; }; EEC111E3294D06020086524F /* JSAlert.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = JSAlert.storyboard; sourceTree = ""; }; EEC111E5294D06290086524F /* JSAlertViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModel.swift; sourceTree = ""; }; EEC4A6682B2C87D300F7C0AA /* VPNLocationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationView.swift; sourceTree = ""; }; EEC4A66C2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationPreferenceItemModel.swift; sourceTree = ""; }; EEC4A6702B2C90AB00F7C0AA /* VPNLocationPreferenceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationPreferenceItem.swift; sourceTree = ""; }; EECE10E429DD77E60044D027 /* FeatureFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = ""; }; + EED735352BB46B6000F173D6 /* AutocompleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteTests.swift; sourceTree = ""; }; + EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtection+VPNAgentConvenienceInitializers.swift"; sourceTree = ""; }; EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPacketTunnelProvider.swift; sourceTree = ""; }; EEF53E172950CED5002D78F4 /* JSAlertViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSAlertViewModelTests.swift; sourceTree = ""; }; + F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionAppStoreRestorer.swift; sourceTree = ""; }; + F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionErrorReporter.swift; sourceTree = ""; }; F1D43AED2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MainMenuActions+VanillaBrowser.swift"; sourceTree = ""; }; F41D174025CB131900472416 /* NSColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSColorExtension.swift; sourceTree = ""; }; F44C130125C2DA0400426E3E /* NSAppearanceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAppearanceExtension.swift; sourceTree = ""; }; @@ -4518,9 +4719,12 @@ 378F44E629B4BDEE00899924 /* SwiftUIExtensions in Frameworks */, 3706FCA7293F65D500E42796 /* BrowserServicesKit in Frameworks */, 3129788A2B64131200B67619 /* DataBrokerProtection in Frameworks */, + 4BCBE4582BA7E17800FC75A1 /* SubscriptionUI in Frameworks */, 3706FCA9293F65D500E42796 /* ContentBlocking in Frameworks */, + 85D44B882BA08D30001B4AB5 /* Suggestions in Frameworks */, 4BF97AD12B43C43F00EB4240 /* NetworkProtectionIPC in Frameworks */, 37F44A5F298C17830025E7FE /* Navigation in Frameworks */, + 4BCBE45A2BA7E17800FC75A1 /* Subscription in Frameworks */, B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */, 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, @@ -4578,6 +4782,7 @@ 37269F012B332FC8005E8E46 /* Common in Frameworks */, EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */, 4B2537772A11BFE100610219 /* PixelKit in Frameworks */, + 7B37C7A52BAA32A50062546A /* Subscription in Frameworks */, 7BBE2B7B2B61663C00697445 /* NetworkProtectionProxy in Frameworks */, 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */, 4B25375B2A11BE7300610219 /* NetworkExtension.framework in Frameworks */, @@ -4605,6 +4810,7 @@ buildActionMask = 2147483647; files = ( 7BFCB7502ADE7E2300DA3EA7 /* PixelKit in Frameworks */, + 4BCBE45C2BA7E18500FC75A1 /* Subscription in Frameworks */, 7BA7CC612AD1211C0042E5CE /* Networking in Frameworks */, 7BEEA5142AD1236300A9E72B /* NetworkProtectionIPC in Frameworks */, 7B00997F2B6508C200FE7C31 /* NetworkProtectionProxy in Frameworks */, @@ -4618,7 +4824,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - EE7295EB2A545BFC008C0991 /* NetworkProtection in Frameworks */, + 7B624F172BA25C1F00A6C544 /* NetworkProtectionUI in Frameworks */, 37269F052B3332C2005E8E46 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4631,6 +4837,7 @@ EE7295E72A545BBB008C0991 /* NetworkProtection in Frameworks */, 4B4D60982A0B2A5C00BCD287 /* PixelKit in Frameworks */, 4B4D60AF2A0C837F00BCD287 /* Networking in Frameworks */, + 7B25856E2BA2F2ED00D49F79 /* NetworkProtectionUI in Frameworks */, 4B4D603F2A0B290200BCD287 /* NetworkExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4664,6 +4871,7 @@ 85E2BBD22B8F536F00DBEC7A /* History in Frameworks */, 4B957BE62AC7AE700062CA31 /* PrivacyDashboard in Frameworks */, 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */, + 85D44B8A2BA08D3B001B4AB5 /* Suggestions in Frameworks */, 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */, 37269F032B332FD8005E8E46 /* Common in Frameworks */, 4B957BE82AC7AE700062CA31 /* SyncUI in Frameworks */, @@ -4684,6 +4892,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EE02D4202BB460C000DBE6B3 /* BrowserServicesKit in Frameworks */, B64E42872B908501006C1346 /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4749,6 +4958,7 @@ 7BA59C9B2AE18B49009A97B1 /* SystemExtensionManager in Frameworks */, 371D00E129D8509400EC8598 /* OpenSSL in Frameworks */, 1E950E412912A10D0051A99B /* PrivacyDashboard in Frameworks */, + 85D44B862BA08D29001B4AB5 /* Suggestions in Frameworks */, 37DF000529F9C056002B7D3E /* SyncDataProviders in Frameworks */, 37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */, 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */, @@ -4956,8 +5166,7 @@ 1EA7B8D62B7E124E000330A4 /* Subscription */ = { isa = PBXGroup; children = ( - EE0629712B90EE8C00D868B4 /* AccountManagerExtension.swift */, - 1EA7B8D72B7E1283000330A4 /* SubscriptionFeatureAvailability.swift */, + 7BBA7CE52BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift */, ); path = Subscription; sourceTree = ""; @@ -5000,6 +5209,14 @@ path = DBP; sourceTree = ""; }; + 31A2FD152BAB419400D0E741 /* DBP */ = { + isa = PBXGroup; + children = ( + 31A2FD162BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift */, + ); + path = DBP; + sourceTree = ""; + }; 31E163BB293A577200963C10 /* PrivacyReferenceTests */ = { isa = PBXGroup; children = ( @@ -5214,18 +5431,22 @@ children = ( 37CD54C427F2FDD100F1F7B9 /* PreferencesSection.swift */, 37CD54C627F2FDD100F1F7B9 /* PreferencesSidebarModel.swift */, - 37A4CEB9282E992F00D75B89 /* StartupPreferences.swift */, 37CD54C827F2FDD100F1F7B9 /* DefaultBrowserPreferences.swift */, + 1DDC84FE2B835BC000670238 /* SearchPreferences.swift */, + 1DDD3EBB2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift */, + 1DDD3EC32B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift */, + 4B41EDA22B1543B9001EEDF4 /* VPNPreferencesModel.swift */, + 37A4CEB9282E992F00D75B89 /* StartupPreferences.swift */, + 37CD54C327F2FDD100F1F7B9 /* DownloadsPreferences.swift */, 3775912C29AAC72700E26367 /* SyncPreferences.swift */, 37CD54C727F2FDD100F1F7B9 /* AppearancePreferences.swift */, - 37CD54C127F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift */, - 4B0511A6262CAA5A00F6079C /* PrivacySecurityPreferences.swift */, - 37F19A6628E1B43200740DC6 /* DuckPlayerPreferences.swift */, 37CD54C227F2FDD100F1F7B9 /* AutofillPreferencesModel.swift */, 3776582E27F82E62009A6B35 /* AutofillPreferences.swift */, - 37CD54C327F2FDD100F1F7B9 /* DownloadsPreferences.swift */, + 1D01A3D32B88CF7700FE8150 /* AccessibilityPreferences.swift */, + 37CD54C127F2FDD100F1F7B9 /* DataClearingPreferences.swift */, + 37F19A6628E1B43200740DC6 /* DuckPlayerPreferences.swift */, 37CD54C527F2FDD100F1F7B9 /* AboutModel.swift */, - 4B41EDA22B1543B9001EEDF4 /* VPNPreferencesModel.swift */, + 1D220BFB2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift */, ); path = Model; sourceTree = ""; @@ -5303,13 +5524,19 @@ 37AFCE8627DA334800471A10 /* PreferencesRootView.swift */, 37AFCE8427DA2D3900471A10 /* PreferencesSidebar.swift */, 37AFCE8A27DB69BC00471A10 /* PreferencesGeneralView.swift */, + 1D01A3D72B88DF8B00FE8150 /* PreferencesSyncView.swift */, + 1DDC84FA2B8356CE00670238 /* PreferencesDefaultBrowserView.swift */, + 1DDC84F62B83558F00670238 /* PreferencesPrivateSearchView.swift */, + 1DDC85022B83903E00670238 /* PreferencesWebTrackingProtectionView.swift */, + 1DDD3EBF2B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift */, + 1D220BF72B86192200F8BBC6 /* PreferencesEmailProtectionView.swift */, + 4B41EDA62B1543C9001EEDF4 /* PreferencesVPNView.swift */, 37D2771427E870D4003365FD /* PreferencesAppearanceView.swift */, - 37CC53EB27E8A4D10028713D /* PreferencesPrivacyView.swift */, - 37F19A6428E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift */, 379DE4BC27EA31AC002CC3DE /* PreferencesAutofillView.swift */, - 37CC53EF27E8D1440028713D /* PreferencesDownloadsView.swift */, + 1D01A3CF2B88CEC600FE8150 /* PreferencesAccessibilityView.swift */, + 37CC53EB27E8A4D10028713D /* PreferencesDataClearingView.swift */, + 37F19A6428E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift */, 37AFCE9127DB8CAD00471A10 /* PreferencesAboutView.swift */, - 4B41EDA62B1543C9001EEDF4 /* PreferencesVPNView.swift */, ); path = View; sourceTree = ""; @@ -5319,12 +5546,18 @@ children = ( 37CD54B427F1AC1300F1F7B9 /* PreferencesSidebarModelTests.swift */, 37CD54B627F1B28A00F1F7B9 /* DefaultBrowserPreferencesTests.swift */, - 37CD54B827F1F8AC00F1F7B9 /* AppearancePreferencesTests.swift */, + 378205F7283BC6A600D1D4AA /* StartupPreferencesTests.swift */, + 1D9FDEB62B9B5D150040B78C /* SearchPreferencesTests.swift */, + 1D9FDEB92B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift */, + 1D9FDEBC2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift */, 37CD54BA27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift */, + 37CD54B827F1F8AC00F1F7B9 /* AppearancePreferencesTests.swift */, 3776583027F8325B009A6B35 /* AutofillPreferencesTests.swift */, 37CD54BC27F2ECAE00F1F7B9 /* AutofillPreferencesModelTests.swift */, - 378205F7283BC6A600D1D4AA /* StartupPreferencesTests.swift */, + 1D9FDEBF2B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift */, + 1D9FDEC22B9B63C90040B78C /* DataClearingPreferencesTests.swift */, 3714B1E628EDB7FA0056C57A /* DuckPlayerPreferencesTests.swift */, + 1D9FDEC52B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift */, ); path = Preferences; sourceTree = ""; @@ -5584,6 +5817,7 @@ 4B41ED9F2B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift */, EEF12E6D2A2111880023E6BF /* MacPacketTunnelProvider.swift */, 7B0099802B65C6B300FE7C31 /* MacTransparentProxyProvider.swift */, + EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */, ); path = NetworkExtensionTargets; sourceTree = ""; @@ -5610,6 +5844,7 @@ 4B5F14F72A148B230060320F /* NetworkProtectionAppExtension */ = { isa = PBXGroup; children = ( + 1D85BCC92BA982FC0065BA04 /* InfoPlist.xcstrings */, 4B5F14F82A148B230060320F /* Info.plist */, ); path = NetworkProtectionAppExtension; @@ -5827,6 +6062,7 @@ children = ( 4B9292AE26670F5300AD2C21 /* NSOutlineViewExtensions.swift */, B6C0BB6629AEFF8100AE8E3C /* BookmarkExtension.swift */, + 9F872D972B8DA9F800138637 /* Bookmarks+Tab.swift */, ); path = Extensions; sourceTree = ""; @@ -5836,6 +6072,7 @@ children = ( 7BD8679A2A9E9E000063B9F7 /* NetworkProtectionFeatureVisibility.swift */, 4B6785432AA8DE1F008A5004 /* NetworkProtectionFeatureDisabler.swift */, + 7B7FCD0E2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift */, 31F2D1FE2AF026D800BF0144 /* WaitlistTermsAndConditionsActionHandler.swift */, 31C9ADE42AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift */, 4B9DB0072A983B23000927DB /* Waitlist.swift */, @@ -5883,6 +6120,8 @@ 4B9DB0192A983B24000927DB /* WaitlistModalViewController.swift */, 3168506C2AF3AD1C009A2828 /* WaitlistViewControllerPresenter.swift */, 4B9DB01A2A983B24000927DB /* WaitlistRootView.swift */, + 4B520F622BA5573A006405C7 /* WaitlistThankYouView.swift */, + 4B6B64832BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift */, ); path = Views; sourceTree = ""; @@ -6080,6 +6319,7 @@ 4BCF15E62ABB98A20083F6DF /* Resources */, 4BCF15E42ABB98990083F6DF /* NetworkProtectionRemoteMessageTests.swift */, 4BD57C032AC112DF00B580EE /* NetworkProtectionRemoteMessagingTests.swift */, + 7B09CBA72BA4BE7000CF245B /* NetworkProtectionPixelEventTests.swift */, ); path = NetworkProtection; sourceTree = ""; @@ -6204,6 +6444,10 @@ 7B4CE8DB26F02108009134B1 /* UITests */ = { isa = PBXGroup; children = ( + EEBCE6802BA444FA00B9DF00 /* Common */, + EED735352BB46B6000F173D6 /* AutocompleteTests.swift */, + EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */, + EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */, 7B4CE8E626F02134009134B1 /* TabBarTests.swift */, ); path = UITests; @@ -6232,9 +6476,11 @@ 7BA7CC132AD11DC80042E5CE /* AppLauncher+DefaultInitializer.swift */, 7BA7CC0E2AD11DC80042E5CE /* DuckDuckGoVPNAppDelegate.swift */, 7BD1688D2AD4A4C400D24876 /* NetworkExtensionController.swift */, + EEDE50102BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift */, 7BA7CC152AD11DC80042E5CE /* NetworkProtectionBouncer.swift */, 7B8DB3192B504D7500EC16DA /* VPNAppEventsHandler.swift */, 7B0694972B6E980F00FA4DBA /* VPNProxyLauncher.swift */, + EE34245D2BA0853900173B1B /* VPNUninstaller.swift */, 7BA7CC112AD11DC80042E5CE /* TunnelControllerIPCService.swift */, 7BA7CC172AD11DC80042E5CE /* UserText.swift */, 7BA7CC122AD11DC80042E5CE /* Assets.xcassets */, @@ -6538,6 +6784,7 @@ 4B8A4DFE27C83B29005F40E8 /* SaveIdentityViewController.swift */, 4BE4005227CF3DC3007D3161 /* SavePaymentMethodPopover.swift */, 4BE4005427CF3F19007D3161 /* SavePaymentMethodViewController.swift */, + C1DAF3B42B9A44860059244F /* AutofillPopoverPresenter.swift */, ); path = View; sourceTree = ""; @@ -6648,6 +6895,49 @@ path = DuckDuckGoDBPBackgroundAgent; sourceTree = ""; }; + 9F872D9B2B9058B000138637 /* Extensions */ = { + isa = PBXGroup; + children = ( + 9F872D9C2B9058D000138637 /* Bookmarks+TabTests.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 9F982F102B82264400231028 /* ViewModels */ = { + isa = PBXGroup; + children = ( + 9F982F112B82268F00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift */, + 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */, + 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 9FA173DD2B7A0ECE00EE4E6E /* Dialog */ = { + isa = PBXGroup; + children = ( + 9FA173D92B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift */, + 9FA173E62B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift */, + 9FA173DE2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift */, + 9FDA6C202B79A59D00E099A9 /* BookmarkFavoriteView.swift */, + 9FA173E22B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift */, + 9FA173EA2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift */, + 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */, + 9F56CFA82B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift */, + 9FEE98642B846870002E44E8 /* AddEditBookmarkView.swift */, + 9F56CFB02B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift */, + ); + path = Dialog; + sourceTree = ""; + }; + 9FA75A3C2BA00DF500DA5FA6 /* Factory */ = { + isa = PBXGroup; + children = ( + 9FA75A3D2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift */, + ); + path = Factory; + sourceTree = ""; + }; AA0877B626D515EE00B05660 /* UserAgent */ = { isa = PBXGroup; children = ( @@ -6887,6 +7177,7 @@ 85AC3B1525D9BBFA00C7D2AA /* Configuration */, 4B82E9B725B6A04B00656FE7 /* ContentBlocker */, 4B70BFFD27B0793D000386ED /* CrashReports */, + 31A2FD152BAB419400D0E741 /* DBP */, 4B723E0226B0003E00E14D75 /* DataExport */, 4B723DFE26B0003E00E14D75 /* DataImport */, 4BBC16A327C488B900E00A38 /* DeviceAuthentication */, @@ -6995,6 +7286,9 @@ AA652CAB25DD820D009059CC /* Bookmarks */ = { isa = PBXGroup; children = ( + 9F872D9B2B9058B000138637 /* Extensions */, + 9FA75A3C2BA00DF500DA5FA6 /* Factory */, + 9F982F102B82264400231028 /* ViewModels */, AA652CAE25DD8228009059CC /* Model */, AA652CAF25DD822C009059CC /* Services */, ); @@ -7016,6 +7310,8 @@ AA652CCD25DD9071009059CC /* BookmarkListTests.swift */, AA652CD225DDA6E9009059CC /* LocalBookmarkManagerTests.swift */, 98A95D87299A2DF900B9B81A /* BookmarkMigrationTests.swift */, + 9F872D9F2B90644800138637 /* ContextualMenuTests.swift */, + 9F0A2CF72B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift */, ); path = Model; sourceTree = ""; @@ -7219,6 +7515,7 @@ AAA0CC32252F181A0079BC96 /* NavigationButtonMenuDelegate.swift */, 85012B0129133F9F003D0DCC /* NavigationBarPopovers.swift */, AA68C3D22490ED62001B8783 /* NavigationBarViewController.swift */, + B60293E52BA19ECD0033186B /* NetPPopoverManagerMock.swift */, D64A5FF72AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift */, ); path = View; @@ -7291,9 +7588,11 @@ AA91F83627076ED100771A0D /* NavigationBar */ = { isa = PBXGroup; children = ( + C17CA7B02B9B52FF008EC3C1 /* Mocks */, AA91F83727076EEE00771A0D /* ViewModel */, 4BF6961F28BEEE8B00D402D4 /* LocalPinningManagerTests.swift */, 56B234BE2A84EFD200F2A1CC /* NavigationBarUrlExtensionsTests.swift */, + C17CA7AF2B9B52EB008EC3C1 /* View */, ); path = NavigationBar; sourceTree = ""; @@ -7401,8 +7700,10 @@ B69A14F12B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift */, B69A14F52B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift */, AAB549DE25DAB8F80058460B /* BookmarkViewModel.swift */, - B6F9BDD72B45B7D900677B33 /* AddBookmarkModalViewModel.swift */, - B6F9BDDF2B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift */, + 9F982F0C2B8224BE00231028 /* AddEditBookmarkFolderDialogViewModel.swift */, + 9F56CFAC2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift */, + 9FEE98682B85B869002E44E8 /* BookmarksDialogViewModel.swift */, + 9FEE986C2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -7473,9 +7774,8 @@ AAC5E4C125D6A6C3007F5990 /* View */ = { isa = PBXGroup; children = ( - 4B9292CA2667123700AD2C21 /* AddBookmarkFolderModalView.swift */, + 9FA173DD2B7A0ECE00EE4E6E /* Dialog */, 7BEC20412B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift */, - 4B9292CB2667123700AD2C21 /* AddBookmarkModalView.swift */, AAC5E4C425D6A6E8007F5990 /* AddBookmarkPopover.swift */, 7BEC20402B0F505F00243D3E /* AddBookmarkPopoverView.swift */, B69A14F92B4D705D00B9417D /* BookmarkFolderPicker.swift */, @@ -7513,6 +7813,7 @@ AAC5E4CE25D6A709007F5990 /* BookmarkManager.swift */, 379E877529E98729001C8BB0 /* BookmarksCleanupErrorHandling.swift */, B6F9BDDB2B45B7EE00677B33 /* WebsiteInfo.swift */, + 9F872DA22B90920F00138637 /* BookmarkFolderInfo.swift */, ); path = Model; sourceTree = ""; @@ -8169,7 +8470,7 @@ 4BB88B4425B7B55C006F6B06 /* DebugUserScript.swift */, 856CADEF271710F400E79BB0 /* HoverUserScript.swift */, 4B2E7D6226FF9D6500D2DB17 /* PrintingUserScript.swift */, - 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */, + F1B33DF92BAD9C83001128B3 /* Subscription */, 1ED910D42B63BFB300936947 /* IdentityTheftRestorationPagesUserScript.swift */, 85AC3AEE25D5CE9800C7D2AA /* UserScripts.swift */, ); @@ -8299,6 +8600,22 @@ path = Tests; sourceTree = ""; }; + C17CA7AF2B9B52EB008EC3C1 /* View */ = { + isa = PBXGroup; + children = ( + C17CA7AC2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift */, + ); + path = View; + sourceTree = ""; + }; + C17CA7B02B9B52FF008EC3C1 /* Mocks */ = { + isa = PBXGroup; + children = ( + C17CA7B12B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift */, + ); + path = Mocks; + sourceTree = ""; + }; C1E961E62B879E2A001760E1 /* Mocks */ = { isa = PBXGroup; children = ( @@ -8357,6 +8674,15 @@ path = JSAlert; sourceTree = ""; }; + EEBCE6802BA444FA00B9DF00 /* Common */ = { + isa = PBXGroup; + children = ( + EE02D4192BB4609900DBE6B3 /* UITests.swift */, + EEBCE6812BA444FA00B9DF00 /* XCUIElementExtension.swift */, + ); + path = Common; + sourceTree = ""; + }; EEC589D62A4F1B1F00BCD60C /* AppAndExtensionAndAgentTargets */ = { isa = PBXGroup; children = ( @@ -8376,6 +8702,16 @@ path = JSAlert; sourceTree = ""; }; + F1B33DF92BAD9C83001128B3 /* Subscription */ = { + isa = PBXGroup; + children = ( + 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */, + F1B33DF12BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift */, + F1B33DF52BAD970E001128B3 /* SubscriptionErrorReporter.swift */, + ); + path = Subscription; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -8427,6 +8763,9 @@ 7B1459562B7D43E500047F2C /* NetworkProtectionProxy */, 85E2BBCF2B8F534A00DBEC7A /* History */, F1D43AF42B98E48900BAB743 /* BareBonesBrowserKit */, + 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */, + 85D44B872BA08D30001B4AB5 /* Suggestions */, + 4BCBE4592BA7E17800FC75A1 /* Subscription */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8547,6 +8886,7 @@ EE7295E82A545BC4008C0991 /* NetworkProtection */, 37269F002B332FC8005E8E46 /* Common */, 7BBE2B7A2B61663C00697445 /* NetworkProtectionProxy */, + 7B37C7A42BAA32A50062546A /* Subscription */, ); productName = NetworkProtectionSystemExtension; productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */; @@ -8611,6 +8951,7 @@ 7BFCB74F2ADE7E2300DA3EA7 /* PixelKit */, 7B00997E2B6508C200FE7C31 /* NetworkProtectionProxy */, 4BA7C4DC2B3F64E500AFE511 /* LoginItems */, + 4BCBE45B2BA7E18500FC75A1 /* Subscription */, ); productName = DuckDuckGoAgentAppStore; productReference = 4B2D06692A13318400DE1F49 /* DuckDuckGo VPN App Store.app */; @@ -8632,8 +8973,8 @@ ); name = DuckDuckGoNotifications; packageProductDependencies = ( - EE7295EA2A545BFC008C0991 /* NetworkProtection */, 37269F042B3332C2005E8E46 /* Common */, + 7B624F162BA25C1F00A6C544 /* NetworkProtectionUI */, ); productName = DuckDuckGoNotifications; productReference = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; @@ -8659,6 +9000,7 @@ 4B4D60AE2A0C837F00BCD287 /* Networking */, EE7295E62A545BBB008C0991 /* NetworkProtection */, 37269EFE2B332FBB005E8E46 /* Common */, + 7B25856D2BA2F2ED00D49F79 /* NetworkProtectionUI */, ); productName = NetworkProtectionAppExtension; productReference = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; @@ -8713,6 +9055,7 @@ 7B94E1642B7ED95100E32B96 /* NetworkProtectionProxy */, 85E2BBD12B8F536F00DBEC7A /* History */, F1D43AF62B98E48F00BAB743 /* BareBonesBrowserKit */, + 85D44B892BA08D3B001B4AB5 /* Suggestions */, ); productName = DuckDuckGo; productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */; @@ -8749,11 +9092,13 @@ buildRules = ( ); dependencies = ( + EE02D41E2BB460B500DBE6B3 /* PBXTargetDependency */, B6F997BD2B8F35EF00476735 /* PBXTargetDependency */, ); name = "UI Tests"; packageProductDependencies = ( B65CD8D22B316E1700A595BB /* SnapshotTesting */, + EE02D41F2BB460C000DBE6B3 /* BrowserServicesKit */, ); productName = "UI Tests"; productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */; @@ -8882,6 +9227,7 @@ 1EA7B8D22B7E078C000330A4 /* SubscriptionUI */, 1EA7B8D42B7E078C000330A4 /* Subscription */, F1D43AF22B98E47800BAB743 /* BareBonesBrowserKit */, + 85D44B852BA08D29001B4AB5 /* Suggestions */, ); productName = DuckDuckGo; productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */; @@ -9253,6 +9599,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1D85BCCA2BA982FC0065BA04 /* InfoPlist.xcstrings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -9729,7 +10076,9 @@ 4B41EDA82B1543C9001EEDF4 /* PreferencesVPNView.swift in Sources */, 3706FA7B293F65D500E42796 /* FaviconUserScript.swift in Sources */, 3706FA7E293F65D500E42796 /* LottieAnimationCache.swift in Sources */, + 9F982F0E2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */, 3706FA7F293F65D500E42796 /* TabIndex.swift in Sources */, + 4B520F642BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */, 3706FA80293F65D500E42796 /* TabLazyLoaderDataSource.swift in Sources */, 3706FA81293F65D500E42796 /* LoginImport.swift in Sources */, C13909FC2B861039001626ED /* AutofillActionPresenter.swift in Sources */, @@ -9744,6 +10093,7 @@ 3706FA89293F65D500E42796 /* CrashReportPromptPresenter.swift in Sources */, 3706FA8B293F65D500E42796 /* PreferencesRootView.swift in Sources */, 3706FA8C293F65D500E42796 /* AppStateChangedPublisher.swift in Sources */, + 9FEE986E2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */, 3706FA8D293F65D500E42796 /* BookmarkTableCellView.swift in Sources */, 3706FA8E293F65D500E42796 /* BookmarkManagementSidebarViewController.swift in Sources */, 3706FA8F293F65D500E42796 /* NSStackViewExtension.swift in Sources */, @@ -9754,6 +10104,7 @@ 3706FA94293F65D500E42796 /* TabShadowConfig.swift in Sources */, 3706FA97293F65D500E42796 /* WindowDraggingView.swift in Sources */, B60D644A2AAF1B7C00B26F50 /* AddressBarTextSelectionNavigation.swift in Sources */, + 1D01A3D52B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */, 3706FA98293F65D500E42796 /* SecureVaultSorting.swift in Sources */, 3706FA99293F65D500E42796 /* PreferencesSidebarModel.swift in Sources */, 3706FA9A293F65D500E42796 /* DuckPlayerURLExtension.swift in Sources */, @@ -9794,6 +10145,8 @@ 4B9DB0332A983B24000927DB /* EnableWaitlistFeatureView.swift in Sources */, 3706FABA293F65D500E42796 /* BookmarkOutlineViewDataSource.swift in Sources */, 3706FABB293F65D500E42796 /* PasswordManagementBitwardenItemView.swift in Sources */, + 1D220BF92B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */, + 9FA173E42B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */, 3706FABD293F65D500E42796 /* NSNotificationName+PasswordManager.swift in Sources */, 3706FABE293F65D500E42796 /* RulesCompilationMonitor.swift in Sources */, 3706FABF293F65D500E42796 /* CrashReportReader.swift in Sources */, @@ -9805,10 +10158,12 @@ F1D43AEF2B98D8DF00BAB743 /* MainMenuActions+VanillaBrowser.swift in Sources */, B6E1491129A5C30A00AAFBE8 /* FBProtectionTabExtension.swift in Sources */, 3706FAC4293F65D500E42796 /* PrintingUserScript.swift in Sources */, + 1D01A3D92B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */, 9D9AE86C2AA76D1B0026E7DC /* LoginItemsManager.swift in Sources */, 4B9DB0392A983B24000927DB /* JoinedWaitlistView.swift in Sources */, 3706FEBF293F6EFF00E42796 /* BWError.swift in Sources */, 3706FAC6293F65D500E42796 /* ConnectBitwardenViewController.swift in Sources */, + 1DDC84FC2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */, EEC589DA2A4F1CE400BCD60C /* AppLauncher.swift in Sources */, 3706FAC8293F65D500E42796 /* AppTrackerDataSetProvider.swift in Sources */, 3706FAC9293F65D500E42796 /* EncryptionKeyGeneration.swift in Sources */, @@ -9835,7 +10190,7 @@ 3706FADE293F65D500E42796 /* PreferencesDuckPlayerView.swift in Sources */, EEC4A65E2B277E8D00F7C0AA /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */, B66260E729ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift in Sources */, - 3706FADF293F65D500E42796 /* AddBookmarkFolderModalView.swift in Sources */, + 1DDD3EC52B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, 3706FAE0293F65D500E42796 /* BookmarkSidebarTreeController.swift in Sources */, 3706FAE1293F65D500E42796 /* HomePageFavoritesModel.swift in Sources */, 3706FAE2293F65D500E42796 /* SequenceExtensions.swift in Sources */, @@ -9855,6 +10210,7 @@ 3706FAF1293F65D500E42796 /* PreferencesAboutView.swift in Sources */, 3706FAF2293F65D500E42796 /* ContentBlocking.swift in Sources */, 31F2D2002AF026D800BF0144 /* WaitlistTermsAndConditionsActionHandler.swift in Sources */, + 9FA173E02B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */, 3706FAF3293F65D500E42796 /* LocalAuthenticationService.swift in Sources */, 1D36E659298AA3BA00AA485D /* InternalUserDeciderStore.swift in Sources */, B6BCC5242AFCDABB002C5499 /* DataImportSourceViewModel.swift in Sources */, @@ -9865,6 +10221,7 @@ B65211262B29A42E00B30633 /* BookmarkStoreMock.swift in Sources */, 3706FAF5293F65D500E42796 /* SafariVersionReader.swift in Sources */, 3706FAF6293F65D500E42796 /* LoginFaviconView.swift in Sources */, + F1B33DF72BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, 3706FEC0293F6EFF00E42796 /* BWRequest.swift in Sources */, 3706FAF7293F65D500E42796 /* FireproofDomainsViewController.swift in Sources */, 4BF0E5062AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, @@ -9891,6 +10248,7 @@ 3706FB07293F65D500E42796 /* Publisher.asVoid.swift in Sources */, 3706FB08293F65D500E42796 /* NavigationButtonMenuDelegate.swift in Sources */, 4BF97ADB2B43C5E000EB4240 /* VPNMetadataCollector.swift in Sources */, + 1DDC84F82B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, 3706FB09293F65D500E42796 /* CrashReport.swift in Sources */, 1E0C72072ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */, 3706FB0A293F65D500E42796 /* NSPathControlView.swift in Sources */, @@ -9917,7 +10275,8 @@ 3793FDD829535EBA00A2E28F /* Assertions.swift in Sources */, B62B48572ADE730D000DECE5 /* FileImportView.swift in Sources */, B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */, - 3706FB1F293F65D500E42796 /* PrivacyPreferencesModel.swift in Sources */, + 3706FB1F293F65D500E42796 /* DataClearingPreferences.swift in Sources */, + 1D01A3D12B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 3706FB20293F65D500E42796 /* LocalUnprotectedDomains.swift in Sources */, 3707C719294B5D0F00682A9F /* HoveredLinkTabExtension.swift in Sources */, 3706FB21293F65D500E42796 /* NavigationBarBadgeAnimator.swift in Sources */, @@ -9996,7 +10355,6 @@ 3706FB5E293F65D500E42796 /* EncryptionKeyStore.swift in Sources */, 3706FB60293F65D500E42796 /* PasswordManagementIdentityItemView.swift in Sources */, 3706FB61293F65D500E42796 /* ProgressExtension.swift in Sources */, - B6F9BDD92B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */, 3706FB62293F65D500E42796 /* CSVParser.swift in Sources */, 3706FB64293F65D500E42796 /* PixelDataModel.xcdatamodeld in Sources */, B626A75B29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */, @@ -10009,15 +10367,16 @@ 3706FB69293F65D500E42796 /* NavigationBarBadgeAnimationView.swift in Sources */, 1D1A334A2A6FEB170080ACED /* BurnerMode.swift in Sources */, B603971B29BA084C00902A34 /* JSAlertController.swift in Sources */, + 9FEE986A2B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */, 3706FB6A293F65D500E42796 /* AddressBarButton.swift in Sources */, 4B41EDA42B1543B9001EEDF4 /* VPNPreferencesModel.swift in Sources */, + 7B7FCD102BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */, 3706FB6C293F65D500E42796 /* FaviconStore.swift in Sources */, 3706FB6D293F65D500E42796 /* SuggestionListCharacteristics.swift in Sources */, 377D801F2AB48191002AF251 /* FavoritesDisplayModeSyncHandler.swift in Sources */, 4B4D60C62A0C849600BCD287 /* NetworkProtectionInviteCodeViewModel.swift in Sources */, 3706FB6F293F65D500E42796 /* BookmarkListViewController.swift in Sources */, 3706FB70293F65D500E42796 /* SecureVaultLoginImporter.swift in Sources */, - 3706FB71293F65D500E42796 /* AddBookmarkModalView.swift in Sources */, 3706FB72293F65D500E42796 /* RecentlyClosedCoordinator.swift in Sources */, 3706FB74293F65D500E42796 /* FaviconHostReference.swift in Sources */, B69A14F32B4D6FE800B9417D /* AddBookmarkFolderPopoverViewModel.swift in Sources */, @@ -10026,6 +10385,7 @@ 37CBCA9B2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */, 3707C72A294B5D2900682A9F /* URLExtension.swift in Sources */, 3706FB76293F65D500E42796 /* ASN1Parser.swift in Sources */, + 9F56CFAA2B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, 37FD78122A29EBD100B36DB1 /* SyncErrorHandler.swift in Sources */, 987799F42999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, 3706FB7A293F65D500E42796 /* FileDownloadManager.swift in Sources */, @@ -10054,7 +10414,6 @@ 37197EA42942441D00394917 /* NewWindowPolicy.swift in Sources */, 3706FB84293F65D500E42796 /* NSWindowExtension.swift in Sources */, 3706FB85293F65D500E42796 /* AddBookmarkPopover.swift in Sources */, - 3706FB86293F65D500E42796 /* PreferencesDownloadsView.swift in Sources */, 7BBD45B22A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, 3706FB87293F65D500E42796 /* ProcessExtension.swift in Sources */, 3706FB88293F65D500E42796 /* PermissionAuthorizationQuery.swift in Sources */, @@ -10083,9 +10442,7 @@ B690152D2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, 1D36F4252A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */, 3706FB97293F65D500E42796 /* ActionSpeech.swift in Sources */, - 3706FB99293F65D500E42796 /* PrivacySecurityPreferences.swift in Sources */, B6AFE6BD29A5D621002FF962 /* HTTPSUpgradeTabExtension.swift in Sources */, - B6F9BDE12B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */, 3706FB9A293F65D500E42796 /* FireproofDomainsStore.swift in Sources */, 3706FB9B293F65D500E42796 /* PrivacyDashboardPermissionHandler.swift in Sources */, 3706FB9C293F65D500E42796 /* TabCollectionViewModel.swift in Sources */, @@ -10098,10 +10455,12 @@ 3706FB9E293F65D500E42796 /* AboutModel.swift in Sources */, 3706FB9F293F65D500E42796 /* PasswordManagementCreditCardItemView.swift in Sources */, 3706FBA0293F65D500E42796 /* NSTextFieldExtension.swift in Sources */, + 9FA173E82B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */, 3706FBA1293F65D500E42796 /* FireproofDomainsContainer.swift in Sources */, 3706FBA2293F65D500E42796 /* GeolocationService.swift in Sources */, 4B4D60C42A0C849600BCD287 /* NetworkProtectionInvitePresenter.swift in Sources */, 3706FBA3293F65D500E42796 /* FireproofingURLExtensions.swift in Sources */, + 1DDD3EC12B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, 3706FBA4293F65D500E42796 /* ContentOverlayPopover.swift in Sources */, 3706FBA5293F65D500E42796 /* TabShadowView.swift in Sources */, 3706FBA7293F65D500E42796 /* EncryptedValueTransformer.swift in Sources */, @@ -10112,7 +10471,6 @@ B66260E829ACD0C900E9E3EE /* DuckPlayerTabExtension.swift in Sources */, 3706FBAA293F65D500E42796 /* HoverUserScript.swift in Sources */, 3706FBAC293F65D500E42796 /* MainMenuActions.swift in Sources */, - 1EA7B8D92B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */, 4BF97AD92B43C5C000EB4240 /* Bundle+VPN.swift in Sources */, 3706FBAE293F65D500E42796 /* DataImport.swift in Sources */, 3706FBAF293F65D500E42796 /* FireproofDomains.xcdatamodeld in Sources */, @@ -10154,6 +10512,7 @@ 3706FBCC293F65D500E42796 /* CustomRoundedCornersShape.swift in Sources */, 3706FBCD293F65D500E42796 /* LocaleExtension.swift in Sources */, 3706FBCE293F65D500E42796 /* SavePaymentMethodViewController.swift in Sources */, + 1DDC85002B835BC000670238 /* SearchPreferences.swift in Sources */, 3706FBD0293F65D500E42796 /* WebKitVersionProvider.swift in Sources */, 3706FBD1293F65D500E42796 /* NSCoderExtensions.swift in Sources */, B6D6A5DD2982A4CE001F5F11 /* Tab+Navigation.swift in Sources */, @@ -10161,7 +10520,6 @@ B684121D2B6A1D880092F66A /* ErrorPageHTMLTemplate.swift in Sources */, 3706FBD2293F65D500E42796 /* RunningApplicationCheck.swift in Sources */, 3706FBD3293F65D500E42796 /* StatePersistenceService.swift in Sources */, - EE0629732B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */, 3706FBD4293F65D500E42796 /* WindowManager+StateRestoration.swift in Sources */, 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 3706FBD5293F65D500E42796 /* TabCollection+NSSecureCoding.swift in Sources */, @@ -10173,6 +10531,7 @@ 3706FBD9293F65D500E42796 /* NSAppearanceExtension.swift in Sources */, 3706FBDA293F65D500E42796 /* PermissionManager.swift in Sources */, 3706FBDB293F65D500E42796 /* DefaultBrowserPreferences.swift in Sources */, + 9FEE98662B846870002E44E8 /* AddEditBookmarkView.swift in Sources */, 3706FBDC293F65D500E42796 /* Permissions.xcdatamodeld in Sources */, 4B41EDB52B168C55001EEDF4 /* VPNFeedbackFormViewModel.swift in Sources */, 3706FBDD293F65D500E42796 /* PaddedImageButton.swift in Sources */, @@ -10187,6 +10546,7 @@ EEC4A6722B2C90AB00F7C0AA /* VPNLocationPreferenceItem.swift in Sources */, 3706FBE0293F65D500E42796 /* NSException+Catch.m in Sources */, 3706FBE1293F65D500E42796 /* AppStateRestorationManager.swift in Sources */, + 9FA173EC2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 3706FBE2293F65D500E42796 /* ClickToLoadUserScript.swift in Sources */, 3706FBE3293F65D500E42796 /* WindowControllersManager.swift in Sources */, 37197EAA2942443D00394917 /* ModalSheetCancellable.swift in Sources */, @@ -10208,9 +10568,11 @@ 3706FBF0293F65D500E42796 /* PasswordManagementItemModel.swift in Sources */, 3706FBF2293F65D500E42796 /* FindInPageModel.swift in Sources */, 1D9A4E5B2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */, + 9F56CFAE2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */, 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */, 1D26EBAD2B74BECB0002A93F /* NSImageSendable.swift in Sources */, 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */, + 1D220BFD2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */, 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */, 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */, @@ -10229,10 +10591,11 @@ 3706FC01293F65D500E42796 /* ChromiumBookmarksReader.swift in Sources */, 3706FC02293F65D500E42796 /* Downloads.xcdatamodeld in Sources */, B60C6F7829B0E286007BFAA8 /* SearchNonexistentDomainNavigationResponder.swift in Sources */, + 9F56CFB22B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */, 3707C720294B5D2900682A9F /* WKWebsiteDataStoreExtension.swift in Sources */, 3706FC03293F65D500E42796 /* TabPreviewViewController.swift in Sources */, 4B9754EC2984300100D7B834 /* EmailManagerExtension.swift in Sources */, - 3706FC04293F65D500E42796 /* PreferencesPrivacyView.swift in Sources */, + 3706FC04293F65D500E42796 /* PreferencesDataClearingView.swift in Sources */, 3706FC05293F65D500E42796 /* NSPasteboardExtension.swift in Sources */, 1DCFBC8B29ADF32B00313531 /* BurnerHomePageView.swift in Sources */, 3706FC06293F65D500E42796 /* OnboardingViewModel.swift in Sources */, @@ -10250,7 +10613,10 @@ 4B9DB0452A983B24000927DB /* WaitlistModalViewController.swift in Sources */, B66CA41F2AD910B300447CF0 /* DataImportView.swift in Sources */, 3706FC0C293F65D500E42796 /* NSAttributedStringExtension.swift in Sources */, + 4B6B64852BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */, + C1DAF3B62B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */, 3706FC0D293F65D500E42796 /* AnimationView.swift in Sources */, + 9FA173DB2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */, 3706FC0E293F65D500E42796 /* NSRectExtension.swift in Sources */, 3706FC0F293F65D500E42796 /* YoutubeOverlayUserScript.swift in Sources */, 3775913729AB9A1C00E26367 /* SyncManagementDialogViewController.swift in Sources */, @@ -10268,14 +10634,18 @@ 3706FC19293F65D500E42796 /* NSNotificationName+Favicons.swift in Sources */, 3706FC1A293F65D500E42796 /* PinningManager.swift in Sources */, 4B37EE782B4CFF3900A89A61 /* DataBrokerProtectionRemoteMessage.swift in Sources */, + 7BBA7CE72BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 3706FC1B293F65D500E42796 /* TabCollectionViewModel+NSSecureCoding.swift in Sources */, 3706FC1D293F65D500E42796 /* EmailManagerRequestDelegate.swift in Sources */, 3706FC1E293F65D500E42796 /* ApplicationVersionReader.swift in Sources */, + F1B33DF32BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */, 3706FC1F293F65D500E42796 /* BookmarksBarViewController.swift in Sources */, + 1DDC85042B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */, 3706FC20293F65D500E42796 /* PreferencesAutofillView.swift in Sources */, 3706FC21293F65D500E42796 /* UserText+PasswordManager.swift in Sources */, 3706FC22293F65D500E42796 /* LoadingProgressView.swift in Sources */, 3706FC23293F65D500E42796 /* StatisticsStore.swift in Sources */, + 1DDD3EBD2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */, 3706FC25293F65D500E42796 /* ColorView.swift in Sources */, 3706FC26293F65D500E42796 /* RecentlyClosedCacheItem.swift in Sources */, 3706FC27293F65D500E42796 /* PopupBlockedPopover.swift in Sources */, @@ -10319,6 +10689,7 @@ 3706FC43293F65D500E42796 /* PinnedTabsViewModel.swift in Sources */, 85D0327C2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, B6685E4329A61C470043D2EE /* DownloadsTabExtension.swift in Sources */, + 4BCBE4562BA7E16900FC75A1 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, 3706FC44293F65D500E42796 /* BookmarkList.swift in Sources */, 3706FC45293F65D500E42796 /* BookmarkTableRowView.swift in Sources */, 7BEC20462B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, @@ -10335,11 +10706,14 @@ 3706FC50293F65D500E42796 /* FeedbackWindow.swift in Sources */, 3706FC51293F65D500E42796 /* RecentlyVisitedView.swift in Sources */, B645D8F729FA95440024461F /* WKProcessPoolExtension.swift in Sources */, + 9F514F922B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */, + B60293E72BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, 3706FC53293F65D500E42796 /* TabBarScrollView.swift in Sources */, 3706FC54293F65D500E42796 /* BookmarkListTreeControllerDataSource.swift in Sources */, 3706FC55293F65D500E42796 /* AddressBarViewController.swift in Sources */, 3706FC56293F65D500E42796 /* Permissions.swift in Sources */, + 9F872D992B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */, B6B4D1D02B0E0DD000C26286 /* DataImportNoDataView.swift in Sources */, 3706FC57293F65D500E42796 /* TabPreviewWindowController.swift in Sources */, 3706FC58293F65D500E42796 /* NSSizeExtension.swift in Sources */, @@ -10364,6 +10738,7 @@ 3706FC65293F65D500E42796 /* HomePageViewController.swift in Sources */, 3706FC67293F65D500E42796 /* OperatingSystemVersionExtension.swift in Sources */, B6F9BDE52B45CD1900677B33 /* ModalView.swift in Sources */, + 9F872DA42B90920F00138637 /* BookmarkFolderInfo.swift in Sources */, B677FC502B06376B0099EB04 /* ReportFeedbackView.swift in Sources */, 3706FC68293F65D500E42796 /* ToggleableScrollView.swift in Sources */, 3706FC69293F65D500E42796 /* UserScripts.swift in Sources */, @@ -10381,6 +10756,7 @@ 3706FC73293F65D500E42796 /* AddressBarButtonsViewController.swift in Sources */, 3706FC76293F65D500E42796 /* PixelDataRecord.swift in Sources */, 7BFE955A2A9DF4550081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, + 9FDA6C222B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, 3706FC77293F65D500E42796 /* PageObserverUserScript.swift in Sources */, 4BF0E5132AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 3706FC78293F65D500E42796 /* SecureVaultErrorReporter.swift in Sources */, @@ -10396,6 +10772,7 @@ 3706FC81293F65D500E42796 /* DispatchQueueExtensions.swift in Sources */, C13909F02B85FD4E001626ED /* AutofillActionExecutor.swift in Sources */, 3706FC82293F65D500E42796 /* PermissionAuthorizationPopover.swift in Sources */, + 4BCBE4552BA7E16600FC75A1 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, 3706FC83293F65D500E42796 /* PopoverMessageViewController.swift in Sources */, 4BF97ADA2B43C5DC00EB4240 /* VPNFeedbackCategory.swift in Sources */, 9D9AE86E2AA76D1F0026E7DC /* LoginItem+NetworkProtection.swift in Sources */, @@ -10457,6 +10834,7 @@ 3706FDDE293F661700E42796 /* SuggestionViewModelTests.swift in Sources */, 3706FDDF293F661700E42796 /* BookmarkSidebarTreeControllerTests.swift in Sources */, 3706FDE0293F661700E42796 /* TabIndexTests.swift in Sources */, + 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */, 3706FDE1293F661700E42796 /* AdjacentItemEnumeratorTests.swift in Sources */, 3706FDE2293F661700E42796 /* PixelArgumentsTests.swift in Sources */, 4B9DB0572A983B55000927DB /* MockNotificationService.swift in Sources */, @@ -10476,6 +10854,7 @@ 3706FDF0293F661700E42796 /* WebKitVersionProviderTests.swift in Sources */, 3706FDF1293F661700E42796 /* AtbAndVariantCleanupTests.swift in Sources */, 1D1C36E729FB019C001FA40C /* HistoryTabExtensionTests.swift in Sources */, + 1D9FDEBB2B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift in Sources */, 3706FDF3293F661700E42796 /* ChromiumLoginReaderTests.swift in Sources */, 3706FDF4293F661700E42796 /* TabCollectionTests.swift in Sources */, 3706FDF5293F661700E42796 /* StartupPreferencesTests.swift in Sources */, @@ -10485,6 +10864,7 @@ 3706FDF8293F661700E42796 /* FileStoreTests.swift in Sources */, 5603D90729B7B746007F9F01 /* MockTabViewItemDelegate.swift in Sources */, 3706FDF9293F661700E42796 /* TabViewModelTests.swift in Sources */, + 9F872DA12B90644800138637 /* ContextualMenuTests.swift in Sources */, 3706FDFA293F661700E42796 /* DefaultBrowserPreferencesTests.swift in Sources */, 3706FDFB293F661700E42796 /* DispatchQueueExtensionsTests.swift in Sources */, 9F180D102B69C553000D695F /* Tab+WKUIDelegateTests.swift in Sources */, @@ -10498,6 +10878,7 @@ 3706FE01293F661700E42796 /* PixelStoreMock.swift in Sources */, 3706FE02293F661700E42796 /* BookmarksBarViewModelTests.swift in Sources */, 3706FE03293F661700E42796 /* CoreDataStoreTests.swift in Sources */, + 1D9FDEC42B9B63C90040B78C /* DataClearingPreferencesTests.swift in Sources */, 3706FE04293F661700E42796 /* TreeControllerTests.swift in Sources */, 3706FE05293F661700E42796 /* DownloadsWebViewMock.m in Sources */, 3706FE06293F661700E42796 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, @@ -10512,6 +10893,7 @@ 3706FE0E293F661700E42796 /* FirefoxDataImporterTests.swift in Sources */, 3706FE0F293F661700E42796 /* CSVLoginExporterTests.swift in Sources */, 3706FE10293F661700E42796 /* TestNavigationDelegate.swift in Sources */, + 1D9FDEC12B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift in Sources */, 3706FE11293F661700E42796 /* URLSuggestedFilenameTests.swift in Sources */, 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, B6F56569299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, @@ -10529,6 +10911,7 @@ 3706FE1C293F661700E42796 /* ConnectBitwardenViewModelTests.swift in Sources */, 4B9DB0552A983B55000927DB /* MockWaitlistStorage.swift in Sources */, 3706FE1D293F661700E42796 /* PixelStoreTests.swift in Sources */, + 1D9FDEC72B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, C13909F52B85FD79001626ED /* AutofillDeleteAllPasswordsExecutorTests.swift in Sources */, 857E44642A9F70F200ED77A7 /* CampaignVariantTests.swift in Sources */, 3706FE1E293F661700E42796 /* GeolocationProviderTests.swift in Sources */, @@ -10539,6 +10922,7 @@ B6619EF72B10DFF700CD9186 /* InstructionsFormatParserTests.swift in Sources */, 3706FE21293F661700E42796 /* DownloadsPreferencesTests.swift in Sources */, 3706FE22293F661700E42796 /* FireproofDomainsTests.swift in Sources */, + C17CA7AE2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift in Sources */, 3706FE23293F661700E42796 /* SuggestionLoadingMock.swift in Sources */, 3706FE24293F661700E42796 /* PasteboardFolderTests.swift in Sources */, B603971229B9D67E00902A34 /* PublishersExtensions.swift in Sources */, @@ -10547,6 +10931,7 @@ 3706FE26293F661700E42796 /* TemporaryFileCreator.swift in Sources */, 3706FE27293F661700E42796 /* AppPrivacyConfigurationTests.swift in Sources */, B626A7652992506A00053070 /* SerpHeadersNavigationResponderTests.swift in Sources */, + 9F26060C2B85C20B00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */, 3706FE28293F661700E42796 /* BookmarkTests.swift in Sources */, 3706FE29293F661700E42796 /* SuggestionContainerViewModelTests.swift in Sources */, 1D8C2FEB2B70F5A7005E4BBD /* MockWebViewSnapshotRenderer.swift in Sources */, @@ -10576,6 +10961,7 @@ 3706FE3C293F661700E42796 /* FireproofDomainsStoreMock.swift in Sources */, 3706FE3D293F661700E42796 /* DataEncryptionTests.swift in Sources */, 3706FE3E293F661700E42796 /* ClickToLoadModelTests.swift in Sources */, + C17CA7B32B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift in Sources */, 3706FE3F293F661700E42796 /* FileStoreMock.swift in Sources */, B6619F042B17123200CD9186 /* DataImportViewModelTests.swift in Sources */, 1D8C2FE62B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift in Sources */, @@ -10587,6 +10973,7 @@ 3706FE44293F661700E42796 /* GeolocationServiceTests.swift in Sources */, 1DA6D1032A1FFA3B00540406 /* HTTPCookieTests.swift in Sources */, 3706FE45293F661700E42796 /* ProgressEstimationTests.swift in Sources */, + 31A2FD182BAB43BA00D0E741 /* DataBrokerProtectionVisibilityTests.swift in Sources */, B6619F072B17138D00CD9186 /* DataImportSourceViewModelTests.swift in Sources */, 3706FE46293F661700E42796 /* EncryptedValueTransformerTests.swift in Sources */, 9F3910632B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */, @@ -10599,6 +10986,7 @@ 3706FE4A293F661700E42796 /* BookmarkManagedObjectTests.swift in Sources */, EEC8EB402982CD550065AA39 /* JSAlertViewModelTests.swift in Sources */, 3706FE4B293F661700E42796 /* BookmarksHTMLImporterTests.swift in Sources */, + 9FA75A3F2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */, 56D145E929E6BB6300E3488A /* CapturingDataImportProvider.swift in Sources */, 3706FE4C293F661700E42796 /* CSVParserTests.swift in Sources */, 3706FE4D293F661700E42796 /* OnboardingTests.swift in Sources */, @@ -10612,6 +11000,7 @@ 3706FE54293F661700E42796 /* PasteboardBookmarkTests.swift in Sources */, 3706FE55293F661700E42796 /* CBRCompileTimeReporterTests.swift in Sources */, 566B196429CDB824007E38F4 /* MoreOptionsMenuTests.swift in Sources */, + 9F0A2CF92B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift in Sources */, 3706FE56293F661700E42796 /* FaviconManagerMock.swift in Sources */, 3706FE57293F661700E42796 /* LocalPinningManagerTests.swift in Sources */, 3706FE58293F661700E42796 /* HistoryStoreTests.swift in Sources */, @@ -10623,6 +11012,7 @@ 37716D8029707E5D00A9FC6D /* FireproofingReferenceTests.swift in Sources */, B6AA64742994B43300D99CD6 /* FutureExtensionTests.swift in Sources */, 3706FE5C293F661700E42796 /* DuckPlayerPreferencesTests.swift in Sources */, + 1D9FDEB82B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */, 3706FE5D293F661700E42796 /* FileSystemDSL.swift in Sources */, 3706FE5E293F661700E42796 /* DataImportMocks.swift in Sources */, 3706FE5F293F661700E42796 /* CrashReportTests.swift in Sources */, @@ -10635,6 +11025,7 @@ 3706FE64293F661700E42796 /* DownloadListStoreTests.swift in Sources */, 3706FE65293F661700E42796 /* ContentBlockingUpdatingTests.swift in Sources */, 3706FE67293F661700E42796 /* EncryptionMocks.swift in Sources */, + 9F872D9E2B9058D000138637 /* Bookmarks+TabTests.swift in Sources */, 3706FE68293F661700E42796 /* DuckPlayerURLExtensionTests.swift in Sources */, 3706FE6A293F661700E42796 /* FirefoxKeyReaderTests.swift in Sources */, 3706FE6B293F661700E42796 /* AppKitPrivateMethodsAvailabilityTests.swift in Sources */, @@ -10642,6 +11033,7 @@ C1E961F42B87B276001760E1 /* MockAutofillActionPresenter.swift in Sources */, 3706FE6E293F661700E42796 /* FirefoxBookmarksReaderTests.swift in Sources */, 4B9DB05B2A983B55000927DB /* MockWaitlistRequest.swift in Sources */, + 1D9FDEBE2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift in Sources */, 028904212A7B25770028369C /* AppConfigurationURLProviderTests.swift in Sources */, 3706FE6F293F661700E42796 /* LocalStatisticsStoreTests.swift in Sources */, 3706FE70293F661700E42796 /* HistoryCoordinatorTests.swift in Sources */, @@ -10662,9 +11054,11 @@ 3706FE78293F661700E42796 /* HistoryCoordinatingMock.swift in Sources */, 3706FE79293F661700E42796 /* AppearancePreferencesTests.swift in Sources */, 3706FE7A293F661700E42796 /* FirePopoverViewModelTests.swift in Sources */, + 7B09CBAA2BA4BE8200CF245B /* NetworkProtectionPixelEventTests.swift in Sources */, 3706FE7B293F661700E42796 /* HistoryStoringMock.swift in Sources */, 562984702AC4610100AC20EB /* SyncPreferencesTests.swift in Sources */, 3706FE7C293F661700E42796 /* LocalBookmarkStoreTests.swift in Sources */, + 9F982F142B822C7400231028 /* AddEditBookmarkFolderDialogViewModelTests.swift in Sources */, B6CA4825298CE4B70067ECCE /* AdClickAttributionTabExtensionTests.swift in Sources */, 3707C72D294B5D4100682A9F /* EmptyAttributionRulesProver.swift in Sources */, 376E2D2629428353001CD31B /* PrivacyReferenceTestHelper.swift in Sources */, @@ -10767,10 +11161,10 @@ files = ( 4B25377A2A11C01700610219 /* UserText+NetworkProtectionExtensions.swift in Sources */, B65DA5F42A77D3FA00CBEE8D /* BundleExtension.swift in Sources */, + EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, 4B2D062D2A11C12300DE1F49 /* Logging.swift in Sources */, 7B2E52252A5FEC09000C6D39 /* NetworkProtectionAgentNotificationsPresenter.swift in Sources */, B602E8232A1E260E006D261F /* Bundle+NetworkProtectionExtensions.swift in Sources */, - EEAD7A7B2A1D3E20002A24E7 /* AppLauncher.swift in Sources */, 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */, B602E8192A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */, @@ -10791,10 +11185,10 @@ files = ( B6F92BA22A691580002ABA6B /* UserDefaultsWrapper.swift in Sources */, 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */, - 7BA7CC5B2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */, 7BA7CC3A2AD11E2D0042E5CE /* DuckDuckGoVPNAppDelegate.swift in Sources */, 7BAF9E4C2A8A3CCA002D3B6E /* UserDefaults+NetworkProtectionShared.swift in Sources */, 7BA7CC592AD1203B0042E5CE /* UserText+NetworkProtection.swift in Sources */, + EEDE50112BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */, 7BA7CC562AD11FFB0042E5CE /* NetworkProtectionOptionKeyExtension.swift in Sources */, 7B2DDCFA2A93B25F0039D884 /* KeychainType+ClientDefault.swift in Sources */, 7BA7CC4C2AD11EC70042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, @@ -10802,9 +11196,9 @@ 7B8DB31A2B504D7500EC16DA /* VPNAppEventsHandler.swift in Sources */, 7BA7CC532AD11FCE0042E5CE /* Bundle+VPN.swift in Sources */, 7BFE95562A9DF29B0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, - EE0629742B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */, 7BA7CC5D2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, 7BA7CC4A2AD11EA00042E5CE /* NetworkProtectionTunnelController.swift in Sources */, + EE3424602BA0853900173B1B /* VPNUninstaller.swift in Sources */, 7BD1688E2AD4A4C400D24876 /* NetworkExtensionController.swift in Sources */, 7BA7CC3E2AD11E380042E5CE /* TunnelControllerIPCService.swift in Sources */, 7BA7CC402AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, @@ -10828,17 +11222,17 @@ 4BA7C4DB2B3F63AE00AFE511 /* NetworkExtensionController.swift in Sources */, 4B2D067C2A13340900DE1F49 /* Logging.swift in Sources */, 7B1459552B7D438F00047F2C /* VPNProxyLauncher.swift in Sources */, + EEDE50122BA360C80017F3C4 /* NetworkProtection+VPNAgentConvenienceInitializers.swift in Sources */, B6F92BAD2A6937B5002ABA6B /* OptionalExtension.swift in Sources */, 4BA7C4D92B3F61FB00AFE511 /* BundleExtension.swift in Sources */, - 7BA7CC5A2AD120640042E5CE /* NetworkProtection+ConvenienceInitializers.swift in Sources */, EEC589DC2A4F1CE800BCD60C /* AppLauncher.swift in Sources */, 7BA7CC3F2AD11E3D0042E5CE /* AppLauncher+DefaultInitializer.swift in Sources */, 4B0EF7292B5780EB009D6481 /* VPNAppEventsHandler.swift in Sources */, 7BA7CC412AD11E420042E5CE /* NetworkProtectionBouncer.swift in Sources */, 4BF0E5082AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, - EE0629752B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */, 7BA7CC582AD1203A0042E5CE /* UserText+NetworkProtection.swift in Sources */, 7BA7CC4B2AD11EC60042E5CE /* NetworkProtectionControllerErrorStore.swift in Sources */, + EE3424612BA0853900173B1B /* VPNUninstaller.swift in Sources */, 4BF0E5152AD25A2600FFEC9E /* DuckDuckGoUserAgent.swift in Sources */, 7BFE95592A9DF2AF0081ABE9 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, 7BA7CC5C2AD120C30042E5CE /* EventMapping+NetworkProtectionError.swift in Sources */, @@ -10874,7 +11268,9 @@ buildActionMask = 2147483647; files = ( 4B41EDA02B15437A001EEDF4 /* NetworkProtectionNotificationsPresenterFactory.swift in Sources */, + 7B25856C2BA2F2D000D49F79 /* AppLauncher.swift in Sources */, 4B4D609F2A0B2C7300BCD287 /* Logging.swift in Sources */, + EE66418C2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */, 7B7DFB202B7E736B009EA1A3 /* MacPacketTunnelProvider.swift in Sources */, 4B4D60A12A0B2D6100BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */, B602E8182A1E2570006D261F /* URL+NetworkProtection.swift in Sources */, @@ -10886,7 +11282,6 @@ 4BF0E50C2AD2552300FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B4D60AC2A0C804B00BCD287 /* OptionalExtension.swift in Sources */, B65DA5F22A77D3C600CBEE8D /* UserDefaultsWrapper.swift in Sources */, - EEAD7A7A2A1D3E20002A24E7 /* AppLauncher.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -10914,7 +11309,9 @@ 4B9579542AC7AE700062CA31 /* DownloadListStore.swift in Sources */, 4B9579552AC7AE700062CA31 /* Logging.swift in Sources */, 4B9579562AC7AE700062CA31 /* CrashReportPromptPresenter.swift in Sources */, + 1DDC84F92B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, B6B4D1CD2B0C8C9200C26286 /* FirefoxCompatibilityPreferences.swift in Sources */, + 9FA173ED2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 4B9579572AC7AE700062CA31 /* BWCredential.swift in Sources */, 4B9579582AC7AE700062CA31 /* PreferencesRootView.swift in Sources */, 4B9579592AC7AE700062CA31 /* AppStateChangedPublisher.swift in Sources */, @@ -10924,6 +11321,7 @@ 4B95795D2AC7AE700062CA31 /* OptionalExtension.swift in Sources */, 4B95795E2AC7AE700062CA31 /* PasswordManagementLoginItemView.swift in Sources */, 4B95795F2AC7AE700062CA31 /* UserText.swift in Sources */, + 9F872D9A2B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */, 4B9579602AC7AE700062CA31 /* WKWebView+Download.swift in Sources */, 4B9579612AC7AE700062CA31 /* TabShadowConfig.swift in Sources */, 4B9579622AC7AE700062CA31 /* URLSessionExtension.swift in Sources */, @@ -10944,6 +11342,8 @@ 4B95796E2AC7AE700062CA31 /* LegacyBookmarkStore.swift in Sources */, 4B95796F2AC7AE700062CA31 /* NSAlert+DataImport.swift in Sources */, 4B9579702AC7AE700062CA31 /* MainWindow.swift in Sources */, + 9F872DA52B90920F00138637 /* BookmarkFolderInfo.swift in Sources */, + 9FEE986B2B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */, 4B9579712AC7AE700062CA31 /* CrashReportPromptViewController.swift in Sources */, 4B9579722AC7AE700062CA31 /* BookmarksCleanupErrorHandling.swift in Sources */, 4B9579732AC7AE700062CA31 /* ContextMenuManager.swift in Sources */, @@ -10956,8 +11356,10 @@ 4B9579792AC7AE700062CA31 /* BWRequest.swift in Sources */, 4B95797A2AC7AE700062CA31 /* WKWebViewConfigurationExtensions.swift in Sources */, 4B95797B2AC7AE700062CA31 /* HomePageDefaultBrowserModel.swift in Sources */, + 9F514F932B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, 4B95797C2AC7AE700062CA31 /* CrashReporter.swift in Sources */, 4B95797D2AC7AE700062CA31 /* AddressBarTextSelectionNavigation.swift in Sources */, + 1D01A3DA2B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */, 4B37EE7D2B4CFF8300A89A61 /* SurveyURLBuilder.swift in Sources */, 4B95797E2AC7AE700062CA31 /* BadgeNotificationAnimationModel.swift in Sources */, 4B95797F2AC7AE700062CA31 /* HyperLink.swift in Sources */, @@ -10994,6 +11396,7 @@ 4B95799E2AC7AE700062CA31 /* EncryptionKeyGeneration.swift in Sources */, 4B95799F2AC7AE700062CA31 /* TabLazyLoader.swift in Sources */, B690152F2ACBF4DA00AD0BAB /* MenuPreview.swift in Sources */, + 1D01A3D22B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 4B9579A02AC7AE700062CA31 /* InvitedToWaitlistView.swift in Sources */, 4B9579A22AC7AE700062CA31 /* SaveCredentialsViewController.swift in Sources */, 4B9579A32AC7AE700062CA31 /* PopUpButton.swift in Sources */, @@ -11016,7 +11419,6 @@ 4B9579B52AC7AE700062CA31 /* FirefoxLoginReader.swift in Sources */, 4B9579B62AC7AE700062CA31 /* AtbParser.swift in Sources */, 4B9579B72AC7AE700062CA31 /* PreferencesDuckPlayerView.swift in Sources */, - 4B9579B82AC7AE700062CA31 /* AddBookmarkFolderModalView.swift in Sources */, 4B41EDB62B169883001EEDF4 /* VPNFeedbackFormViewController.swift in Sources */, 4B9579B92AC7AE700062CA31 /* BookmarkSidebarTreeController.swift in Sources */, 4B9579BA2AC7AE700062CA31 /* HomePageFavoritesModel.swift in Sources */, @@ -11025,6 +11427,7 @@ 4B9579BD2AC7AE700062CA31 /* ChromiumDataImporter.swift in Sources */, 4B9579BE2AC7AE700062CA31 /* BackForwardListItemViewModel.swift in Sources */, 4B9579BF2AC7AE700062CA31 /* BWNotRespondingAlert.swift in Sources */, + 1DDC85052B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */, 4B9579C02AC7AE700062CA31 /* DebugUserScript.swift in Sources */, 1DC669722B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, 4B9579C12AC7AE700062CA31 /* RecentlyClosedTab.swift in Sources */, @@ -11043,6 +11446,7 @@ 4B9579CE2AC7AE700062CA31 /* CredentialsCleanupErrorHandling.swift in Sources */, 4B9579CF2AC7AE700062CA31 /* SafariBookmarksReader.swift in Sources */, 4B9579D02AC7AE700062CA31 /* HTTPCookie.swift in Sources */, + 1DDD3EC62B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, 4B9579D12AC7AE700062CA31 /* SafariVersionReader.swift in Sources */, 4B9579D22AC7AE700062CA31 /* LoginFaviconView.swift in Sources */, 4B9579D32AC7AE700062CA31 /* FireproofDomainsViewController.swift in Sources */, @@ -11064,12 +11468,14 @@ 4B9579E02AC7AE700062CA31 /* BookmarkExtension.swift in Sources */, 4B9579E12AC7AE700062CA31 /* PasswordManagementCreditCardModel.swift in Sources */, B677FC522B06376B0099EB04 /* ReportFeedbackView.swift in Sources */, + 1D220BFE2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, 4B9579E22AC7AE700062CA31 /* NSEventExtension.swift in Sources */, 1D26EBB22B74DB600002A93F /* TabSnapshotCleanupService.swift in Sources */, 4B9579E32AC7AE700062CA31 /* Onboarding.swift in Sources */, 4B9579E42AC7AE700062CA31 /* PopUpWindow.swift in Sources */, 4B9579E52AC7AE700062CA31 /* Favicons.xcdatamodeld in Sources */, 4B9579E62AC7AE700062CA31 /* Publisher.asVoid.swift in Sources */, + 9FEE986F2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */, 4B9579E72AC7AE700062CA31 /* Waitlist.swift in Sources */, 3158B1582B0BF76000AF130C /* DataBrokerProtectionFeatureVisibility.swift in Sources */, 4B9579E82AC7AE700062CA31 /* NavigationButtonMenuDelegate.swift in Sources */, @@ -11104,7 +11510,7 @@ 4B957A032AC7AE700062CA31 /* LocalBookmarkStore.swift in Sources */, 4B957A042AC7AE700062CA31 /* BWEncryption.m in Sources */, 4B957A052AC7AE700062CA31 /* StatisticsLoader.swift in Sources */, - 4B957A072AC7AE700062CA31 /* PrivacyPreferencesModel.swift in Sources */, + 4B957A072AC7AE700062CA31 /* DataClearingPreferences.swift in Sources */, 4B957A082AC7AE700062CA31 /* LocalUnprotectedDomains.swift in Sources */, 4B957A092AC7AE700062CA31 /* InternalUserDeciderStore.swift in Sources */, 4B957A0A2AC7AE700062CA31 /* NewWindowPolicy.swift in Sources */, @@ -11114,6 +11520,7 @@ 4B957A0E2AC7AE700062CA31 /* UserDialogRequest.swift in Sources */, 4B957A0F2AC7AE700062CA31 /* DownloadsCellView.swift in Sources */, 4B957A112AC7AE700062CA31 /* PublishedAfter.swift in Sources */, + 1DDC85012B835BC000670238 /* SearchPreferences.swift in Sources */, B6B5F58C2B03673B008DB58A /* BrowserImportMoreInfoView.swift in Sources */, 4B957A122AC7AE700062CA31 /* FirefoxBerkeleyDatabaseReader.swift in Sources */, 4B957A132AC7AE700062CA31 /* WebViewSnapshotView.swift in Sources */, @@ -11133,6 +11540,7 @@ 4B957A202AC7AE700062CA31 /* CancellableExtension.swift in Sources */, 4B957A212AC7AE700062CA31 /* PinnedTabsHostingView.swift in Sources */, 4B957A222AC7AE700062CA31 /* FirefoxBookmarksReader.swift in Sources */, + 9F982F0F2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */, 4B0526622B1D55320054955A /* VPNFeedbackSender.swift in Sources */, 4B957A232AC7AE700062CA31 /* DeviceIdleStateDetector.swift in Sources */, 85D0327D2B8E3D090041D1FB /* HistoryCoordinatorExtension.swift in Sources */, @@ -11156,6 +11564,7 @@ 4B957A332AC7AE700062CA31 /* Favicon.swift in Sources */, 1E2AE4CA2ACB21A000684E0A /* NetworkProtectionRemoteMessage.swift in Sources */, 4B957A342AC7AE700062CA31 /* SuggestionContainerViewModel.swift in Sources */, + 9F56CFAF2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */, 4B957A352AC7AE700062CA31 /* FirePopoverWrapperViewController.swift in Sources */, 4B957A362AC7AE700062CA31 /* NSPasteboardItemExtension.swift in Sources */, 4B957A372AC7AE700062CA31 /* AutofillPreferencesModel.swift in Sources */, @@ -11168,6 +11577,7 @@ 4B957A3E2AC7AE700062CA31 /* EnableWaitlistFeatureView.swift in Sources */, 4B957A3F2AC7AE700062CA31 /* GrammarFeaturesManager.swift in Sources */, 4B957A402AC7AE700062CA31 /* WaitlistModalViewController.swift in Sources */, + B60293E82BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, B6BCC53E2AFD15DF002C5499 /* DataImportProfilePicker.swift in Sources */, 4B957A412AC7AE700062CA31 /* WKMenuItemIdentifier.swift in Sources */, 4B957A422AC7AE700062CA31 /* SafariFaviconsReader.swift in Sources */, @@ -11192,6 +11602,7 @@ 4B957A542AC7AE700062CA31 /* VisitMenuItem.swift in Sources */, 4B957A552AC7AE700062CA31 /* EncryptionKeyStore.swift in Sources */, 4B957A562AC7AE700062CA31 /* TabExtensionsBuilder.swift in Sources */, + 9F56CFB32B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */, 1E2AE4C82ACB216B00684E0A /* HoverTrackingArea.swift in Sources */, 4B957A582AC7AE700062CA31 /* PasswordManagementIdentityItemView.swift in Sources */, 4B957A592AC7AE700062CA31 /* ProgressExtension.swift in Sources */, @@ -11213,10 +11624,10 @@ 4B957A662AC7AE700062CA31 /* SuggestionListCharacteristics.swift in Sources */, 4B957A672AC7AE700062CA31 /* TimeIntervalExtension.swift in Sources */, 4B957A682AC7AE700062CA31 /* NetworkProtectionFeatureDisabler.swift in Sources */, + 7BBA7CEA2BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, 4B957A692AC7AE700062CA31 /* BookmarkListViewController.swift in Sources */, 4B957A6A2AC7AE700062CA31 /* SecureVaultLoginImporter.swift in Sources */, 4B957A6B2AC7AE700062CA31 /* WKProcessPoolExtension.swift in Sources */, - 4B957A6C2AC7AE700062CA31 /* AddBookmarkModalView.swift in Sources */, 4B957A6D2AC7AE700062CA31 /* LoginItemsManager.swift in Sources */, 4B957A6E2AC7AE700062CA31 /* PixelExperiment.swift in Sources */, 4B957A6F2AC7AE700062CA31 /* DuckPlayerTabExtension.swift in Sources */, @@ -11225,6 +11636,7 @@ B6080BC82B21E78100B418EF /* DataImportErrorView.swift in Sources */, 4B957A722AC7AE700062CA31 /* FaviconHostReference.swift in Sources */, 4B957A732AC7AE700062CA31 /* DownloadsTabExtension.swift in Sources */, + 1D220BFA2B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */, 4B957A752AC7AE700062CA31 /* ASN1Parser.swift in Sources */, 4B957A762AC7AE700062CA31 /* FileDownloadManager.swift in Sources */, 4B957A772AC7AE700062CA31 /* BookmarkImport.swift in Sources */, @@ -11246,7 +11658,6 @@ 4B41EDA52B1543B9001EEDF4 /* VPNPreferencesModel.swift in Sources */, 4B957A822AC7AE700062CA31 /* SyncDebugMenu.swift in Sources */, 4B957A832AC7AE700062CA31 /* AddBookmarkPopover.swift in Sources */, - 4B957A842AC7AE700062CA31 /* PreferencesDownloadsView.swift in Sources */, 4B957A852AC7AE700062CA31 /* QRSharingService.swift in Sources */, 4B957A862AC7AE700062CA31 /* ProcessExtension.swift in Sources */, B68412162B694BA10092F66A /* NSObject+performSelector.m in Sources */, @@ -11264,18 +11675,19 @@ 4B957A902AC7AE700062CA31 /* BookmarkManagementSplitViewController.swift in Sources */, 4B957A912AC7AE700062CA31 /* CookieManagedNotificationContainerView.swift in Sources */, 4B957A922AC7AE700062CA31 /* FileManagerExtension.swift in Sources */, + 1DDD3EBE2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */, 4B957A932AC7AE700062CA31 /* PermissionModel.swift in Sources */, 4B957A942AC7AE700062CA31 /* PasteboardFolder.swift in Sources */, 4B957A952AC7AE700062CA31 /* CookieManagedNotificationView.swift in Sources */, 4B957A962AC7AE700062CA31 /* PermissionType.swift in Sources */, 4B957A982AC7AE700062CA31 /* RecentlyClosedWindow.swift in Sources */, 4B957A992AC7AE700062CA31 /* ActionSpeech.swift in Sources */, - 4B957A9A2AC7AE700062CA31 /* PrivacySecurityPreferences.swift in Sources */, 4B957A9B2AC7AE700062CA31 /* ModalSheetCancellable.swift in Sources */, 4B957A9C2AC7AE700062CA31 /* FireproofDomainsStore.swift in Sources */, 4B957A9D2AC7AE700062CA31 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 4B957A9E2AC7AE700062CA31 /* PrivacyDashboardPermissionHandler.swift in Sources */, 4B957A9F2AC7AE700062CA31 /* TabCollectionViewModel.swift in Sources */, + 4B520F652BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */, 4B957AA02AC7AE700062CA31 /* BookmarkManager.swift in Sources */, 4B957AA12AC7AE700062CA31 /* AboutModel.swift in Sources */, 4B957AA22AC7AE700062CA31 /* PasswordManagementCreditCardItemView.swift in Sources */, @@ -11295,6 +11707,7 @@ 4B957AAD2AC7AE700062CA31 /* Tab+Dialogs.swift in Sources */, 4B957AAE2AC7AE700062CA31 /* PasteboardBookmark.swift in Sources */, 4B957AAF2AC7AE700062CA31 /* PinnedTabsManager.swift in Sources */, + 1D01A3D62B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */, 4B957AB02AC7AE700062CA31 /* HoverUserScript.swift in Sources */, 4B957AB12AC7AE700062CA31 /* MainMenuActions.swift in Sources */, 4B957AB22AC7AE700062CA31 /* WKWebView+SessionState.swift in Sources */, @@ -11305,10 +11718,10 @@ 4B957AB62AC7AE700062CA31 /* FireproofDomains.xcdatamodeld in Sources */, 3158B14F2B0BF74F00AF130C /* DataBrokerProtectionManager.swift in Sources */, 4B957AB82AC7AE700062CA31 /* HomePageView.swift in Sources */, + 9FEE98672B846870002E44E8 /* AddEditBookmarkView.swift in Sources */, 4B957AB92AC7AE700062CA31 /* SerpHeadersNavigationResponder.swift in Sources */, 4B957ABA2AC7AE700062CA31 /* HomePageContinueSetUpModel.swift in Sources */, 4B957ABB2AC7AE700062CA31 /* WebKitDownloadTask.swift in Sources */, - EE0629762B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */, 4B957ABC2AC7AE700062CA31 /* ChromiumLoginReader.swift in Sources */, B6BCC5522AFE4F7D002C5499 /* DataImportTypePicker.swift in Sources */, 4B957ABD2AC7AE700062CA31 /* NSAlert+PasswordManager.swift in Sources */, @@ -11321,12 +11734,12 @@ 4B957AC42AC7AE700062CA31 /* BWVault.swift in Sources */, 4B957AC52AC7AE700062CA31 /* NSViewExtension.swift in Sources */, BBDFDC5C2B2B8D7000F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, + 9FA173E52B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */, 4B957AC72AC7AE700062CA31 /* DownloadListViewModel.swift in Sources */, 4B957AC82AC7AE700062CA31 /* BookmarkManagementDetailViewController.swift in Sources */, 4B957AC92AC7AE700062CA31 /* CSVImporter.swift in Sources */, 4B957ACA2AC7AE700062CA31 /* StartupPreferences.swift in Sources */, 4B957ACB2AC7AE700062CA31 /* UserDefaults+NetworkProtectionWaitlist.swift in Sources */, - B6F9BDE22B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */, 4B957ACC2AC7AE700062CA31 /* MainMenu.swift in Sources */, 4B957ACE2AC7AE700062CA31 /* BrowserTabViewController.swift in Sources */, 4B957ACF2AC7AE700062CA31 /* CallToAction.swift in Sources */, @@ -11341,6 +11754,7 @@ 4B957AD72AC7AE700062CA31 /* CustomRoundedCornersShape.swift in Sources */, 4B957AD82AC7AE700062CA31 /* LocaleExtension.swift in Sources */, 4B957AD92AC7AE700062CA31 /* SavePaymentMethodViewController.swift in Sources */, + 9FA173E92B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */, 4B957ADA2AC7AE700062CA31 /* BWStatus.swift in Sources */, 4B957ADB2AC7AE700062CA31 /* WebKitVersionProvider.swift in Sources */, B6BCC54D2AFDF24B002C5499 /* TaskWithProgress.swift in Sources */, @@ -11368,8 +11782,8 @@ 4B957AF02AC7AE700062CA31 /* NSException+Catch.m in Sources */, 4B957AF12AC7AE700062CA31 /* AppStateRestorationManager.swift in Sources */, 4B957AF22AC7AE700062CA31 /* DailyPixel.swift in Sources */, + 9FDA6C232B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, 4B957AF32AC7AE700062CA31 /* NavigationHotkeyHandler.swift in Sources */, - 1EA7B8DA2B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */, 4B957AF42AC7AE700062CA31 /* ClickToLoadUserScript.swift in Sources */, 4B957AF52AC7AE700062CA31 /* WindowControllersManager.swift in Sources */, 4B957AF62AC7AE700062CA31 /* FireAnimationView.swift in Sources */, @@ -11408,10 +11822,11 @@ B66CA4212AD910B300447CF0 /* DataImportView.swift in Sources */, 4B957B162AC7AE700062CA31 /* Downloads.xcdatamodeld in Sources */, 4B957B172AC7AE700062CA31 /* TabPreviewViewController.swift in Sources */, - B6F9BDDA2B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */, - 4B957B182AC7AE700062CA31 /* PreferencesPrivacyView.swift in Sources */, + 4B957B182AC7AE700062CA31 /* PreferencesDataClearingView.swift in Sources */, + 4B957B182AC7AE700062CA31 /* PreferencesDataClearingView.swift in Sources */, 4B957B192AC7AE700062CA31 /* NSPasteboardExtension.swift in Sources */, 4B957B1A2AC7AE700062CA31 /* OnboardingViewModel.swift in Sources */, + F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */, 4B957B1B2AC7AE700062CA31 /* ScriptSourceProviding.swift in Sources */, 4B957B1C2AC7AE700062CA31 /* CoreDataBookmarkImporter.swift in Sources */, 4B957B1D2AC7AE700062CA31 /* SuggestionViewModel.swift in Sources */, @@ -11461,6 +11876,7 @@ 4B957B462AC7AE700062CA31 /* Tab+NSSecureCoding.swift in Sources */, 4B957B472AC7AE700062CA31 /* NSNotificationName+EmailManager.swift in Sources */, B6619EFE2B111CCC00CD9186 /* InstructionsFormatParser.swift in Sources */, + 1DDD3EC22B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, 4B957B482AC7AE700062CA31 /* MouseOverButton.swift in Sources */, 4B957B492AC7AE700062CA31 /* FireInfoViewController.swift in Sources */, 4B957B4A2AC7AE700062CA31 /* LoginItem+NetworkProtection.swift in Sources */, @@ -11494,6 +11910,7 @@ 4B957B612AC7AE700062CA31 /* HomePage.swift in Sources */, 4B957B622AC7AE700062CA31 /* RoundedSelectionRowView.swift in Sources */, B6A22B652B1E29D000ECD2BA /* DataImportSummaryViewModel.swift in Sources */, + 9FA173E12B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */, 4B957B632AC7AE700062CA31 /* LocalStatisticsStore.swift in Sources */, 4B957B642AC7AE700062CA31 /* BackForwardListItem.swift in Sources */, 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */, @@ -11510,6 +11927,7 @@ 4B957B712AC7AE700062CA31 /* TabPreviewWindowController.swift in Sources */, 4B957B722AC7AE700062CA31 /* NSSizeExtension.swift in Sources */, 4B957B732AC7AE700062CA31 /* Fire.swift in Sources */, + 1DDC84FD2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */, 4B957B742AC7AE700062CA31 /* SyncBookmarksAdapter.swift in Sources */, B6ABC5982B4861D4008343B9 /* FocusableTextField.swift in Sources */, 4B957B752AC7AE700062CA31 /* RandomAccessCollectionExtension.swift in Sources */, @@ -11522,12 +11940,15 @@ 4B957B7B2AC7AE700062CA31 /* DeviceAuthenticator.swift in Sources */, 4B957B7C2AC7AE700062CA31 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 4B957B7D2AC7AE700062CA31 /* TabBarCollectionView.swift in Sources */, + C1DAF3B72B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */, 4B957B7E2AC7AE700062CA31 /* NetworkProtection+ConvenienceInitializers.swift in Sources */, 7BA7CC502AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 4B957B7F2AC7AE700062CA31 /* NavigationActionExtension.swift in Sources */, + F1B33DF82BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, 4B957B802AC7AE700062CA31 /* NSAlertExtension.swift in Sources */, 4B957B812AC7AE700062CA31 /* ThirdPartyBrowser.swift in Sources */, 4B957B822AC7AE700062CA31 /* SearchNonexistentDomainNavigationResponder.swift in Sources */, + 4B6B64862BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */, B6B71C5A2B23379600487131 /* NSLayoutConstraintExtension.swift in Sources */, B65211272B29A43000B30633 /* BookmarkStoreMock.swift in Sources */, 4B957B832AC7AE700062CA31 /* CircularProgressView.swift in Sources */, @@ -11568,6 +11989,8 @@ 4B957BA12AC7AE700062CA31 /* UserDefaults+NetworkProtectionShared.swift in Sources */, 4B957BA22AC7AE700062CA31 /* NavigationActionPolicyExtension.swift in Sources */, 4B957BA32AC7AE700062CA31 /* CIImageExtension.swift in Sources */, + 9F56CFAB2B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, + 9FA173DC2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */, 4B957BA42AC7AE700062CA31 /* NSMenuExtension.swift in Sources */, 4B957BA52AC7AE700062CA31 /* MainWindowController.swift in Sources */, 4B957BA62AC7AE700062CA31 /* Tab.swift in Sources */, @@ -11613,6 +12036,7 @@ 4B957BCB2AC7AE700062CA31 /* PreferencesAppearanceView.swift in Sources */, 4B957BCC2AC7AE700062CA31 /* NSMenuItemExtension.swift in Sources */, 4B957BCD2AC7AE700062CA31 /* ContiguousBytesExtension.swift in Sources */, + 7B7FCD112BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */, 4B957BCE2AC7AE700062CA31 /* AdjacentItemEnumerator.swift in Sources */, 4B957BCF2AC7AE700062CA31 /* BookmarkDatabase.swift in Sources */, 4B957BD02AC7AE700062CA31 /* ChromiumKeychainPrompt.swift in Sources */, @@ -11633,7 +12057,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EEBCE6842BA4643200B9DF00 /* NSSizeExtension.swift in Sources */, + EE02D41C2BB460A600DBE6B3 /* BrowsingHistoryTests.swift in Sources */, + EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */, + EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */, + EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */, + EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */, 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */, + EEBCE6832BA463DD00B9DF00 /* NSImageExtensions.swift in Sources */, + EEBCE6822BA444FA00B9DF00 /* XCUIElementExtension.swift in Sources */, + EED735362BB46B6000F173D6 /* AutocompleteTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -11728,7 +12161,7 @@ 1D1A33492A6FEB170080ACED /* BurnerMode.swift in Sources */, 14505A08256084EF00272CC6 /* UserAgent.swift in Sources */, 987799F12999993C005D8EB6 /* LegacyBookmarkStore.swift in Sources */, - B6F9BDE02B45C1A800677B33 /* AddBookmarkFolderModalViewModel.swift in Sources */, + 1D220BF82B86192200F8BBC6 /* PreferencesEmailProtectionView.swift in Sources */, 4B8AC93526B3B2FD00879451 /* NSAlert+DataImport.swift in Sources */, AA7412BD24D2BEEE00D22FE0 /* MainWindow.swift in Sources */, AAD6D8882696DF6D002393B3 /* CrashReportPromptViewController.swift in Sources */, @@ -11746,6 +12179,7 @@ B65C7DFB2B886CF0001E2D5C /* WKPDFHUDViewWrapper.swift in Sources */, B68458CD25C7EB9000DC17B6 /* WKWebViewConfigurationExtensions.swift in Sources */, 85AC7ADD27BEB6EE00FFB69B /* HomePageDefaultBrowserModel.swift in Sources */, + 1D01A3D82B88DF8B00FE8150 /* PreferencesSyncView.swift in Sources */, B6619EFB2B111CC500CD9186 /* InstructionsFormatParser.swift in Sources */, 1DC669702B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, AAC30A26268DFEE200D2D9CD /* CrashReporter.swift in Sources */, @@ -11807,7 +12241,6 @@ 4B8AC93926B48A5100879451 /* FirefoxLoginReader.swift in Sources */, B69B503E2726A12500758A2B /* AtbParser.swift in Sources */, 37F19A6528E1B3FB00740DC6 /* PreferencesDuckPlayerView.swift in Sources */, - 4B9292D22667123700AD2C21 /* AddBookmarkFolderModalView.swift in Sources */, 4B92929E26670D2A00AD2C21 /* BookmarkSidebarTreeController.swift in Sources */, EEC4A6712B2C90AB00F7C0AA /* VPNLocationPreferenceItem.swift in Sources */, 85589E8727BBB8F20038AD11 /* HomePageFavoritesModel.swift in Sources */, @@ -11816,6 +12249,7 @@ 4B59024026B35F3600489384 /* ChromiumDataImporter.swift in Sources */, B62B48562ADE730D000DECE5 /* FileImportView.swift in Sources */, AAA0CC3C25337FAB0079BC96 /* BackForwardListItemViewModel.swift in Sources */, + 9F982F0D2B8224BF00231028 /* AddEditBookmarkFolderDialogViewModel.swift in Sources */, 1D43EB3429297D760065E5D6 /* BWNotRespondingAlert.swift in Sources */, 4BB88B4525B7B55C006F6B06 /* DebugUserScript.swift in Sources */, AAC6881928626BF800D54247 /* RecentlyClosedTab.swift in Sources */, @@ -11842,6 +12276,7 @@ AA92127725ADA07900600CD4 /* WKWebViewExtension.swift in Sources */, AAAB9114288EB1D600A057A9 /* CleanThisHistoryMenuItem.swift in Sources */, B6C0B23626E732000031CB7F /* DownloadListItem.swift in Sources */, + 9F872DA32B90920F00138637 /* BookmarkFolderInfo.swift in Sources */, 4B9DB0232A983B24000927DB /* WaitlistRequest.swift in Sources */, B6B1E87E26D5DA0E0062C350 /* DownloadsPopover.swift in Sources */, 85774AFF2A713D3B00DE0561 /* BookmarksBarMenuFactory.swift in Sources */, @@ -11854,6 +12289,7 @@ B68C92C1274E3EF4002AC6B0 /* PopUpWindow.swift in Sources */, AA5FA6A0275F948900DCE9C9 /* Favicons.xcdatamodeld in Sources */, 3158B1502B0BF75200AF130C /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, + 7BBA7CE62BAB03C1007579A3 /* DefaultSubscriptionFeatureAvailability+DefaultInitializer.swift in Sources */, B684592225C93BE000DC17B6 /* Publisher.asVoid.swift in Sources */, 4B9DB01D2A983B24000927DB /* Waitlist.swift in Sources */, BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */, @@ -11868,6 +12304,7 @@ 98779A0029999B64005D8EB6 /* Bookmark.xcdatamodeld in Sources */, 85589E9E27BFE4500038AD11 /* DefaultBrowserPromptView.swift in Sources */, 4B4032842AAAC24400CCA602 /* WaitlistActivationDateStore.swift in Sources */, + 1D220BFC2B87AACF00F8BBC6 /* PrivacyProtectionStatus.swift in Sources */, AA512D1424D99D9800230283 /* FaviconManager.swift in Sources */, 7BB108592A43375D000AB95F /* PFMoveApplication.m in Sources */, 4B0AACAC28BC63ED001038AC /* ChromiumFaviconsReader.swift in Sources */, @@ -11888,7 +12325,7 @@ 1D02633628D8A9A9005CBB41 /* BWEncryption.m in Sources */, B6B5F5892B03673B008DB58A /* BrowserImportMoreInfoView.swift in Sources */, B69B503A2726A12500758A2B /* StatisticsLoader.swift in Sources */, - 37CD54C927F2FDD100F1F7B9 /* PrivacyPreferencesModel.swift in Sources */, + 37CD54C927F2FDD100F1F7B9 /* DataClearingPreferences.swift in Sources */, B6F1C80B2761C45400334924 /* LocalUnprotectedDomains.swift in Sources */, B69A14FA2B4D705D00B9417D /* BookmarkFolderPicker.swift in Sources */, 1D36E658298AA3BA00AA485D /* InternalUserDeciderStore.swift in Sources */, @@ -11913,6 +12350,7 @@ 85C6A29625CC1FFD00EEB5F1 /* UserDefaultsWrapper.swift in Sources */, 85625998269C9C5F00EE44BC /* PasswordManagementPopover.swift in Sources */, 1DDF076328F815AD00EDFBE3 /* BWCommunicator.swift in Sources */, + 9FEE98652B846870002E44E8 /* AddEditBookmarkView.swift in Sources */, 85589E9127BFB9810038AD11 /* HomePageRecentlyVisitedModel.swift in Sources */, 85012B0229133F9F003D0DCC /* NavigationBarPopovers.swift in Sources */, B626A7602992407D00053070 /* CancellableExtension.swift in Sources */, @@ -11954,6 +12392,7 @@ 4B9DB0442A983B24000927DB /* WaitlistModalViewController.swift in Sources */, B6DA06E8291401D700225DE2 /* WKMenuItemIdentifier.swift in Sources */, 4B0AACAE28BC6FD0001038AC /* SafariFaviconsReader.swift in Sources */, + B60293E62BA19ECD0033186B /* NetPPopoverManagerMock.swift in Sources */, B6B3E0E12657EA7A0040E0A2 /* NSScreenExtension.swift in Sources */, B65E6BA026D9F10600095F96 /* NSBezierPathExtension.swift in Sources */, 4B4D60E02A0C875F00BCD287 /* Bundle+VPN.swift in Sources */, @@ -11997,9 +12436,9 @@ 4B6785472AA8DE68008A5004 /* NetworkProtectionFeatureDisabler.swift in Sources */, 4B0526642B1D55D80054955A /* VPNFeedbackCategory.swift in Sources */, 4B9292D42667123700AD2C21 /* BookmarkListViewController.swift in Sources */, + 9F56CFB12B843F6C00BB7F11 /* BookmarksDialogViewFactory.swift in Sources */, 4B723E0D26B0006100E14D75 /* SecureVaultLoginImporter.swift in Sources */, B645D8F629FA95440024461F /* WKProcessPoolExtension.swift in Sources */, - 4B9292D32667123700AD2C21 /* AddBookmarkModalView.swift in Sources */, 9D9AE86B2AA76CF90026E7DC /* LoginItemsManager.swift in Sources */, 857E5AF52A79045800FC0FB4 /* PixelExperiment.swift in Sources */, B6C416A7294A4AE500C4F2E7 /* DuckPlayerTabExtension.swift in Sources */, @@ -12026,14 +12465,15 @@ AA9E9A5625A3AE8400D1959D /* NSWindowExtension.swift in Sources */, 7BD3AF5D2A8E7AF1006F9F56 /* KeychainType+ClientDefault.swift in Sources */, 370A34B12AB24E3700C77F7C /* SyncDebugMenu.swift in Sources */, + 9FDA6C212B79A59D00E099A9 /* BookmarkFavoriteView.swift in Sources */, AAC5E4C725D6A6E8007F5990 /* AddBookmarkPopover.swift in Sources */, - 37CC53F027E8D1440028713D /* PreferencesDownloadsView.swift in Sources */, B6F7127E29F6779000594A45 /* QRSharingService.swift in Sources */, B68C2FB227706E6A00BF2C7D /* ProcessExtension.swift in Sources */, B6106BA726A7BECC0013B453 /* PermissionAuthorizationQuery.swift in Sources */, 3171D6BA288984D00068632A /* BadgeAnimationView.swift in Sources */, 1DB67F292B6FE4A6003DF243 /* WebViewSnapshotRenderer.swift in Sources */, 4B9292CE2667123700AD2C21 /* BrowserTabSelectionDelegate.swift in Sources */, + 9FEE986D2B85BA17002E44E8 /* AddEditBookmarkDialogCoordinatorViewModel.swift in Sources */, B6BCC53B2AFD15DF002C5499 /* DataImportProfilePicker.swift in Sources */, 3158B1562B0BF75D00AF130C /* DataBrokerProtectionFeatureVisibility.swift in Sources */, 56D6A3D629DB2BAB0055215A /* ContinueSetUpView.swift in Sources */, @@ -12056,12 +12496,12 @@ B6106BAB26A7BF1D0013B453 /* PermissionType.swift in Sources */, AAC6881B28626C1900D54247 /* RecentlyClosedWindow.swift in Sources */, 85707F2A276A35FE00DC0649 /* ActionSpeech.swift in Sources */, - 4B0511BD262CAA5A00F6079C /* PrivacySecurityPreferences.swift in Sources */, B6BE9FAA293F7955006363C6 /* ModalSheetCancellable.swift in Sources */, B6830963274CDEC7004B46BB /* FireproofDomainsStore.swift in Sources */, 7B430EA12A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 1E7E2E942902AC0E00C01B54 /* PrivacyDashboardPermissionHandler.swift in Sources */, AA9FF95F24A1FB690039E328 /* TabCollectionViewModel.swift in Sources */, + 4B520F632BA5573A006405C7 /* WaitlistThankYouView.swift in Sources */, 4BF0E5052AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, AAC5E4D125D6A709007F5990 /* BookmarkManager.swift in Sources */, 37CD54CD27F2FDD100F1F7B9 /* AboutModel.swift in Sources */, @@ -12086,12 +12526,10 @@ AA6EF9B525081B4C004754E6 /* MainMenuActions.swift in Sources */, B63D466925BEB6C200874977 /* WKWebView+SessionState.swift in Sources */, 4B4D60C02A0C848D00BCD287 /* NetworkProtectionControllerErrorStore.swift in Sources */, - B6F9BDD82B45B7D900677B33 /* AddBookmarkModalViewModel.swift in Sources */, 4B723E1226B0006E00E14D75 /* DataImport.swift in Sources */, 7BE146072A6A83C700C313B8 /* NetworkProtectionDebugMenu.swift in Sources */, B6085D092743AAB600A9C456 /* FireproofDomains.xcdatamodeld in Sources */, 85589E8227BBB8630038AD11 /* HomePageView.swift in Sources */, - EE0629722B90EE8C00D868B4 /* AccountManagerExtension.swift in Sources */, B6BF5D932947199A006742B1 /* SerpHeadersNavigationResponder.swift in Sources */, 569277C129DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, B68D21CF2ACBC9FC002DA3C2 /* ContentBlockerRulesManagerMock.swift in Sources */, @@ -12099,6 +12537,7 @@ 1DB67F2D2B6FEFDB003DF243 /* ViewSnapshotRenderer.swift in Sources */, 4B44FEF32B1FEF5A000619D8 /* FocusableTextEditor.swift in Sources */, 4B59023E26B35F3600489384 /* ChromiumLoginReader.swift in Sources */, + 9F872D982B8DA9F800138637 /* Bookmarks+Tab.swift in Sources */, 85D885B326A5A9DE0077C374 /* NSAlert+PasswordManager.swift in Sources */, 983DFB2528B67036006B7E34 /* UserContentUpdating.swift in Sources */, 1D9A4E5A2B43213B00F449E2 /* TabSnapshotExtension.swift in Sources */, @@ -12132,6 +12571,7 @@ 4BE4005527CF3F19007D3161 /* SavePaymentMethodViewController.swift in Sources */, 1D2DC009290167A0008083A1 /* BWStatus.swift in Sources */, AAFE068326C7082D005434CC /* WebKitVersionProvider.swift in Sources */, + 9FEE98692B85B869002E44E8 /* BookmarksDialogViewModel.swift in Sources */, B63D467A25BFC3E100874977 /* NSCoderExtensions.swift in Sources */, 1D2DC00B290167EC008083A1 /* RunningApplicationCheck.swift in Sources */, B6A5A27125B9377300AA7ADA /* StatePersistenceService.swift in Sources */, @@ -12141,6 +12581,7 @@ 4BB88B5B25B7BA50006F6B06 /* Instruments.swift in Sources */, 9812D895276CEDA5004B6181 /* ContentBlockerRulesLists.swift in Sources */, 4B0511E2262CAA8600F6079C /* NSViewControllerExtension.swift in Sources */, + 9FA173DF2B7A0EFE00EE4E6E /* BookmarkDialogButtonsView.swift in Sources */, F44C130225C2DA0400426E3E /* NSAppearanceExtension.swift in Sources */, 4B3B8490297A0E1000A384BD /* EmailManagerExtension.swift in Sources */, B64C84F1269310120048FEBE /* PermissionManager.swift in Sources */, @@ -12149,6 +12590,7 @@ EE339228291BDEFD009F62C1 /* JSAlertController.swift in Sources */, 4B9DB04A2A983B24000927DB /* NotificationService.swift in Sources */, 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */, + F1B33DF22BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */, 1DB9618329F67F6200CF5568 /* FaviconNullStore.swift in Sources */, BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, B693954F26F04BEB0015B914 /* PaddedImageButton.swift in Sources */, @@ -12181,6 +12623,7 @@ 85A0118225AF60E700FA6A0C /* FindInPageModel.swift in Sources */, 7BA7CC4E2AD11F6F0042E5CE /* NetworkProtectionIPCTunnelController.swift in Sources */, 4B9292A226670D2A00AD2C21 /* PseudoFolder.swift in Sources */, + 1DDD3EC42B84F96B004CBF2B /* CookiePopupProtectionPreferences.swift in Sources */, 4BCF15D92ABB8A7F0083F6DF /* NetworkProtectionRemoteMessage.swift in Sources */, 4B05265E2B1AE5C70054955A /* VPNMetadataCollector.swift in Sources */, B6DA44022616B28300DD1EC2 /* PixelDataStore.swift in Sources */, @@ -12200,13 +12643,14 @@ AAE8B110258A456C00E81239 /* TabPreviewViewController.swift in Sources */, B6C8CAA72AD010DD0060E1CD /* YandexDataImporter.swift in Sources */, EE66666F2B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */, - 37CC53EC27E8A4D10028713D /* PreferencesPrivacyView.swift in Sources */, + 37CC53EC27E8A4D10028713D /* PreferencesDataClearingView.swift in Sources */, 4B0135CE2729F1AA00D54834 /* NSPasteboardExtension.swift in Sources */, 85707F31276A7DCA00DC0649 /* OnboardingViewModel.swift in Sources */, 85AC3B0525D6B1D800C7D2AA /* ScriptSourceProviding.swift in Sources */, 4BB99D0026FE191E001E4761 /* CoreDataBookmarkImporter.swift in Sources */, 4BCF15D72ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift in Sources */, C168B9AC2B31DC7E001AFAD9 /* AutofillNeverPromptWebsitesManager.swift in Sources */, + 9FA173E72B7B122E00EE4E6E /* BookmarkDialogStackedContentView.swift in Sources */, D64A5FF82AEA5C2B00B6D6E7 /* HomeButtonMenuFactory.swift in Sources */, 37A6A8F62AFCCA59008580A3 /* FaviconsFetcherOnboardingViewController.swift in Sources */, AA3F895324C18AD500628DDE /* SuggestionViewModel.swift in Sources */, @@ -12253,12 +12697,15 @@ 987799F32999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, 4B379C1527BD91E3008A968E /* QuartzIdleStateProvider.swift in Sources */, 37F19A6728E1B43200740DC6 /* DuckPlayerPreferences.swift in Sources */, + 1D01A3D42B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */, B6C0B22E26E61CE70031CB7F /* DownloadViewModel.swift in Sources */, 4B41EDA72B1543C9001EEDF4 /* PreferencesVPNView.swift in Sources */, + 9FA173DA2B79BD8A00EE4E6E /* BookmarkDialogContainerView.swift in Sources */, 373A1AA8283ED1B900586521 /* BookmarkHTMLReader.swift in Sources */, B68458B825C7E8B200DC17B6 /* Tab+NSSecureCoding.swift in Sources */, 4B37EE612B4CFC3C00A89A61 /* SurveyURLBuilder.swift in Sources */, 85378DA0274E6F42007C5CBF /* NSNotificationName+EmailManager.swift in Sources */, + 1DDC84FF2B835BC000670238 /* SearchPreferences.swift in Sources */, B693955726F04BEC0015B914 /* MouseOverButton.swift in Sources */, AA61C0D02722159B00E6B681 /* FireInfoViewController.swift in Sources */, 9D9AE8692AA76CDC0026E7DC /* LoginItem+NetworkProtection.swift in Sources */, @@ -12275,9 +12722,12 @@ B69B503D2726A12500758A2B /* VariantManager.swift in Sources */, AA97BF4625135DD30014931A /* ApplicationDockMenu.swift in Sources */, 4B8A4DFF27C83B29005F40E8 /* SaveIdentityViewController.swift in Sources */, + 1DDC84FB2B8356CE00670238 /* PreferencesDefaultBrowserView.swift in Sources */, + F1B33DF62BAD970E001128B3 /* SubscriptionErrorReporter.swift in Sources */, EEC589D92A4F1CE300BCD60C /* AppLauncher.swift in Sources */, 4BA1A69B258B076900F6F690 /* FileStore.swift in Sources */, B6A9E47F26146A800067D1B9 /* PixelArguments.swift in Sources */, + 1D01A3D02B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 37BF3F21286F0A7A00BD9014 /* PinnedTabsViewModel.swift in Sources */, EEC4A6692B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, AAC5E4D225D6A709007F5990 /* BookmarkList.swift in Sources */, @@ -12302,20 +12752,25 @@ AAC5E4E425D6BA9C007F5990 /* NSSizeExtension.swift in Sources */, AA6820EB25503D6A005ED0D5 /* Fire.swift in Sources */, 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */, + 9F56CFA92B82DC4300BB7F11 /* AddEditBookmarkFolderView.swift in Sources */, 37445F9C2A1569F00029F789 /* SyncBookmarksAdapter.swift in Sources */, C1E961EF2B87AA29001760E1 /* AutofillActionBuilder.swift in Sources */, B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */, 4B9292AF26670F5300AD2C21 /* NSOutlineViewExtensions.swift in Sources */, + 9F56CFAD2B84326C00BB7F11 /* AddEditBookmarkDialogViewModel.swift in Sources */, AA585D82248FD31100E9A3E2 /* AppDelegate.swift in Sources */, 7B1E81A027C8874900FF0E60 /* ContentOverlayViewController.swift in Sources */, + C1DAF3B52B9A44860059244F /* AutofillPopoverPresenter.swift in Sources */, B687B7CA2947A029001DEA6F /* ContentBlockingTabExtension.swift in Sources */, 85B7184C27677C6500B4277F /* OnboardingViewController.swift in Sources */, 4B379C1E27BDB7FF008A968E /* DeviceAuthenticator.swift in Sources */, 7BFE95522A9DF1CE0081ABE9 /* NetworkProtectionWaitlistFeatureFlagOverridesMenu.swift in Sources */, 1456D6E124EFCBC300775049 /* TabBarCollectionView.swift in Sources */, 4B4D60BF2A0C848A00BCD287 /* NetworkProtection+ConvenienceInitializers.swift in Sources */, + 4B6B64842BA930420009FF9F /* WaitlistThankYouPromptPresenter.swift in Sources */, 3158B1592B0BF76400AF130C /* DataBrokerProtectionFeatureDisabler.swift in Sources */, B655124829A79465009BFE1C /* NavigationActionExtension.swift in Sources */, + 9FA173EB2B7B232200EE4E6E /* AddEditBookmarkDialogView.swift in Sources */, 85308E25267FC9F2001ABD76 /* NSAlertExtension.swift in Sources */, B69A14F62B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 4B59024826B3673600489384 /* ThirdPartyBrowser.swift in Sources */, @@ -12336,6 +12791,7 @@ 85707F28276A34D900DC0649 /* DaxSpeech.swift in Sources */, 31F28C5328C8EECA00119F70 /* DuckURLSchemeHandler.swift in Sources */, AA13DCB4271480B0006D48D3 /* FirePopoverViewModel.swift in Sources */, + 1DDC84F72B83558F00670238 /* PreferencesPrivateSearchView.swift in Sources */, 1D43EB38292B636E0065E5D6 /* BWCommand.swift in Sources */, F41D174125CB131900472416 /* NSColorExtension.swift in Sources */, AAC5E4F625D6BF2C007F5990 /* AddressBarButtonsViewController.swift in Sources */, @@ -12352,6 +12808,7 @@ 4BB99D0226FE191E001E4761 /* ImportedBookmarks.swift in Sources */, 7B934C412A866DD400FC8F9C /* UserDefaults+NetworkProtectionShared.swift in Sources */, B626A75A29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */, + 1DDD3EBC2B84DCB9004CBF2B /* WebTrackingProtectionPreferences.swift in Sources */, B603FD9E2A02712E00F3FCA9 /* CIImageExtension.swift in Sources */, AA6EF9B3250785D5004754E6 /* NSMenuExtension.swift in Sources */, AA7412B524D1536B00D22FE0 /* MainWindowController.swift in Sources */, @@ -12362,6 +12819,7 @@ B64C84EB2692DD650048FEBE /* PermissionAuthorizationPopover.swift in Sources */, 85378D9E274E664C007C5CBF /* PopoverMessageViewController.swift in Sources */, AA6FFB4624DC3B5A0028F4D0 /* WebView.swift in Sources */, + 1DDD3EC02B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, B693955026F04BEB0015B914 /* ShadowView.swift in Sources */, AA3D531D27A2F58F00074EC1 /* FeedbackSender.swift in Sources */, B6BDDA012942389000F68088 /* TabExtensions.swift in Sources */, @@ -12382,6 +12840,7 @@ 37AFCE8127DA2CA600471A10 /* PreferencesViewController.swift in Sources */, 4B02198A25E05FAC00ED7DEA /* FireproofDomains.swift in Sources */, 4B677442255DBEEA00025BD8 /* Database.swift in Sources */, + 1DDC85032B83903E00670238 /* PreferencesWebTrackingProtectionView.swift in Sources */, 4BE5336E286915A10019DBFD /* HorizontallyCenteredLayout.swift in Sources */, B6BCC5232AFCDABB002C5499 /* DataImportSourceViewModel.swift in Sources */, 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */, @@ -12397,8 +12856,10 @@ AA72D5FE25FFF94E00C77619 /* NSMenuItemExtension.swift in Sources */, 4BA1A6C2258B0A1300F6F690 /* ContiguousBytesExtension.swift in Sources */, B6A22B622B1E29D000ECD2BA /* DataImportSummaryViewModel.swift in Sources */, + 9F514F912B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, + 9FA173E32B7A12B600EE4E6E /* BookmarkDialogFolderManagementView.swift in Sources */, 37534CA8281198CD002621E7 /* AdjacentItemEnumerator.swift in Sources */, - 1EA7B8D82B7E1283000330A4 /* SubscriptionFeatureAvailability.swift in Sources */, + 7B7FCD0F2BA33B2700C04FBE /* UserDefaults+vpnLegacyUser.swift in Sources */, 987799F62999996B005D8EB6 /* BookmarkDatabase.swift in Sources */, 4BE53374286E39F10019DBFD /* ChromiumKeychainPrompt.swift in Sources */, B6553692268440D700085A79 /* WKProcessPool+GeolocationProvider.swift in Sources */, @@ -12428,12 +12889,15 @@ 37534C9E28104D9B002621E7 /* TabLazyLoaderTests.swift in Sources */, B6619EF62B10DFF700CD9186 /* InstructionsFormatParserTests.swift in Sources */, 569277C429DEE09D00B633EF /* ContinueSetUpModelTests.swift in Sources */, + 31A2FD172BAB41C500D0E741 /* DataBrokerProtectionVisibilityTests.swift in Sources */, 85F1B0C925EF9759004792B6 /* URLEventHandlerTests.swift in Sources */, 4B9292BD2667103100AD2C21 /* BookmarkOutlineViewDataSourceTests.swift in Sources */, + C17CA7B22B9B5317008EC3C1 /* MockAutofillPopoverPresenter.swift in Sources */, 4BF6961D28BE911100D402D4 /* RecentlyVisitedSiteModelTests.swift in Sources */, B6619F062B17138D00CD9186 /* DataImportSourceViewModelTests.swift in Sources */, 4BBF0917282DD6EF00EE1418 /* TemporaryFileHandlerTests.swift in Sources */, B6A5A27925B93FFF00AA7ADA /* StateRestorationManagerTests.swift in Sources */, + 9F982F132B822B7B00231028 /* AddEditBookmarkFolderDialogViewModelTests.swift in Sources */, B630E7FE29C887ED00363609 /* NSErrorAdditionalInfo.swift in Sources */, 4B9292BB2667103100AD2C21 /* BookmarkNodeTests.swift in Sources */, 4B0219A825E0646500ED7DEA /* WebsiteDataStoreTests.swift in Sources */, @@ -12441,9 +12905,11 @@ B662D3DE275613BB0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */, 1D3B1ABF29369FC8006F4388 /* BWEncryptionTests.swift in Sources */, B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, + 1D9FDEC62B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */, 37D23780287EFEE200BCE03B /* PinnedTabsManagerTests.swift in Sources */, AA0877BA26D5161D00B05660 /* WebKitVersionProviderTests.swift in Sources */, + 9FA75A3E2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */, B69B50462726C5C200758A2B /* AtbAndVariantCleanupTests.swift in Sources */, 567DA94529E95C3F008AC5EE /* YoutubeOverlayUserScriptTests.swift in Sources */, 4B59024C26B38BB800489384 /* ChromiumLoginReaderTests.swift in Sources */, @@ -12465,6 +12931,8 @@ FD23FD2B28816606007F6985 /* AutoconsentMessageProtocolTests.swift in Sources */, 1D77921A28FDC79800BE0210 /* FaviconStoringMock.swift in Sources */, 1D1C36E629FB019C001FA40C /* HistoryTabExtensionTests.swift in Sources */, + 1D9FDEBD2B9B5F0F0040B78C /* CookiePopupProtectionPreferencesTests.swift in Sources */, + 7B09CBA92BA4BE8100CF245B /* NetworkProtectionPixelEventTests.swift in Sources */, B6DA441E2616C84600DD1EC2 /* PixelStoreMock.swift in Sources */, 4B434690285ED7A100177407 /* BookmarksBarViewModelTests.swift in Sources */, B6BBF1702744CDE1004F850E /* CoreDataStoreTests.swift in Sources */, @@ -12476,6 +12944,7 @@ 4B8AD0B127A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift in Sources */, B69B50472726C5C200758A2B /* VariantManagerTests.swift in Sources */, 8546DE6225C03056000CA5E1 /* UserAgentTests.swift in Sources */, + 9F26060B2B85C20A00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */, B63ED0DE26AFD9A300A9DAD1 /* AVCaptureDeviceMock.swift in Sources */, 98A95D88299A2DF900B9B81A /* BookmarkMigrationTests.swift in Sources */, B63ED0E026AFE32F00A9DAD1 /* GeolocationProviderMock.swift in Sources */, @@ -12503,16 +12972,19 @@ B63ED0E326B3E7FA00A9DAD1 /* CLLocationManagerMock.swift in Sources */, 37CD54BB27F25A4000F1F7B9 /* DownloadsPreferencesTests.swift in Sources */, 4BE344EE2B2376DF003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, + 9F872D9D2B9058D000138637 /* Bookmarks+TabTests.swift in Sources */, 9F3910622B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */, 4B9DB0562A983B55000927DB /* MockNotificationService.swift in Sources */, 4B02199C25E063DE00ED7DEA /* FireproofDomainsTests.swift in Sources */, AA0F3DB7261A566C0077F2D9 /* SuggestionLoadingMock.swift in Sources */, B60C6F8129B1B4AD007BFAA8 /* TestRunHelper.swift in Sources */, 4B9DB0582A983B55000927DB /* MockNetworkProtectionCodeRedeemer.swift in Sources */, + 9F872DA02B90644800138637 /* ContextualMenuTests.swift in Sources */, 4B9292BE2667103100AD2C21 /* PasteboardFolderTests.swift in Sources */, 4B9292C52667104B00AD2C21 /* CoreDataTestUtilities.swift in Sources */, 4B723E1926B000DC00E14D75 /* TemporaryFileCreator.swift in Sources */, 98EB5D1027516A4800681FE6 /* AppPrivacyConfigurationTests.swift in Sources */, + 1D9FDEBA2B9B5E090040B78C /* WebTrackingProtectionPreferencesTests.swift in Sources */, 4B9292C22667103100AD2C21 /* BookmarkTests.swift in Sources */, 5601FECD29B7973D00068905 /* TabBarViewItemTests.swift in Sources */, 1D8C2FE52B70F4C4005E4BBD /* TabSnapshotExtensionTests.swift in Sources */, @@ -12530,6 +13002,7 @@ B63ED0DC26AE7B1E00A9DAD1 /* WebViewMock.swift in Sources */, 4B4F72EC266B2ED300814C60 /* CollectionExtension.swift in Sources */, AAE39D1B24F44885008EF28B /* TabCollectionViewModelDelegateMock.swift in Sources */, + 9F0A2CF82B96A58600C5B8C0 /* BaseBookmarkEntityTests.swift in Sources */, 373A1AAA283ED86C00586521 /* BookmarksHTMLReaderTests.swift in Sources */, 317295D42AF058D3002C3206 /* MockWaitlistFeatureSetupHandler.swift in Sources */, AA9C363025518CA9004B1BA3 /* FireTests.swift in Sources */, @@ -12557,6 +13030,7 @@ 1D3B1AC22936B816006F4388 /* BWMessageIdGeneratorTests.swift in Sources */, B6C2C9F62760B659005B7F0A /* TestDataModel.xcdatamodeld in Sources */, 1DA6D1022A1FFA3700540406 /* HTTPCookieTests.swift in Sources */, + 1D9FDEC02B9B5FEA0040B78C /* AccessibilityPreferencesTests.swift in Sources */, B68172AE269EB43F006D1092 /* GeolocationServiceTests.swift in Sources */, B6AE74342609AFCE005B9B1A /* ProgressEstimationTests.swift in Sources */, B6619F032B17123200CD9186 /* DataImportViewModelTests.swift in Sources */, @@ -12579,6 +13053,7 @@ 4BBF0925283083EC00EE1418 /* FileSystemDSLTests.swift in Sources */, 4B11060A25903EAC0039B979 /* CoreDataEncryptionTests.swift in Sources */, B603971029B9D67E00902A34 /* PublishersExtensions.swift in Sources */, + 9F26060E2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */, 4B9292C32667103100AD2C21 /* PasteboardBookmarkTests.swift in Sources */, B610F2E427A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift in Sources */, AABAF59C260A7D130085060C /* FaviconManagerMock.swift in Sources */, @@ -12610,6 +13085,7 @@ 56D145EE29E6DAD900E3488A /* DataImportProviderTests.swift in Sources */, 4BB99D0F26FE1A84001E4761 /* ChromiumBookmarksReaderTests.swift in Sources */, 4BC2621D293996410087A482 /* PixelEventTests.swift in Sources */, + 1D9FDEB72B9B5D150040B78C /* SearchPreferencesTests.swift in Sources */, 1D12F2E2298BC660009A65FD /* InternalUserDeciderStoreMock.swift in Sources */, 4BB99D1026FE1A84001E4761 /* FirefoxBookmarksReaderTests.swift in Sources */, 4B117F7D276C0CB5002F3D8C /* LocalStatisticsStoreTests.swift in Sources */, @@ -12623,6 +13099,7 @@ 857E44652A9F70F300ED77A7 /* CampaignVariantTests.swift in Sources */, 3776582D27F71652009A6B35 /* WebsiteBreakageReportTests.swift in Sources */, 31E163BA293A56F400963C10 /* BrokenSiteReportingReferenceTests.swift in Sources */, + 1D9FDEC32B9B63C90040B78C /* DataClearingPreferencesTests.swift in Sources */, 4B723E0826B0003E00E14D75 /* MockSecureVault.swift in Sources */, 37CD54B527F1AC1300F1F7B9 /* PreferencesSidebarModelTests.swift in Sources */, B6CA4824298CDC2E0067ECCE /* AdClickAttributionTabExtensionTests.swift in Sources */, @@ -12641,6 +13118,7 @@ B6C2C9EF276081AB005B7F0A /* DeallocationTests.swift in Sources */, B63ED0D826AE729600A9DAD1 /* PermissionModelTests.swift in Sources */, B69B504B2726CA2900758A2B /* MockStatisticsStore.swift in Sources */, + C17CA7AD2B9B52E6008EC3C1 /* NavigationBarPopoversTests.swift in Sources */, 37CD54BD27F2ECAE00F1F7B9 /* AutofillPreferencesModelTests.swift in Sources */, C1E961ED2B879ED9001760E1 /* MockAutofillActionExecutor.swift in Sources */, 37D23789288009CF00BCE03B /* TabCollectionViewModelTests+PinnedTabs.swift in Sources */, @@ -12819,6 +13297,11 @@ isa = PBXTargetDependency; productRef = B6F997C02B8F35F800476735 /* SwiftLintPlugin */; }; + EE02D41E2BB460B500DBE6B3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = B6EC37E729B5DA2A001ACE79 /* tests-server */; + targetProxy = EE02D41D2BB460B500DBE6B3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -13704,7 +14187,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 122.2.0; + version = "132.0.0-1"; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { @@ -13720,7 +14203,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; requirement = { kind = exactVersion; - version = 1.15.3; + version = 1.15.4; }; }; B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { @@ -14105,6 +14588,20 @@ isa = XCSwiftPackageProductDependency; productName = LoginItems; }; + 4BCBE4572BA7E17800FC75A1 /* SubscriptionUI */ = { + isa = XCSwiftPackageProductDependency; + productName = SubscriptionUI; + }; + 4BCBE4592BA7E17800FC75A1 /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + 4BCBE45B2BA7E18500FC75A1 /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; 4BF97AD02B43C43F00EB4240 /* NetworkProtectionIPC */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionIPC; @@ -14130,6 +14627,10 @@ isa = XCSwiftPackageProductDependency; productName = NetworkProtectionProxy; }; + 7B25856D2BA2F2ED00D49F79 /* NetworkProtectionUI */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionUI; + }; 7B31FD8B2AD125620086AA24 /* NetworkProtectionIPC */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionIPC; @@ -14138,6 +14639,11 @@ isa = XCSwiftPackageProductDependency; productName = NetworkProtectionIPC; }; + 7B37C7A42BAA32A50062546A /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; 7B5DD6992AE51FFA001DE99C /* PixelKit */ = { isa = XCSwiftPackageProductDependency; productName = PixelKit; @@ -14146,6 +14652,10 @@ isa = XCSwiftPackageProductDependency; productName = PixelKit; }; + 7B624F162BA25C1F00A6C544 /* NetworkProtectionUI */ = { + isa = XCSwiftPackageProductDependency; + productName = NetworkProtectionUI; + }; 7B7DFB212B7E7473009EA1A3 /* Networking */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -14222,6 +14732,21 @@ isa = XCSwiftPackageProductDependency; productName = PixelKit; }; + 85D44B852BA08D29001B4AB5 /* Suggestions */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Suggestions; + }; + 85D44B872BA08D30001B4AB5 /* Suggestions */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Suggestions; + }; + 85D44B892BA08D3B001B4AB5 /* Suggestions */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Suggestions; + }; 85E2BBCD2B8F534000DBEC7A /* History */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -14376,6 +14901,11 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Configuration; }; + EE02D41F2BB460C000DBE6B3 /* BrowserServicesKit */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = BrowserServicesKit; + }; EE2F9C5A2B90F2FF00D45FC9 /* Subscription */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -14396,11 +14926,6 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = NetworkProtection; }; - EE7295EA2A545BFC008C0991 /* NetworkProtection */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = NetworkProtection; - }; EE7295EC2A545C0A008C0991 /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9111ff4055..30c89eddd8 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "4e05a46f0a9ce56f6d6379b79a92dc7a0182e027", - "version" : "122.2.0" + "revision" : "f15a57645aa1d07a69df9b41f483ea1ef0b786f7", + "version" : "132.0.0-1" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "f6241631fc14cc2d0f47950bfdc4d6c30bf90130", - "version" : "5.4.0" + "revision" : "62d5dc3d02f6a8347dc5f0b52162a0107d38b74c", + "version" : "5.8.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "03d3e3a959dd75afbe8c59b5a203ea676d37555d", - "version" : "10.1.0" + "revision" : "6493e296934bf09277c03df45f11f4619711cb24", + "version" : "10.2.0" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "43a6e1c1864846679a254e60c91332c3fbd922ee", - "version" : "3.3.0" + "revision" : "620921fea14569eb00745cb5a44890d5890d99ec", + "version" : "3.4.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "c8ed701b513cf5177118a175d85fbbbcd707ab41", - "version" : "1.3.0" + "revision" : "46989693916f56d1186bd59ac15124caef896560", + "version" : "1.3.1" } }, { @@ -131,8 +131,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { - "revision" : "e7b77228b34057041374ebef00c0fd7739d71a2b", - "version" : "1.15.3" + "revision" : "5b0c434778f2c1a4c9b5ebdb8682b28e84dd69bd", + "version" : "1.15.4" } }, { @@ -176,8 +176,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/wireguard-apple", "state" : { - "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", - "version" : "1.1.1" + "revision" : "13fd026384b1af11048451061cc1b21434990668", + "version" : "1.1.3" } } ], diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme index 29eecc0cc8..00cc0d9f02 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo Privacy Browser App Store.xcscheme @@ -106,6 +106,9 @@ + + @@ -164,6 +167,16 @@ ReferencedContainer = "container:LocalPackages/PixelKit"> + + + + + + diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index d0aa5916c6..a76b942094 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -82,7 +82,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel var privacyDashboardWindow: NSWindow? #if NETWORK_PROTECTION && SUBSCRIPTION - private let networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler() + // Needs to be lazy as indirectly depends on AppDelegate + private lazy var networkProtectionSubscriptionEventHandler = NetworkProtectionSubscriptionEventHandler() #endif #if DBP && SUBSCRIPTION @@ -182,6 +183,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel featureFlagger = DefaultFeatureFlagger(internalUserDecider: internalUserDecider, privacyConfig: AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager.privacyConfig) +#if SUBSCRIPTION + #if APPSTORE || !STRIPE + SubscriptionPurchaseEnvironment.current = .appStore + #else + SubscriptionPurchaseEnvironment.current = .stripe + #endif +#endif } func applicationWillFinishLaunching(_ notification: Notification) { @@ -246,27 +254,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel defaultValue: defaultEnvironment).wrappedValue SubscriptionPurchaseEnvironment.currentServiceEnvironment = currentEnvironment - #if APPSTORE || !STRIPE - SubscriptionPurchaseEnvironment.current = .appStore - #else - SubscriptionPurchaseEnvironment.current = .stripe - #endif - Task { - let accountManager = AccountManager() - do { - try accountManager.migrateAccessTokenToNewStore() - } catch { - if let error = error as? AccountManager.MigrationError { - switch error { - case AccountManager.MigrationError.migrationFailed: - os_log(.default, log: .subscription, "Access token migration failed") - case AccountManager.MigrationError.noMigrationNeeded: - os_log(.default, log: .subscription, "No access token migration needed") - } - } + let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + if let token = accountManager.accessToken { + _ = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) + _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } - await accountManager.checkSubscriptionState() } #endif @@ -574,18 +567,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel func updateSubscriptionStatus() { #if SUBSCRIPTION Task { - guard let token = AccountManager().accessToken else { - return - } - let result = await SubscriptionService.getSubscription(accessToken: token) + let accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - switch result { - case .success(let success): - if success.isActive { + guard let token = accountManager.accessToken else { return } + + if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { + if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive, frequency: .dailyOnly) } - case .failure: break } + + _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } #endif } diff --git a/DuckDuckGo/Application/URLEventHandler.swift b/DuckDuckGo/Application/URLEventHandler.swift index 3af3ef418f..610fe0f085 100644 --- a/DuckDuckGo/Application/URLEventHandler.swift +++ b/DuckDuckGo/Application/URLEventHandler.swift @@ -21,7 +21,7 @@ import Foundation import AppKit #if NETWORK_PROTECTION -import NetworkProtection +import NetworkProtectionUI #endif #if DBP @@ -143,8 +143,13 @@ final class URLEventHandler { case AppLaunchCommand.showVPNLocations.launchURL: WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .vpn) WindowControllersManager.shared.showLocationPickerSheet() - case AppLaunchCommand.moveAppToApplications.launchURL: +#if SUBSCRIPTION + case AppLaunchCommand.showPrivacyPro.launchURL: + WindowControllersManager.shared.showTab(with: .subscription(.subscriptionPurchase)) + Pixel.fire(.privacyProOfferScreenImpression) +#endif #if !APPSTORE && !DEBUG + case AppLaunchCommand.moveAppToApplications.launchURL: // this should be run after NSApplication.shared is set PFMoveToApplicationsFolderIfNecessary(false) #endif diff --git a/DuckDuckGo/Assets.xcassets/Colors/AlertGreen.colorset/Contents.json b/DuckDuckGo/Assets.xcassets/Colors/AlertGreen.colorset/Contents.json new file mode 100644 index 0000000000..ba7383a80e --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Colors/AlertGreen.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0xC0", + "red" : "0x21" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/HomePage/Rocket.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Contents.json similarity index 78% rename from DuckDuckGo/Assets.xcassets/Images/HomePage/Rocket.imageset/Contents.json rename to DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Contents.json index b899856ca3..313962dece 100644 --- a/DuckDuckGo/Assets.xcassets/Images/HomePage/Rocket.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "rocket.pdf", + "filename" : "Icon 18.pdf", "idiom" : "universal" } ], diff --git a/DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Icon 18.pdf b/DuckDuckGo/Assets.xcassets/Images/Accessibility.imageset/Icon 18.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1f47d51258708f01c2427fa6f1f5f45250c3a497 GIT binary patch literal 6134 zcmbuDTW=*t6@}mXSMROXuI{l% zpn#v+>(t)0uWRq_SMT0_``Rvfn(S=m`@j5kn&+>+n!o;fI^Vt5zmq%S7r#5-eSG?1 z_JEf<_4xE~zPg<*-k$$)eL7$M`kVR9cgO!ukJH~LyO{Pjo9*HCczXHU_~~>znI(j5 zVns5R;BDZcCZIGwp6>X$D=yoEL^@{O5^Qm~%pPHzottgiejEZjbecW`-b}li{pjIo zT(VXZrD&W%pY0?axidUH`CBDu<`Knr=j$;$7x_U(+|^!>2CVt|Cuu9 zT#AWI1#5FEN}12mn3l4U?{V$zULRqxJ{Xhd@$mBaK!~ngN{Y4md4M)<8QxV4Xk3Zt z01rxbb>r6|vJ0H1xF*p+OS3>_x|JpS3M;C9d94vr-5^*5lQ+$gN_(1Ug zWx?%t4;4FfxN-+~C5NX`+(Tp+Pyjys;^&l@Uuvy1?@XF{UqcJy`Q?LXA?T^v~PS!Ja_nhwb3 z8*;<;Zq?8-rpT)MW=3q+e*lY^f8!81`78)GGFezBWiaMedI#1sqrpC-5D5`(S8 zRXOu^z5%PIK=Hz&uU;w?P_(-xg%V;uBGdsUqHCP8=ue(WAv*1n!a_{8NpewwWUTR~ zXr361$F6~_x?=WW$8;)tN9iJm`c*FZWIzaDAb>RD5`#Cv%&*;It1kqC39*2c;su6~ zY}QpVgyDmh3ZT@S0Tr2h=E5*$?G}ioq~sJ3{*i7XsybL zZxL;6<6|){wQ!VJV`>AK?Bt|G2NNk6%0Y++p_&6K%+lm6x}w~&v1}I}mC=_lI0kADQ0}M5Iqb5oi7H4X0O6Kg=9Gd-6^gBy z5Nl=>b@Fi2Fqab>LCcL(P1hPcDH%Bxl5&f4&-<#2N;{ldCk086oNixPM71X*QR6Kf zqaIuJ#TJc$wHhswtTu+U)v*vqFVJ4S+LhtFwc+TB8?d6LQ?d5mfqfaJ1h)5=n=}4I z<9i$CTAQxA^;~b(ykz6DGkZhlrFY?Id&R+cx=`Aa_1M^AYOJkPHyD8?05Cu)Hi`;% zMB7_Cv&w0NJ*SnQy@*gdDm8~KeF6{E#nFC@ zokj;#BS>}y_S+E)_nEA+>rA3!j4997(>SZtnBf^_V?WnwrJWj(Dx11dukYuwIOtPt zJ9M~pfU9v-T3WEWKw7H7*3@Wpe)0Tan%LPq2@eQ4lDT- z(#j&D9c*0(CrY^-*71$IXqPA?IeJ0uYih1c6ukX?D43$kBrAvV{Y zXhUu5M+R2Ev30U%T6qmh*+sEw2qX`G>y)fVmu0TH(l%Q>Bp4vrg#b2qn=}D?*^PKX zf!NS0_7GQLXd~gU@reHoZAVO$-jO9{vSn=|!abF3bko1Yu4i1q;MAJlUKd*MOC_yp zl3qLXp%SiI=oKbopEsQPT+{%Gi-27GR z0&#L0GfJ;8BU^>B+^q=4tO)bwrJQszL^CrvU^^t7C^CjrZ2PR>IJAyR7uW z{$O0)w0o9|6=A?N#No|~d@4uJ6Rbvu_yZwed&4Bqe8h1vgQDCK&)k10CwkJ%ru191 zv;ZWH?26JJQPBYtTV1`zy_GamBgTZ!Jv%BYrlV`Mtqd|ebcWvE@=TZLK6=*j<5~~A zw99t(Nrn(uNyR~C8wPDQjMql&;uJ5ME8?4_&*c{hVCU)U8IeO?5jX`YPHY983(x zhDYC35t@T*NFLjbD;gs;syA5_n3R;T<_R^e^_-<+l@md;6N>p=pm{70B#^cfV(53( zcf*(v9c#xq716$);&ttFazuuGj{8`9sLr;xH!RW24NLsk5GF=H)#CSDu2H+Ez~v#m zt>sjEDwJf1sW9kX?ZzgfraU5SZ3JYF}{pZ)F|O{OyrouB=fW(bDTCZ7$ne zOe3+;cdi7_gZ0SMZi=$}>Dl2mK`}K#)*OoOct2uOUbCNg{m@8Y;$WFDs>X|@j@8EN z=n+v(5|Fm#ZR1$A%dBf7ZeUb6^Fz8=$QwDW`;ZW?TpxNxSls%qZB0UQpy}7HqZ5S? z)Z7r-T%+;je9+V&w|zqz>(K2ERyd31Rrlf=@08T>XyYz85V^@bFwo1uWJp`UhNgiW zx4t8onbLgg8oF2npI^B{D!Xpk4jz5#+Ky1ycKC#%Pqh8}>-Mo$-(}a2xCFqDZ@4TL z`?uYTU*6r_KRwRh|Cw*S{9XL%pMN{g7w@hGJ$||Kr2y{5b1t@?}@|=Kk*Ke8-LYl)QoOt{%W<)}RI*J~#^+AEGaQd;K0$ zXL)crZ}+^p{}EaH!v05)gC_QugC;rnI4;~Be{=P8b#wpmQ_-Iu&L1WkYg)Z-o@)@s z8Myz!742!}Lv;UR41Eew#NwEe7nk? + + + + + + + diff --git a/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/Contents.json index fd82163d37..b3519a0b69 100644 --- a/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "icon-16-bookmark-add.pdf", + "filename" : "AddBookmark.svg", "idiom" : "universal" } ], diff --git a/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/icon-16-bookmark-add.pdf b/DuckDuckGo/Assets.xcassets/Images/AddBookmark.imageset/icon-16-bookmark-add.pdf deleted file mode 100644 index 27e0c7b892a5446cbfb1350213f2e4b25779bb98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3871 zcmdT{TW{1x6n^)wm=`3d#O&NJ5{d)~L{*ER2@go94~t_$=w>(Fb*j)`-|vja$KGsO zklL48h>Sm<%Xcm_bN12OlUGl*j7_735}S`-8X=xN6N?uwHh1EKP*Rm=TIeuB)9P0! z;aQw@yUp#k?RLe)%e(&cykGX+@TSY3b_r8yuz7SO%D<|8e*3Zidee!;tIhVhVn285 z%l@-4;C5D_)9vQ=rr3H{q|5v8t*{uVoQuV~?Q*@lS#G;^`*rX8(NQ%u7rdXL=R(5=2*xX8BMk!z?hddy(Lob=AEHIya(h*WhJMAKn?2I>Az+Z}x7gk!O zvLRl;067KB1s#KeRLW5~k_sBh(LiEwCRrfx;J|?Nf-W&ybY!%~m3WNF8g!gg5v2%N zM5B^6BjT~JHo!Y4V2bWu}MpyG7&$d{8Tl%h|4z);&n znQC(|xM~vekkDu|<%AIFA*i?rVuF}T3)L!@r1s864bF^3B-K&FBuU{IBjDa!<)g?_ zc^7O1k`?kyxy;f;6Otz^p{FdQTu$0q7*J?`Yk;6_d`yJIsEjIl$vae!upAToWpsOC zpCno(qm5>WMX=~G@>iv%ZePtn-W5qw1EP%6hMLmId#rtE#kM}B+XTUlaOlg>5T6G_WCPFNf*Yv}`OXOZpdiw#hlRE!Pqfx+8ZV?~osLu1A;0vX(aBv4?> z@P9KI@%-@*6Oa2JPCUG~dGYOok42|x^17MU(+Zu=yNZ?ehMC$_Gi=hB&hiJ!PPwjS zw}z|k8KtV%A+gMz?{QO8d1zi7rp*UO-SgMW-EXr&m0tJe&}m%VUoB1rwOk!|_rD#y zEimamU)_9Oe%tg&_rL}Li(we%NdC^;II+XnxIe;Prs~L!Yir2RcTLpweqA87CmYKcXu3v8Zk{#=#Y92j$`R3JMbJ8YC diff --git a/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/AddFolder.svg b/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/AddFolder.svg new file mode 100644 index 0000000000..b9b806e64d --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/AddFolder.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/Contents.json index b75b5d095f..55db1eefb9 100644 --- a/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "icon-16-folder-add.pdf", + "filename" : "AddFolder.svg", "idiom" : "universal" } ], diff --git a/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/icon-16-folder-add.pdf b/DuckDuckGo/Assets.xcassets/Images/AddFolder.imageset/icon-16-folder-add.pdf deleted file mode 100644 index ba53443ad58c36c58052d6ce25d057f1946ee063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3057 zcmai0+in{-5Pj!Y@WntTAJp~6b|Fixq!ezyzGt}HS=tWT zg<;cYhlex6GqZDcb@AbiQAr3RwcP*qyO8qjTe-S?Jbk=AZBFC#pQ(R_jnpb@dgQxz z(~lT;H?&RIC;NXGcekfI=>V=_D2}JYxOot(i}A1fVO)QFFVC<0|AwRZQy5jbHBM=Z zH@xcKSzol3Ky1)C$l*b-*~`Uo*e$P(Q4X7om)#y?RZ2cZqBJSk;G}V?1g(vgRz++; zzzZ}8uc85*I&dq3ia9!yJ20vkoy&$G=S|L9CS_d634B)JP0CIVA}O0fDA;P1E-8lq zyAXZwUc%*KvNjG72ucAPLxUjZ<{+b6VSEkAgOO1aYD^T0R>|8Gv0|(!TO6GNN(O^M zI-HIYj2#$_38ijMF=uOKQ1H|ttFVf(BohEd@2$=gpEogt3^14CQXo6yoc2aWWvq7z zj(U|5h;x7(Uiw5^5r@yX8v>(tCX!vM>HvfjQbayFC?FePo3>7NNtNUad_oke=8CT2 zB{p5PF}Up=WqC0!EMFelKwoW|Dj&-bWaRU z`kg@5We-BYI#{n!s9x#pT|vf?lvwMeD+$KvYUb);a0?K5v98}uLW3Y{l^K(RFAy3! zhO^KfRA5W?7S~g2!ytuJ$kYrXL!id5GJA%uwRP(N!36VNR+j0t$buh^UsO2KndK9~s@VS-+$GXXQbG-qUhmeKR3 zc--tVMV-)(AP>eMo!JV-m{H*!(g+68VU2*9pzn!*&trtXL+vFmwhP7^o?w;V!NGP= zcXV@ykwG}>AQ|0=F%FC!>=2HJvCc1}e4z&=(VQmDH-5&nszzmTutS)?!&+9EiI3*< z7@>ID(Y)9$7%qOw^b@Q(*fLQY^kl^%;<_UKmQ}_$u;yT1!sqk4d+hw;>+3KsYMQ#* zNHx!2G-^%r8@?Hl$1?QiZmj!ux7(kN^3$&v^YE;G`TLJiuC6w>V*`F1Z|*nm_mA?^ zQn@U9!!lS_Gt+uJ?!O#{@hEAKTsOM&{q8jGa3W2IGkCQ*0F$9Xm=5O{2wG;)@9%Hm zs-y_kaz5vL{{?%^3;&NmIwpK0JEkej^UZ0q-QTvY6!h_HPCOpQ&jJ&In6yH_C8ie_ zB;fWKG&_1EX3O>@_(=~#_}IgXXOP8b`6-kz%07qiEt#RyVROG74}ymB?=FyzSI7Os s_*%ZYzj-|+WwqY#Pm%(z30_^_|1-k-I&^Oiry7pUE{L + + + + + + + + + diff --git a/DuckDuckGo/Assets.xcassets/Images/BookmarksFolder.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/BookmarksFolder.imageset/Contents.json new file mode 100644 index 0000000000..aee9d2b2fb --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/BookmarksFolder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "BookmarksFolder.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/Chevron-Next-16.imageset/Chevron-Next-16.pdf b/DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Chevron-Medium-Right-16.pdf similarity index 53% rename from DuckDuckGo/Assets.xcassets/Images/Chevron-Next-16.imageset/Chevron-Next-16.pdf rename to DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Chevron-Medium-Right-16.pdf index 463b781e2007af1affac63754707c0f9f2c7df0e..7d7e84fbe4807dd5ba892d914bf166abb2b50894 100644 GIT binary patch delta 511 zcmZ9Iu};H442DI7gp!TD!%3(M5|-_=@6KWcR>U))ZUyxXq7F!mRNZ+AUJzdzrAco| z@+Hpq|Nd_G?a!tqz~O|$Zgr~=v-)FTC^=ii!|z>>KqaDYR@qSof;due#|;Yi+wi~k5A2gL-Mz4zOEk8ThtRiyWxtM*sCs4HoHV6$0J_9 M-Eg;FKfk{G14id%#{d8T literal 1160 zcmZXUUvHZ*6vf}~r?@YX+DSDw1{qrgG^Kyp8*l zXD<18i|hOOQ&z)oAOYD9ypqe=xTy57D20(aR0gvnj60T-IxD~m8%eDtsz3z3iBy0` zh=jD-_ccSMb?o~%AuUW8%fRBaRSRv53Pa35iAYIfB(3V$1W|y8nZ_V-m`N-Y$o-js zoX)o*h-{!OQaR+&C=!y&4-K_+AVydf8!L&7zNGLwW<0U55G zu65XHMa&K2Do2H>Yg}e;gdiEPl3Ucfoufs=GkAgLcTm80;Mnw;7y`su>4FvdYK&Dd zQi_}Rz9`D3#)n6W9}Vxv-(L>BZMt_o`093fdRxBX1GlLQ(>PIPK+|kEDRFiAT4khkC=@gm<{c1`h;vTTqGZX4{~=l>kJZ$BDo)wJcrI)>Hi H!?(Nt>Sg|1 diff --git a/DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Contents.json new file mode 100644 index 0000000000..fa088142ba --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/Chevron-Medium-Right-16.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Chevron-Medium-Right-16.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Contents.json new file mode 100644 index 0000000000..f3d28607c2 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 13.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Icon 13.pdf b/DuckDuckGo/Assets.xcassets/Images/CookieProtectionIcon.imageset/Icon 13.pdf new file mode 100644 index 0000000000000000000000000000000000000000..261eb7e2fe7bb67d9ca3ac87a5ef2a649e3002d9 GIT binary patch literal 3835 zcmb7HO^@3)5WVwP@UlQsAhIZa3J3x;yG>EFMYm3GK@YB0*|=ESt?d+Pe|=}D;Um4i zv^p5{6X(sF;S4F>+~2)>WxA-8(Y-$W_Pf&h^=tj+jhgol{8tSvzWQO_Kb}8nOW>Mk zozBO3vs0_P`LFFXufKh(Z{N57rjz&7v;(d*a+Z=$oBY#4OjGnjlojJ+l(4c4ciQ&B)r7}!yTCN_NzJ}J|8V+h9B zIkO9k-{*(ghtSk$qG%% zcGkxfTr;n(f$4%9jOFZR%bsZ$j!6Ln1qOJ9=|Cu%*Q`|#C@KUFGHqj#@S2F*BwVik zq&}&SU#Y$N`M;N)L|m2cDCFp=bb87M7m%fL@A_aE;j+V2tS_4gj~O~=(h!rjolhnX zgEpbOgh7RJm3Y_p>0+LtA|FRyEna9^x73+nLhuVj-4HdK23onET+qCf++g)sd=ccs zFj7ynkXed;QWd23cf&yaLMtYt`qFn|0Vpp|W_B0M;$I}?f~3M_osgbQYdjKSo1N=K zNLtAh^J-#|Yos3;2Ew&33jqv+>sKJ)TEd@Nkvru&V`;r&(@NOID1|BbaikV4>i`Z4 zpt*4sGtwhVPY-rKY%3nZKdya~Wyc*sY63+{< z4|%WT0o9wlTR6xd*Co~}`--MbQFVVUvj+438`x5>vR^%v0i7Gvx6v*QY=`JW*gd(^(grEvcP!4}UIBFWiaayo|cu}Ix2d9WqDU<1;q2L`QRY7k@# z81*W5l%sMK$F4|$KOnqc$aG*(4kjRokE6yPxX$}laV{kUT9FfVgFrnnkz;9==(=_e zB!6<0<-W_-imB027qM9}tdOKJ)mVBA1YIW8urma+9%UbhyY+ z)71;k>g)afa6ah|ztV+=p4BgZ|1s;;{pN8N;K%u4yLo%~qCfE2k)JxK-y(HXnAY>@ z@cB5+C(T!?wb0!j_UCy|9{E01!28XSFzp3uZe0xFHn#eH`#@4PN3hn#&+Xwe*_tc; zCqfz~`lxT1IKUGe*1&H!=grgM@zV6masH_2E})k1`Bw+;t=9z0W9TE9A5EXd@>tTz z4HFy!wPsmA2-{l`Kai6y87;p((&SS*v&8Xi|xb9B`K@*;c(U* ea8B^*{q~=k)aPsJ=6KHG*kG)>x%uX&cmD$7ANC*s literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Contents.json new file mode 100644 index 0000000000..e7cd622579 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 12.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Icon 12.pdf b/DuckDuckGo/Assets.xcassets/Images/EmailProtectionIcon.imageset/Icon 12.pdf new file mode 100644 index 0000000000000000000000000000000000000000..671d4a0b39c46b9cd5ea853f3391fd7a47995818 GIT binary patch literal 6306 zcmeI1O>Z2>5r*&cE9N4=4&Y&be*lJoSc-!n38K)s89p$pwaj3}O-MR$^6T?dPftyA zNf?ljqvs%v-tDfgs($;en%1kgZ@&4$E_s^lZ07Ud|1r(;mtW3beKj5L-pM~FH{w@+ zI^Mm1{Bibx*D~ti@&0)8VY+&A{P*qQc>VjY=hxqk|2jNO|C;P#+B2K&t+ozyZy`IVK;i1@)m`TI_((t*H{P4EiM>W+?Rb#d#OJ_^Mb~ObRJRd{rzh#`zG*3+Zw-HLHC|>$WJ>#lvK=94zaz`Zg{sKBrvW zoR$)5v}U$(n;MtmtIGyQc2F~U8bCbg8MhcD_e|P%i*;M}^MGOU`L>>>AEzIFGu=&p z{RBRh>Y{A$hmnaAfK6mE>~bGB@JU0E)V&m#?0I@N zf7p@1YTrA~&A!&S@!j(g>H&@fiEMV_@DQ~a6n0tI7084q#({%9`y*F7ggthoc+a`W z+b&g@g-QgB5t1vOBZKu~{2Z2Ai>Y#Co+sgxSe0yap0!P>^752bHCPMPkgJ_l2@#8_ zL}3d|B4%M#696;E#X*iFIpM69?7d*s=R%$^Q+!7LNRlN&j>!_LiPh`~3Gs_BL_Z8` z2^OIXX&*_>lQSN|us|gWavnI_P^RwpCo#;++i%^NVHC5-5BQ>m&JM|(ycN`FqdgN zhzNX~2J|ee=CztqN|9p&zk_E-#EZ<|rfC2}dri?vtq$1|OPO~@>JEyU6|%oZBMXw5 z`mCmOyB9U3u4buo+lv^$gu0>Yi01bn6%)fmu>l!b+dHr$sLZQaho|?S~G_p23P)Wx^&DtK3oNX1wx;G-K`#>0jl0Zl- z#wA%7qlZR5*65v7FYGxAYZOV3X#NU;A`>xXI4SnpI01E$X0JJe0OH}g-3;8`h_yW; zVq1KQ9fl22@ROuxUBeMtOs z8LCrU$<@KaR2xkrm!o)9lpNV&3G`SNp@EC^P*%W^hiy> z6)Xs-4Jah;WkfSe6BuX@3seoXUbHSE2rd?A3+9kCPPb@lu?C@?g^9r>Y3VEtG5!c* zBB@Js4vvB%^EkHrbeJfelrs*DREgriq#{+`k!DT3lr}PN)9Ixt)FSh&5=5BT0&HeNQo(i_m+4L zB|`-RjFR}^YM^NngwGV|ST4hrnnvxVtEa7}8Ss#(u5D*^5V+|zZIkerL+K?U61qA8bm^*Ef6*!SRnGr#?W8O z>zaesYi+vrYk6(;_5WI4Kc-cb)7>8Qub-Ai=9K9abJ@AiAl#?&{4(o zYoD8q80geFt$iM+Vf}bVrZ!9+kptg-(w70#xdX=MGA* z0cz_bx?1Y>I(gV8=p7xmGHg@ei`m9jePtMjq7PU8O51aZoxr*Y9mm&xd*q;q^++XG zq`yd4&|h^SX~#O&-I4F{ZbBng9~2Qe(0Tnuwy8xVt=Hv`8m#`L28Py4^CjD)f}>=V z^U+DAF9XqD!gMYj)i!s)^wLhQllxuqcCjpX$m@l3OeyMpGaI+xLtp*&?(Y2fFn{+w z_tHF9fBVlrkMq^roA<{K{Q3Cq_U7yJ&+~ULWW{P`_qWZ`_3`2S)BWN2Fms~k>u&D# z`R?&}$BJ_8y@79U?!jiRFd8&mjtiuh=2w5beTS>FL~uQC>%2by#9aH#{|Au45}P_$ zk^moc!9DQTH;*@`^ZTb$f4)EdFqt_|D=Fm9nG0dAVSxL??QMIT?-b(7?I{$q+|tOS zqhij~zNZkMK^G7eXP@|Ze{*{}-cS55!qp$%kdDtD&L57S&!64C`+QBx)%E%OI7`4S e!B^kj{`ZLY?SB()?jKt?$&@g?diA@%eDgn?l-5E3 literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Contents.json new file mode 100644 index 0000000000..9a9c668121 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 17.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Icon 17.pdf b/DuckDuckGo/Assets.xcassets/Images/FireSettings.imageset/Icon 17.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bf31e9a7386206c8c9b2d7fd8270ce01bfc9d1a7 GIT binary patch literal 33137 zcmd_zORwhHRp0S>y^1qIf&taf{UTYGzy>QyB8Z&<63U<{7sjM7PInVQUVT2_wg1o4 zUFQ@eWSSb-r+;VP_qwmO|L2eX`j>zCr~CKUcklKuyO(eO?jPU1y!`BEFJJuEfBD15 zmoNV2um0{IKYsWD=HD0p>Ek!Q`{5s64jTS?gMRhhxBv8qpLCf2UcZ|@tK)Bs_uZG@ zeE)|pzx(*jhkyCx_doynHq>wa>F<7+N&oWO@4nu!Uw{1m+kg7*!^iKN;@R@g|NPs3 ze)-#%-TU4A`|*eU&5y>}UvJ0LizzO*+wS0p^Yya7xsmtB!+n1{ zzZ~D+PRHYA|MKCTC0*~w{bB!dH7abU{c(S~oQ<%1zu#XD{qz?4V1J)!cRrr%=jCql z8T%jim+V9W1>keYS=l%UN$cJ}tTiNwt zce-4Re%J4?)9-%W9WLjW{eFf5eQ;ox-DQ7p6sPxx%jISjcjtaM?JhyLixu8J2Yq<= znRa%v+aE6Wc6@){&uF*z*WLYa-oNY*@Apo?Xm#Bk&iAWX_wV<+-Tv0GoKI)7b+nfS zb;yUq;dF2Q-R!~9`t|*Ga}L+{*A{$TJ~Lro4UAUw1Q~siomW{ z$Bfc$h_7}I?|i-OBlz>-cskxp?-BJMFq99& z0sG_CM4gn&?}}a{AKtyiZ0K_8`ml1>*A|aw1MOc(hacxV?T>Z@yV@gO(5l0_Ip||& zS-<^#x8ENjx7#t+YrgaSj351S+w_GDIUcmSKl;5zKD>L2q1D~>ewiECkw;fcxgaMr zc6on7?r8DDJ1j`bkVohD#K2->>%2g=^K7Fh;^uI4Qpe8jtRL)h$8O1|g`Mp0ej-R* zDn@jbZ-wQDcmMG2@85m%?!WxIWi(-Va$6=GA&oAHG*j*Gmz^_37Pm{6@`;ch=6CIc zwC&xFSHdYFjgZeTU%}iXW^2XRmRNB@_J_<90w??cacQIq~Tior^BxEIiFBPy!Jeg=eoXf9#7S9dVf!VrBZ02-RXR6dN+5XH@^Bz zyL$hq2@)%pBg*+2sQVs0VbKG5M6-MaywVG4C*t|G-_80j`_t)8@9a~7huzU~Plv-1 zxdvTNnAyUOLhdGG4YI%7`zif*kN34Zk?ftB^`8^n!?pyLvyh(Jdu}J*M~lO8cgd?A@8`t=*Y4x#4U|`*KWtC5#D+X>~%% z2$@K?YTS4`IP4*Af}q>Svxj36^O%fCVl^WzvWJ|ngj4;leuLb9q9vc}TC08N#nG8- zxrwjqJ>gC&TllUk9uNU;;AsQjbZ^ujwc|NGA7^|Gyx4*}+oR<3^-S!31-u?H2L*7( z?8l{VuZZHT9Yc$;a7aI1jz@Ac=&-w<(cu!*XA~GnDx8rzDRj76de8~c5EOTc7d5ze z5UXee?q}B5vVT<9lvV~*%O+9}ccL_%M-^RZt*-))`~B6jf#?u%20pa?vwNd1T`??4 zkvPmD2`FCPr}{{b^L2k3J_vR5qpe83-Tq?W&yBE+d|5gj#kjIrZ;f|%axbD`k2r%F zVRyY=5CTHG*p7}8vEjY@p2Na!rQACbcSQPKLI`}jpA#6MR~3w%)UQqdpBeTA0%{x{bov9dF06S6P5GP!jHNxf2@<8w&kE;p=%NDXwE| zp~-Pmc2wNGHV@_@O)vrOP2}y3X2&*uMQr3E)#9j$@~fk}QN=fM->Hmyu?)=Tx?1hi zRE!^ppcMJl74Yp+GiU+H&W-K37jr6hm92Nh*cX<_QYt5W$wDV#um|ss`+8LjsII9s$tM!7rE9(OQNhrZ4n zh|6HkCed8G8)6H~my2WKs4;?{dX6PL(BvaYBzx$X~VtYw(p`Lt2-mHEkO^-B_B zxaDacKl?V~)6hjT$J?g&E_yNG$OD$sk?|Q-Tu>h?huDei$&l!SPG#;+yZh}vws5iw z$!LXSE~nfvw|}>3Vi>iud>K3`N~#rCWr^lm1*JNtE4I2={!a{X?Y8x`?5FL`%}zgc z5cMCo36-!UjM$=KvD{#}@!6@H-Tv&`h)=7$9|-uH5f9`q&d(QT#%1|_wJ=-}w!iT) zVQ>4cH6Cf9wus$be@58}BOREcFp~E~JJ7~=)SusE(^pH)Ex0GlGG&ag?7j;D?rvFJ z9r;Kj3XKp)|Gv|hccUNvPNGt)14n)Y>ag(yK2+Ba@mXQRyVYtOO{ETcRBE%U}GFn~O zGii%JW{wW+MqI4bB@W$1&~2X>4Wj=yh$>mL#@gilv=eYkeSyYIt*aoIUxHX6TdN%U zHLF|CE2$k>*@CspZeP$9Jq={Dk5=0Or?fXtzfpBXDVkzMDU6m^HBce4HClW=d)?dW z1586rJL+R=M^$VP;k~-%ZLO}PI{?yDG zLoCA(t&y2^6@TS{O}k5ejjlIy#0R=EEXdKwML=fFiZ#&YbS2AK`?X`uOjs6=qRa!aDUGFT zEFU5GiSKAhg{RRnj%2UzL^RrKF%OQVJ7#pbIZG*KjG5To1kT>^+=5<9%-QRk0K&f# zEO1t&>k0xebg0aZk5Y{9$f;ZaFkLCS-0kVgaa$L`ZDmW;&N(a|5&ej6 zg6Oa{8kvJfR#&dq22v-EIwR+@WGB)f?iaL)zXO3_s@0Xvwsl2ywpiDF^2F(j5WAa! zc4?%6&SdS@brXQvO(3PNH2l_%x4d;NDluIdMxX2YX!qEd^{%}#Ft@ew5tf>4icglZ zR@dRI)s3#J)naOEJFm>aY;1|$;*-c~?R7CkP!#z&HJK25O?L%6X%P`yf)Ya(*g$o~ z`SM}2IuXo)NlYsg`r{cL1rJJxF@nyn1SYRQ!7^bbO`JLJgPCDvIsMK)A&e_R>SxiU zCd>((Y!7mi>8c;ktl%YEoRV=E5Kg6u>qpzr2VuGq=n&CU2Nfb-KmEwwHBu>yh(jx? z#j$IZv}jdAKuOJz)16=r5%ld0c5FYyI{kzJ-T7d-!L^dwS^Y*(hTI(Kv^fZuZvF0i zBD;RFDrT=IcW@3dS|B0d^V5%>$j(sSF3d0AUCI8zmYS8xUQ`q3u;b} zaIvjvwVjl-Cc!Ys$|xCo`Due?md;oN(`xle82D|lxIpcJZ$V{CjuERYhCxaK4U(c6 zqsq|%tHLUwIN*AeD@UU^eZA=KaSqSC7d0-GKiS+YU9u! z{c-}QA5Xhhmdg?uM3!?QDY_6YZTDU)w)yIJjjSE=T8AZJU(1~3fp4kvXSn}sJ9?cKfUtWVT(N5ssgr>0hX)~%MJEw9 z_gZLk&KbhHoPMm@ZW?lQrj5kkS|CFINx`%C-Z*#3H^+nF3Cn?a#|x9r>HP zWn82s?C>Q1!~l;96VK;9>QiwwIUHZoz=ucFd3b87txQN zQ$BOEFy1(8&w7PA2xA=qi({=Hw?JeUjoGQHbLqrjLX|iHHBWk>R%DN}fVd7$w}1*E zG)OURS+n{H@@`o6RiitA6s4FT(P7^ZbUW!Y@eCbTztWN2TDL%8YLx}lT?-YK!V7V3 z3lVErx{&{mS87!{VCJLaC&pXN4k1v#PH5V=wMRc9V^$?(G&=3V3e~)kqyT1}bSa?& z5#PiWvp*5pq0)wEg~8pgI>{YsurD~9TZjP_6z#Drh{ zWQ;~S!0feF!lg`#`jv1-fv+V`nf0NrQkjdoPLrJx-$K6;CORV^?OW6q-R8H*O-TB) z1Vt8^4tTzr9L~p3gt%+>04xp7z-oUC(s8UMv(*$W!IQ}~t%|alsw@GWR#l0v0;4P_ z9= z+`u?AX!+FBE{a;vwyhJ}^f9aC&$c>LIi*OtmVjSf>8R;Cf@DEk4#HC6q^%u>n1!%) zAFFe)3o5L#7Cc7(fFgSjaOp!E%jjP145HOmru9rPx7RnR-huX+`YZdp?)5;G8f~C5 z1=CeEPtMc=?OIpPwUgK&Og4~M%L8JPZ(XB~)s6);?FgI4XzB$PG1QVDKQ3~Iu2qA_EQ=;Ez9hj&Wzk8jwUgTk}b0$2h_WF^k;^;bt_w2UfMx>a5N9PZh zq3VZ(Vh=jU#v{@4o3-mE=Iq!`I6jEEyJl4r%IKN}7r!VHTBQ)ORn^>#?nh8{=#&K1*n)9EBj3ZJKAcTwaErCp^f`M2u94e{`B9uq;$FdE;EqH@6@Ji%5tEs^1RW;|It-2@G&dHWM*~g5zchAk~OQ6y#>im zu31w%kFFw-tDPfxw9At>t4(L`&R&`53#yFyvDf^j+S%+jE3LHJRnoC_t!RH*E$Uh0 zRo61$6n@VXOJNzT$iQlqnNcfO{o@H&dOV=Ii$}Y;f9wTZAnOh3OjnuW1&uW} zP_-L%oizEQYvrbaC1AF$1&V{D2eI39b^Qam%OU?jr`g#FKaD0HXgQMXfvM^kXnF4$ zc@BM>RqaabqzHxqyQZL(J9GpzDf7Bw$OS3-n#&2Kydnuoa?7@$=Gz2_vO8CL80jjo zsTCDQv~07gx|s!YuOnEa;X_$bS6$KxNesl~1);oZSXbBLMPnym@0Gj&lp7>)g`o0J zWQ^p&sK2aY!Rc*QhFgrqkhjdNaY-~u=Fwr|P@}>VpFGR?WtF58olVld5?INx&ye2K zUcN5gY(31zMFa}k^Me;e=DJ+a1dktJCVAo}9k|+2kqg>(XEge2pyE_>Fik;xF0kOy z>grAhs^)5g$Z`aor>}NJ^fu_x71V2)%)aqM9$ja3z>L5(D`Hu*rcnpN zt_w=EKNc+2I3PkBp0bCYnbftw(&|det*-3Cjo>TV;t^}d8c*^@jg-zTgvI;hcZ^=E zsz9F1T@fV8*dd#=T6CsSbE*8rFI>nrKn?4lQ3*cfpQ2&rdIGZOE$r#$e606 zD~+m3k|3L|_yU&##nclxMk|5qv_YKpO$o4FR++tg6Dx0Zbu3Fo`P~xRsnS`U0%L2R z(M&c*Q6)mddQE|A$9Up{6^L0-Ar!)bPmzIIE8&`vA0@OOb^()qBnSmnrO9fyJ;{;H z%juo2<%qDx>o3%08TSN4Ghv6Rz9Z32SXondxce5T9MMx)%7HBHwSI3CMj;SCV5 zv=CZQWLon4H?fW=C5-AVqQ4cfDVJNdx1lslvDJEWfM=B)t%6a5t@?o434sg&@e&F% zxxqn6iLGUIMhi-hZCL)^)>~lZ(K`*c)-#Sy>#^}|J?MFznl(Dzt+#g_%Os+G z(cAPc^R(u{M}BIJ6=rVO%1tbE9HrSebu}#+F|A^-F}L)FEnCd_#C>aS7IM3tmJ@K9 zmScM?tWbjcMnFj&Vz5<2J zm?9fiq3!gZ%;YvVOKIDW!V$Rg%Z6caNvG-H2dr_noQKsn*iN$;D&kUsmtZ z0aj+;;@eBFxh^l-wv}VYREOSTers+DxF;lGm7&duo+50OGekrAu30(dIpH!blf2HQ z9K8YveUc}|T1Gk`3w#+SSGkmsoEH_b&N5%In;+-02!NJ2RR}Q5Bon};R>@bQ+}tWi z$mwDg3=1L0+$X=c;NlHr3vW9*Pjzy7i+6aS^0bp)i@LGoK0}v8VAUU9P0jrj1RabMgT5?>rmRNVg3Sg|>o42MnXLT9(L5AicRJ|2^BYXT)50qETD{e0J+K0}TW_ZH>|15u1gYyygKrqx zSo@Z!>{OKSNM>+fw%$a}j42~fs(RLg!57BBj;f6qBg~CcfCXPxZ>m4KHKsUdC!i&D zzqqVCRyqW>o`=-Vk)y!rU7&qQ68;<$qm~G`puUzA=-emFYlgxC$sk4ZsEsInP_>|Ep&X$Ms;Xfmf91Fw8lQt7l0-(+Ojc*l zv|5jlOpN?^+T;DZ^j5&i16DmT4ht^G-lE#v2Qy1GIaQjBA!X%*qenWK<>%IBzyeND z1+DDdxp-PK(YMMw5yOemd+u0l$~nX>rR`R4Lk>^#kSc>48AKOWx2h#|SDk|qy_lK! zt@E;{his7G8~Fz<$Tc!(qA>OktD<98F3dXFNf-}qlA?`yPzkwOTKLkxvU7yO^pi?UpudMdR0)Jr-Ln87Yr>mQFTe>D|?C zGn8R*z?``iZP6#Fli6DK2xLYS?BCXdtREQg*?X$7TV*drx88D|TkpaMYi@b;2Zj$H zb90$CbTs~)P=V|<_q;v$jGQZgxu}xaGgMhYeHI{d57i3Yc(%kxEha5P&AYN9F?_;2 z#kk(TQPkV}x6u#j0!~d2ok}gc+97CtGIc~}+29-X@^jgLeW`#Jo&(1VmHQV!70%ek z!wR3QZ^ZV@&0L$g=b7CYu6&|SdN!-*yaSMC$nC2jm5nrZef2;tK_HQ-l-$FSuU^58 zuZ2+r;xwf2#-N+Rq2e**fcq|y(ta!NoBdKdn=pF&2(VzWV118?SVf7lj+%z*9S+iB z6|FEdek29SLARicYKwGl4c%_Z^Mn)y`N<2u=5V$6 zRx3oB%iMa4&>}v07UJ7h%Zs?~TlboMTm0KTt7&MThmGT$;H#CjV4>kx5BVk-kAU#I z0tcoC;l(LZoJ2QCDX|==04nymh|rKu61^OgGfAY z$SNnU75jQdcTc!Aq$^q@QBs4rh&d?}lND=!7;%k--DjO1OU26z0H?WTm8`D){133{nVv(qvp$Pc>A2Qey^Wb4nnLTKw2v` z8`A5Ag{&&Q!76c@R-l@~$_@gQ;#3^Rq9L>;}p=97}xMz1)_NaP*cc1DWR21%nq z=-+Xb!%*dhm4pb%uvo3CtqMWVTbmLJGoPr6%{P`)j~Z!ca$_O6ln@yslu#SIlfx4x zoIAy#OcCMJ1Jj+`M+dcsf<0kmgSd1;91_%Zrq(Y`5p;w~(f@#BsC#~z} zMORf7S_wl9x0MRwTR>zJhOEs8A`9q4tZv6t$+G+Z*DEe8Zd+DFfrsPI%IP%c_QOdly2q zPce42%G)}d7#U<1Gjb+O#=~*^8O1W&6LTZ|$=*yU-sf7U*IU)fQAB;9sQppr0=&`8 zGMgz<8VaY<8nQ3j{W-*HBC5M9BBoyjz5G+f53y`JR^P^_RDYxPA+vFD0Cn@p6Kq_O zGnF>$U`>S5c=qm86`;)we_^36H@cNES=_|CC`wMlIlYkNHw=N9|7M*}6Lnk-Qjb;eZlmtb_uGOWODHmca+(mf2X3o%to*yvxo(>ES$y z6%~sV}R#&-K>!ca?VKe zgmcZuG3-ti*_Obak>ndT#8MVQ=uR6|E!#SCEjG*zPAm4rAf93+kF81IZ6U&lkA5;v z8-|ysRgsrjd4bG@44v6ahq0}**CErYmo3xE(Jmx|`>o7b6$&IAf{3!%u!uZK#5=1{ z%~z=;Vn#;tmL*n0!`I$;jC3m>AJ8;ZUS{7nk7M1<})Fj(R>m zqhqSk3$H&q5ZAw6*RtTuUbL}UM_aU7CB&ugS&J2aPL_zwvM0_k#Y}XtL5fO^XT@~Z zewIr+O(|aw->~4i6JG!LvQ-#!Ahzx{1F_Pk9T2a-U+#w=i~C{*eA zieAjDLQvKD)zxPEQiXQC3UIn0VgU&TiATt)bn?YR7sS-ux>jdB(FIm;A5hS)7$+wr zhrF_^fvO!?5c!ju)vmgO4J`gqJ2q*txVrMJ<||}s^;Sqj_H%u|sdkyj)6OZP&LCFd z>WZTYfS__?s})5YSq7SRoN2~+;l6TD)tZsoJd_ z;nHYbFxHu_<;`YRV8psW4}5Oc;dNTS#?SgOoIx zHdVH)e$IYtRcS!2Jj06yjkK-`vMS0#hGV8xU(}dZ6|;t*rwu_;3t1KZwpt*5%{N)7 z`iU=gI11EDX$>MuGl*)b4e0^J>c==*`;!Uj1goYdck3r!XGm*(>Z9u5s1hgS-u0yqHt!HV zsL1v}-SM;%Q(AnlU9Wc+hND#L?r)tkv_XZo%XB<8w7vBE&xqi7S+1>1rd5o@mqiP>5^V~i-U;Jn@-OJ55X zwi|mSkg8h;fXCc>AHyup-mDz&-sc6$`p??f&>1Nm6DPno;%Zls$y^RQtBgj`8}7rp zX|f|)l@sbc@tiN?Yp^QM**DcuJEaUjfrB=s)U}F&)H%CHFywJBuf7k1re&7L@TR^h zgGABF2k<9l&N6mE5bQlPUgnA&J$3_6j&`9YK` z(t3;9$lZ#7nm~ko?z8l(sX)Xjb~0MQs5!Afp|%BLKnwuN#vEFc;!3m!WdKfBZuRgR zu+P&Bm>V}?7oI>d8B++FC7WusimZd=>6U{^Y)ncm(3#AF`BzKHvc;;WespZ%x)KQ|by$_qe52TBy3sxRdO7?+q`6*&dRaCTyXbHzK&Ey(B?H6xrfE>t-j zh>~#JH6e4(IWly-3a7`YD`M#pAZ9g9A2lt;ibqz^lLb&YW^eFdl_T;DX#zGtUpnH2 z6x-*?_%P0LU>a(9Tw+_fG)NsiH(zD%@tGZBX8nXM5i#cK6u61AQeG1IR$Vtymb5@D zOjG?hZLAMW;(U2?jtF1N9+5(fpDNKdf!mUu9*1-y1#iziT{63@XvB`>@GNn@@x+N? z{Yh3;pgW-%%E}M&O2pl`3jzn;UkU6I4XL(vB*s0_{vsqRaOa(RvP5d3M+SWi37f<* zLa2S9r+#n0WZD<0MDBV&ZTzxn9j2&P-}Wh#jwCu&mUGYEi&UGh)8Rb!(55GJy&@o~ z+z-oT{H|P9Q_n3{M$|156e+T|=g{f=yS^mrlARwBL>4LSXQcU%Y?{DySpoI~}$UyIN4@|yn3-lf(&Z~L%jJgx}2Ff2b6~X3i zkPl~y*duHES3CiQRvPg{uMlhR3B@%koSu)zuv1bR z+d_%WvazXS#8#}cuK|?dM_;Iw8^|4qi!8Z-R<6~2fJ>zS(K&Y29AQ`U8mZrF%xyCg zTzpz+jscbMwv9w^RT^LPL3E#qCgRV?>w*fH2n$$Yti#h9Rz4`8fJ^S+TR2#ULiw+uP}pZ?D&)?b$s#q>x)k|{=dU&^%a zoc1xGxz|gAGAWf<(8tFZ8;ytnE z2kNMhs2>D`NG|7AC_!?XVWP7xsF*HOs~`g*mCBzVl~| zOe{vDB38i$tWs~`j6xM***$~3?%Rk@PR(j4yBe9adpmavb?Bz2&D}x zdFU>fJOfohlrR^%RODAz_HN$AJbxzO%LGUB#z@+v2+RAMSD={M0P%%ZSCPv&<0q1& zc58ht`zfr%bI4K`lQNWclQp(!o@PdWA3QXsXvD$_$%So#?%B5ypK?>Mmu5$^U(ZLk zNJKYNoH!QHy)SVUkC+g1vD*&(ZSTf>%bLT&3`}C#4KqvVzpR5B(-BMX-eRSp6HOJhOSvHJc zvMVqb-O0Tm0s1DR^k_2I*S1nU=H%nh)(KW&0*-gTwAfLRSz#lGd!jwMNQAmjg2ufX z+Kd8b355e=svs@wwo02q*ep$qO?#CJk<6K-OjxzlgHZ%q9J(iOwPK$?kToBKrfZ!p zvY_W`wIr)@3&L-ZUO_dsVArfuIp??^GhfQ9RtPNhg2DSr${~lzT)>A zmcLc2vc;<(6*ov}wLy^LqugV?ZSg zd0B2beliSk4S98s&V?lEQ9!>U8Il0?do6R4n{O#AHOG<9yDS+egx*bu5I}>J(;Orl zeUMJM9ck6m^@R-qLn`7AK^L8T2xD=Oh;{X23(QIMQKr?(mezb^YIo?^GmEC5$FT}xj5Ft zV6J-lMab>Ay7-mfV=z-SAyYX#`zypW7?WxQo*h)yu9fP#p)AEt%e0c?>6@d|4;T8S z+ZXm7zLZ_9Xb>XkGD1V1ssiv#w<=$zUpjMaG4^=DK5ljX{}Lp6b}mZE3zr~PN&>YH zg)lUSTzgh22hKznj$r$+O6As_RE0u9L}Pfi(E7yzZ;dJ`M40X0(%--SlC(vY)q+I4bfWxi(~cM6r^c~7N#HbV69Use`~drt?Giw&l)K@ zo4v5~9_!?VFRWnQjP!{k@mA)n5SVyp)d1x^#D15{goi6l6y7TRJr=)Au~g;)bKFi09;6>J!C z3wqOQaes6nnwpoZ9aoZZt7{)q!p1>;oI3&1w^C3PsF%p(c3n}TV9|hd!|E!MN8Q)1 z7Z8nR)Tv+vQIMmX1N+FDXSt{lo(g`vJ>II_JlX^`;ZY@Zn}Wx910K^!E%AU70nMtIX4`9ZE8AXW9JhA!=XPqB0A348e5|h3kIdSbFOROMXgXk%uk4qi6qpOD_WMS z9NGAaEW8eSvOz)tkqAO^;qq1l`zDU%cnb6M#S>1+$Xp=;1gB^NXB%=1Z6r;=kwX>p zCcIvYsf5>OC~o=muVMn@(wAIwZo&i!@qtPS3^X?F3Obl?YbU)oA6>&y?r62^S=)5o zY8qsbsxDYvx#mNe*)Nl0U?x;aGF=Ji`LJC6>zb7v$DZ4|%HU4B9+mBpFsTg*03X=0Oav)@q5g z!p)<@r)znFHs<#@D5y-{f*9@F3Fkx2_VakR1y9;EkXz#dbR{AecM+|n7=oBAvo_Xp zb*^(v-W07&Zqe#>IWz=XRWE~vAAFZ-*@~x+%O83Disy@=z69l2XlpaYxkC!MN zwp$fD41ZL&t@U*v2u;fpBimjp_X-D9K{`Uqj=}v{l^%W6iA!1anh) zkdjifN0ID&ZxFe-w$0os-kXY~2M? zFQZgguDH0as5HXX${RG=1%&z*zX4fyLN4`+-ESNP* zme#J6S&A`-iuo{5FZ&iG2ecqDh^5_-E>~fJldG*Omw5IXBaU(h_2JG&lcI2PYgRll zd!-i3g4UJ!w5FgbvC}{~F4MK*m91;<{5Fs`u-Y-k7Ssc{1?31#S33Q%V8;DACO0<5 zf?3_u){cpw>z3Nkbqh#lD0*>7GP4ba=7LVX@I<-N{CaD|aVXqPPQk*;;rI1_xhIPphlmf+9|RD2 zuEZqYkyCgI62Gi03$?GHW>eY=TQCgslO)A7nK%`43qlC?W54Rz+}1d1J>4Oyq**v7 z`SeQKyPvXD&RAX_#?G2|hY@sY-C_|BZt839LL&tQkZ`YKV{Li&KKCbkA$UnRVC^ae z#ujFT+r@|`G~5BM7;p2vKKi9j@^Z}C35X@HP&w-{$Nh;}HUggWR0-1OG({=!YJ_Ij z1ZZ3!UnelPmR)63x5Zn*UKYfkE`~6%-pVET9V<066(KU3V>wo7GN&p(T>WmRPqdY{ zDcUO&l)Pgg=e?F@<+vxrMe6b!Q<_yDI)c);vK(EDoY5$e#|{a!wP!Du)}F`Qdvy+- z&S!$yE8NZVC5z+^8PN2OcI<(XF+5OdhM`3kboOX!yu8UICPl%h7!*=DOokEXC9CCm z)X&8|k)JH?x!}U{^sO3O<8Pb5M@B_|woX?P{=L$aMu|fDn;GMDm|A%j|JeW@8K7ko zRpJ(sP2egal#(A3^kOPmBcC~nPG}|g-hKS$x8JY7GVQ1SlJ+}nu>DDG&idh4i@}Zl zfA^=hrJetO{!`nfO8VzAwjU-Jy8ZkFpFfm*fBr`w5S)LB*XPea{^T{@6Mc4C>6yCy zIOk8WAA9_p_ujRwKc&u=d)_(%|H-Xye_DP1cKY8?B;E9nf8zWv{`rSr{pN=+fA~1R z|BL63K0UwEffHz4@;%{I&K!`|1z> z@a6w`=*M4Ve=|nF_9x_@JNeg!`}xn;whi{+4}WsxCnvT2*?FZ5>x4EY`?vp;`xnma zci+GJ!qNWnTYotCGu+vL>y}QJ=hOuLPp1B}Z@&5Vhwoqhwm%~O58r+H`>#HJ_pamp55IJ{fBgNoUw{0Qmp}gfZ~tT+@fW}T_FL!M n@y&gG@tfcOuaBLD!~On;FTeX?PDcvycz*XsfAklB^_Tw}g{fF; literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Contents.json new file mode 100644 index 0000000000..e12d3b5fd5 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 16.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Icon 16.pdf b/DuckDuckGo/Assets.xcassets/Images/GeneralIcon.imageset/Icon 16.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9ab1864ed29b529ac0d88dc26866086ab3263f2e GIT binary patch literal 7715 zcma*sL602CaRuOe|BAjWAQ@0IGpj1I3J?TXyOv?thS!vDf)3Q|XhmZPoF;fZmfG*hecaCj`g_Sy}Oj#-bbItFF!orJzmz^zAb&}*Sg+%J+^hfzPl{9 zag?%7W9@s{>-B-L9eu6!T9@0g?c2Itms_bvJ4Q^a|JTA)+px{Gw%bwGt?$=6k1nb5dRw+yTDPBQ_uiL%uH(Vj z?N@JYIXoR>ulv}9{Wj`0I+s1Xv~`-cy6&T0tH3Nn=Eq zt@F7g_S;d{dfjjR=zBe8pvO`=%UasP66adqocB5sfL7Pjw{;n1XWNc#i$^!^92^?A zwlBRq;H`~C@UHzPlC5mhv8}C5PWo-_BDdKq&enD`Hiz5y{Te99ZQHM#u&u}F9ILHK z97{Rs!sncF8*3Zeb-k4~5}#}~_GRbna@(wu7?G;2&*Zjklp<0BHO78ev-GX^Vy|`^ zZR@S&x%F7s9@vhhN>SE1heHCxwk~z?p&aYF_k0z1MpAu2<)25B-X)+|*8L+JtjwTPfKy=)Q+V1YF_N6#E2hz0E4#8IQgwqH{kLy*Z#%^aG@4 zU9Ce^&MY<|jA>tcSMJNLx1)F$|A1rJOABDQImCv4=%cR(v<5|W0TU+h^-vyOg+)~5 zbU*M1j8=fLhy7OM()v<>xmC~3{m@lYzcG%w_!I_PrL`rSYO3YD_JaaY?7%+;ll{1& z$hR(2f^gJN{*`?eW-Ms{SHxq85ynopYe;+i+W+) zTdQH5Rq@wFI1W`Hm(vu8!Rv8T$>SstPA_H2*TK5lnDi!EG8|HtSKM(PPv%Nui4rXY zV9|$lbTIQJh$Z!!@0IWgffZxzsWMi0bLzPkgTOf30xWfYQZ4H-VN=C8kcOLm*67@` z?R5icRwx|Ha_;F!C3wLN`1+XitNur9;T{xXCv@bYym7s?HeiFF$4%rL3ShKMUTt}J z=0LA8?qK2*zD+D`ZJk^L$_}S%KTVJk5AM{fV8BtF;lLqbDdGijs=*kKT_}SD8!FXl z>VZ>-V#RZsoB|Pq6Iu&#Pp50zmi@L{l5nQ8cV}OL63Mmf$7RlY_-}ODyUU-#3=IUk zN08Hyw|9v`k<}4wljwVui<94qSvs)lyv={>gJIUzEMlRHR$+ zC2^pcTCUy!gnwV2Fz5c`-(4Or|Nj4%Hn3SnnZ54N#}Grb4J_%+i15n~r?$a==@gQn z3yGH40$ZIBV#J*lUBKjaCMzw4P&k-sB?Tr4ba9Z}G#4CHzSQoI8qL!8( zL6P=#G@zU-i{7(^_7tctl>$RE+7HtvSz#KACGNdwrLECEG0H$y+-a>EtZ5Ue1lMM z!UWJLk)!s~-AdC(vJO?Lx7d`P{n#KEo`Jy*8)ktH%fH*+bx1L$uypjO;sVaT8_wx@yvUt!@IiF3c)D*TI76KnDq;!!YbjFa zC18kX<{gAq7Ru~rn^B-L@Sr;raO=i&MwyD`*&+cYuIR7H`$t9AY_!R|>kj`<{|EtC zVsvA~tS?5jA+Rulc0+YjrZw4&$i_AE9H?U%p`T3}CQW5sAxn40y|?JP=`^>5JruVn z(p-w-wv5t+IwTz-@j0AW1~NLa8(9d|GI~_B*l{RtR@ILVu^}F}aWvbPPEkOBGKe!( z0Tt(^L<|ISOL47N%+8T|&hO)q=vgV=Bt$-uVOu;(r_E>2s|dnYa%d+SDK|l7LQI{qFdY^g`I!rev4HX@ z-i~wvQr52Ugj4?u#yw8alW=%dE2ZC4^AJpGh$ew&HZ(yx1CvuQx?dl>v0rLFLV(ee zMvyar?gu?otZL^RA*)=Nf+HO?Bc|C>OifTaqcSV45<~01(%)(Vf(B9dDF;bjdhO~#5-A_bWzkCX(f0ti>VF*Dk2{LM*@Wzfr@TtqkyD= zUm|ZXN~u#}y3B7Ue@r4ZK8;bQcZU-DJdtJ{EVve=b_H_D<(4UL$Z7vZw@nkyY+em! zUgsRzp&qPLiqO17sJwHOLw*F-ic4$3-`41YhJfy{kqzf82_@%tO)r~-@k#wamglBa z$n{pNQHbiyl?z^T)C_*ybfdG)`EvUbSPF^~NS@W52Q6}8!4|}Bh&3F6(N&UgNR8_; zxe5YCPT{=Fm$6k|^yLXNt-+=Qsw(!iO9Mb;k|Ls+t&v^|PuZ9vaV=Mq5Dr~EXGPwa z@|0Xt7AzQkp&BxDkbLWrc3jeMlNUJ4q-%Zxk&ILG3(RoU^Qb2rB5fA^2qY!!2{k%& zv1pcgYuTfGaW{)IHB;gckvUCnubCJ+>)9?)Z)DhVP=~pDe|gN$MpgMe@8!q3kH75Y zN4)bFzku)44}LKJ!w-S4{{Hdt>BIB&cfa?;puboD@xT9de|`1soA2*m4F7un-P=|9s6)kFQ_0`}*ne!~LT-BA8FcZ{NIU zyKXP0&wibZ>C>;4um1V%cU-;Z1h20j-}Cj;pFLZD;{P+tXFKUCpY4GxEStYncJbO-MR${Oj|bn%UuU zcPV7;eUL^q)75o3Rdu@c=KZ^GzVK5W20yxS`;UJP_Tqa8KFg+uA@4z&a;j8 zMRDmqq8CZ^-SbtPGAfC1m_n{TSJsi5?gAMJQ;I%RGuUNslGt0R85cq&X+ajWLglf)`0tj zn$QFLttjQ8pdya9lI4J2>B_}nsbszqhKj8&I>%aGl7XCXKQtdf73TQX027XthBowj z_GBmJFtyOLg4_FIxf*@_a3L{pLJ;c~DU5n5WXQbir4RHIN}fP>Haj;}dyt zOKmkm26E(Fb){Kth1Ltp!&GZ)VCQzAgs57@8CMf7w^3D9SS`O?B>fd7nKA~Dc;8&; zSRnM&noQPO73#r7CXE3*y~`0v23RHL!a7<)0Df9`XxT3)xHTZnyV^;HI!D4cqJ&4g z$hbre_QQZUOahsA!=8C1cj8Gv^5n>zr4Wl}NS5Mbm-B=zf-!0qKvA=4YT$SQsf)hO zGV(zuq=r(YdikmV9>A~{MvtY`=d}{BL_1DB1iGjau;#?ap_hRett-PM3fy^>BNdWxrbl!FXX#1W7&QArSrDKsjRDOEH& zHEp5`Gy#l`%mYP*v7H-Z<@70j{DkY$i}aIH~9 zuF<3B{p%m%+piAyGUSicDK1unI9SrTNhDv&-d`c*FJhvDe{);w_?@vol^9 z5`EclR5FxE!ySCn?7>4Js-@CCMKPHDeryw6xf|M|IIbf=RDs-LeneQ2rlALC)zf9i zN{kpgR0)j)o&(wKumK0bl8DchS)K|!t_qhO8dV`462kWwrYK>ZXcu~80_~B_0yE0A zb!ZA^D+ypnqX{EB#VJ?ltQzNqUPrG|t3UKy3`F}!*dmReOwt|=fwVOU9Yu!FHQ14e zW1}@N%Nv>llvC+FLZBM?K2WXM8k*I97c?KIUL)oZA{sF-f#gwf47OuPVxfSiAavJL zGv6Hiz|dV>f?3M5%s3#8grMRqT-wTjSVj7bJdaeu9GD2+DFlk7LR?83mYgmk_JJAj zp5fyuShS04G1|0-=?~{T74-AzU;|(t<_SndTm8~*AjC5Zgii|z^c^KE8~dRPz>2Bq z`A7yVblfO0dfca~JM(lt2b9K=RRUvScC_-m&KL{di_Ad_XsD18F_u|G^g^8b)Ur~X z10p*2DWD|%OcHXyNLg)IC99=->liZ!+E>YH!n$tt&?0*d&weGapPa%vY0-ogJ9$Pj z?lfl_hdMBzm)#Ce3bgR?EUEA<`q8>h)$Ay6n3t$EgIVlsmGF6BT!7hmoxOsg0$#et zteqH;a&?!T1$&ak&b@U+ie%B*OsTZ*Nfrx^{Zm+vVIi-V)4f)}6rWvuK6Wy7DEQ&Z4=VwXwmRM?D3q^Zz-yX|9J%%TiC} zN0k^g(6cBTZ<+gyDvqQa!i|M7*wn{}7iSRribGMLbBWs}8)77u4^z|`m?HsPMv|_l4vJ7G+Xf*LHs*T9Di~vQ3^DUKYRm?A+eR)pFeJCc zV9Zel0PD>8-K_*yE8fO$vpwnuW?Hy)9j@}fs9mH6GVYLF_pT#v28eSAYtRiU4qsf z+VCq0IVW(Mq)LdJllgw&@L?w8+y{XTcvfn#v20bBA{PU?$KX_+fGR|@^0d6wD1c!IKT=4gU;Os|e*5$|e)o4SsQF#|?Z5xpj2G{(K5iDlpEn zG=8U0&Ao1Y@UZe(^!&8bbh&xl{`hdcc^tW487~)gZ@2ePn|pS|`&Ib<>H*nE=uNbD zOD@rRdw%i9n-7>e%7e>sx6j+{kF51a_CF%o(*z*)G|9oob;D=J-(Edk-EKd=T>8_) z=KH~ob(nqCpCd;%=n~@PCurz5a)Mlby7Ja<6>-8i7LnP5ub2i0qT=>+Jw06A+-@EQ z{yX8~5AO)aXOG*v&FAB1Hy=KqgK}}X-9C+S*rI;%?aj{{thfJGxO#XpcbGVFc=P6W HfBEKr6W-}f diff --git a/DuckDuckGo/Assets.xcassets/Images/ITR-Icon.imageset/ITR-Icon.pdf b/DuckDuckGo/Assets.xcassets/Images/ITR-Icon.imageset/ITR-Icon.pdf index 1ff6a4f6db7f5eb49a1d732bf7d6ebef1fbc4c10..71e908133d48e1c57639464825a9722d736aefbe 100644 GIT binary patch delta 143 zcmZ3ea9(bL_{6h*o0S;NStlzo>Tm96>tX~l^fvo(D6xP=mhr?hG8>s&Z06uAWb`vI tHc>DD0fjs+Fl}ILY-WKWWNC;lWNLvw@_h%c)h-BMTvWGNn%N=f{l%WzN-nCN={TT(6cZx00IR&J1zxGpzmr#v`S;# zD$P(#)^|0;trVfs&TgWZKO0DKdhz7htOgsWaIgZoMnLZ7*{r)5fox-ll>X*sb|n@d z-vp|3^A+xRMrLCJv(09_g^YeihGq&NPZ}uXae-+AV^ebrbRjbXLv$f?a}#tS3zNxT Q`2smD4a~VzRbBnv02LET*Z=?k diff --git a/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Contents.json index 0223097db0..0d830310d5 100644 --- a/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Contents.json @@ -1,12 +1,15 @@ { "images" : [ { - "filename" : "Gift-96.pdf", + "filename" : "Gift-New-96x96.pdf", "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true } } diff --git a/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-96.pdf b/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-96.pdf deleted file mode 100644 index 41180d6d4063373a4e87e2a6ae4d3acaefac519f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9684 zcmeI2%WfUH5r+5u6umJp6F{xzwMk%LAY*$5K@!BVvm0cg9G~$x&?S*Hfs?1tS0uYg z=?I9t$;!uo-Ts>_)}2*kzj*ulhcmNm(`4p#zWLkVr+NPFyZQXh=ldVu-(TDx>;FRe z=WsEvi(Tb6zkJEali&Fp|Ll?W*(2SvN1Er1eECwiKVE+jaVPsg?mrx_Ki>Z`JHU4` z>hAvbcyTqIzdru!)8Y8;$M5G?@5lcg?xufE=WopX@oqwoMK|kwww^xe%jB0W6dk%; zo}!r#SJS!#CqGpD_~GtwJ%uIM6q23Y5@oWPuO`2^&3Na*QkLI*31$Ld0?VEmTYK=^ z++GQZGJFLQ0yfQusggD8To>9bwA>PSncJV9& z4`JCLWWsD0Cmav!$vh*H4SZdKtuo$q?#+@!Fkz-iXqE^N<1Sc6o^|msYD5oBUzcEJ z$3(Cc(RE>T?Sc(RdnL5mqY6eCKwuLwL@>0h>oviI2j!~@hRrU)8uwK&RH+iSR|%x) zp%OsvqMN3#x)3%K-FTK>J&TKAU@*9|qk^>u6+&$73OI@%s+Ox@DKxcV3tdmTm&(DG^g z9Hw8UpQr2T&n1HG7euf{OErQ$3g_ur5u6sAZNivg@uGf-;8@rTMk36&2#$+&#F~uD zV${$cwiR{ADPso}$*M)+3NLVb=dky^Z#R#K_$FER|62eYY3&-%F>xqXL=D~yVB_6WM_qUzQv*nl*_h6q-{*2NLRM#K=o(6X*q1uHx#UsbRY!ODFVj9jJ? z)>lcAPSZmr5JVo;S6#^047;&(kVI4xtCciYjeaVIB4VFh?W0&&9IjCmB7&*fu!XKC zKGOU~`4S%yH^Z{Zy?UTZsD7$?I85r#s;^5hakH{fh+q&^2U5WXMC~(RO|a@_Nvtn| zp=Di9{o!E*yDx}f%@{Tn?|)VRpQeznTkzK{`0EzDlH4?)w6^&2E!ax{YniN>trye! zE;}uuNm2Zs6~AUNW!vt1Mya~j2--1Smpc5>D&DD+O=Q3F7r4Yzx7pu-iBlCTX| zOdn2PDFG?}TE(Hu=x}9Nhb!_hL564R*I>N5znJ{|{AkKhdP7}b^cxGMj_J$OS4v>e zFXiEGv_`2~oMm*lBBe~O!xhs9rKE@wkj+`#sPNvF(c#Ll4p(H*g( z(-J6TmdxBJwa5rMLlY>iqoDxsIT?dy6=V??&4|S^irTsfD#f16E7i8lTep%B8iofo zmZf2l8aLZ{1X;{GDbYLFHAkt9|3q2=RjZ|T5jg^@bWn-{6cdr7gJ?T?M=jLWpvFl% zH_A(gsH^B%;Qtd{de ziP+Tx%_mT3c66(1i?b)pXpM2H?j2;0Z6g9D1bZIXDG&`OV~z_Ea3)CmdQ1 zHg>DAA~`w1RsV$-rZNjz^O4c3_4WYtMEl5qXTg-gsNfCQLpwU9ksUu zrtCnok5tZ~*-;g-trLk>)eo-SIO^EQXZH|jKH*KHBR_Rcr)pJsqx!E?9*ZZtB(99j zwB&9Fuly&lx;4r(NTqwUfrF)IOb1KWDsI+C5 zL4>$Jn7ATWk)@9%3%6G7YBI`3w*=m+X!i}!T0O9>j;L%tiJ&tB+9T5ukcQyQLtO8v zNh-}gQhcJ)=@h#^^|d=j{UzP*N^?GUwDaC6c5Ga`qr*rPFIvIXlEzaRjbQ(NLv2g`P3aGb2V_ZVXV7?nY+0!DeTEH8>g+x7-nshhMZYE zz^AO&GP|Yh$+Z$DH7R&GfNc|7sRQ~* zk~jMZ*_Z&U59EkaCnhfm8cNq(i!@RFeBRT&)q=A3Dpr zYKNql>`vQpLar28B7R3vct>^l2dMH%t+{ieGWM0ai1;X$eXJl{q^YV)K47{wv-?Pu zy;JPG_q97#O6lLU<5=}nb-Eq_`Yve7(W1Im8h%^xG(2g~&p86cn$vVgQPSv_!zPLm zssRPrU7JgC{gxJn)e&sp>o$V?(eJK0j;*v?8R)RBs8f=QBE2Q<2!#pZ|1&IUW5=D$*KlM(I z4kNb=E>e)m?O1kfMsmE6tY?$w#RIH>!OO zR%rmF$mU6o5uL@Kfs_eey1c>VfU#9jkZ>D8S2m2~Ks~y(oNETVS-gsuIY%yRVBBUG zsNR{&4M|vOipmw6UpW#c8&S;48YFHCUk}4bvc__E6kMPv7KT~VbLG|yCiy7_TCliZ zDwe}_yHY*Zi3}DEkqjN$f}bT*VN0*V)s&%65*DcclQhgL-H;QS$|=Q=;nf*>)Fs>oknkg@Fbb2m zl}g-mKd?h2fSi(~Co)Y3F$umQ7hjDU2@M62DEg`jiLrP76clT7K|=h_XA0u3Tio!p zvu$ZhjF7U@L1np9Qx)`875YB~7>*V_sdPRg4Q5*MaxuxRuM3_eCFsUIJqJ~Cl8`(J zrC@7bu>;a28tDg^>`h!Ul7h#PC1gm1569AxIzwqVt|RTY^*Hdrkp!;|+;U@jGVaDT z6USlY69q0+$^K`gYmRr}r9^^aF=J`@LPBSgd^{zZB!p6ule?LXFW^`td%`CKi^Dv? zXh;m1S1_GDXpX|ilUU?RPXdL#g!8O3c7c@Ho$3c69h82t+75QyZNWT*6(x9|MKsD9Ov`57axxe_~Y@zr;G1zKF>d4 zy;jHQL)H_LO>2#v(!1l`&9AqIZAFo+aKBU>fw->iyGyYMa;d2{- z`e#PJ|MUS?XOZCDysz`>=2zyL6aMc&21?}5fszP#rwj67QWN~u#r?(Q&BupRKi?jI zp4NGrs`&EvjGK@}f!j|$eURULy+c%ikDyEefd7=;`Y9XlYO+)ix+SH H^uvDv9PwaX diff --git a/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-New-96x96.pdf b/DuckDuckGo/Assets.xcassets/Images/NetworkProtectionWaitlist/Gift-96.imageset/Gift-New-96x96.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f4fd0b70d68b50e25d46f98792d6a7276bc3c733 GIT binary patch literal 7383 zcmeHM%Z_8W5#8UfxSIj&0CUCn127E4GjR|kK_>0&j4V1DdE9}zm9*M{ldsPyiuV*- z5;8sTCX3Oc!9FC5^=1|Ct9Ng|eQlRKO?Ec(;ctJR=J~6y=C8k=b`S66-^q>m<`27v z4^KbN9^j3vIzBz_Ztthf+ugtJw!53(elx%MZv1b1oc=l4#kAjS*1>CzIOpm*={UK) zpUmQFF}`ANb~RYAvp63yoR8awDLC$m>*#*+W~niFlV(TMaivX*2{D0!VqCVJu7LXi zbU41m9A{gXoNRE=#d@;M>xj^HDi_#(@=Gi>I}xUJ7T20PdZXLk|R zcA!b?DY~D2oPL-dra!;f#i+eA7YhY0C3Y;nI-4*EE{ABFi$#j2N5jSH$;HaRy4plt zOtHbmu7L0fJ!f?>S1w!(%IoT(F#qQ+rUrfHV)@d=RMYBW14jL9Cq>mJqdOp_UBngj z#ktH;pjKPT@kJa0ZjejxHeldg4Aq?ZLs$=yG}X@|HiRW7_XFcs!C|-h_REARBp5mR zc>atO+)^3$6WLI3k1XD!RzV(ATO7A6-Sxm|A95DS4!TjQD|r&-aw0H1`6URYmecVW zhU&*Bj(C7VB)pvzB*Xgwq+EK0O0T>qwjFZP%j}jZ(V5-;cT(amJ^QoWX!KuXHwFU9 z>E+D1!0R*-JB3@TPT+}Y9oS@$M^3q8^-o>~_oxqIbK030VhuNtceQ2(EpNcI#FUNk zvqGe?ofK3e!-!P4MK>TEPNc&?np`OnLZpGfypT-tF&AiMgsXEYCdOiB1*vK47h6Mc zMnjo2hD^fsdp%;nu29jlt{gucq3e{$Fk+VA7TwTd|5v>rxfcAN-N?Y`m#$4+es&`$ z=$RWax6r$4ugstQI-cI$T9+wNzXFYI_!~n92%?jWMb0wb#74C_tBunk;P9` zS!DJlE2lcv%MYnEtUYv<&iT~WNRQHfnriPS7tjNx7^V2_vz}K*UTpFq#91LF0+G2; zspv>&b&q1(=?ZbOpi~Gck>Oe(4GylXCp6Y-FY?S%p#WoJY6?I{5Z&DLTvk1v59Eo$ zFUdrMb|Ng*xm@imS75c)nW9)&oXtK5AjP=sWH~y}Xfg&dl~_G814!7KGdkzRq5^P@ zzzOk-_qo)h``l7eO&&Z>uuKHwa;t`f2ZA)ix8)6@6zO(_eTxU6rQq^S>w`u^e6+R} z-M5{PfUM0X$;Jv_31(KvE~%E9iOsu`KsHoUG2M_m| zUn?rP%qQy`*3VIFU`RnSuJot~J($oh4zdXDK5iAb8&Z=f)`leo?~8|73>+s%Um6-r zbu9v_@wthNm@C!7fGwpO#xt~qjdLc78oAak#o@&ujOQTaaO)rZCyU3Gq2V zFu6UEL2t!{FgaZPL`PBWn)H-sSZC*1L*^tS;3wIamnB9kbK|J9iP^RVd~ye9Qd~tr zlkzG$aC}XQE^mMv=}M{wB&h~fAy2uYeL_v{&N;y*b{CeQ@*1gTiW*mYjCw?G-G0y1 z*gh0A6H`v!^lDZ@((88ih)K%s+z}(2q(2~?4WZS|MSg`AvF<&TZtzV;1oAj?#DF?@oRCuD*k5`*tKRg_sj`R0_#m6lCHh=l|KX&ux-R*~6 z2mZKwe|P)M;ph2#q_3`c4l2@M%BYNHjQZqC>1KC4{Peip9cTGmcGJndIXpb=9&n<3 zD_g;Lw~xSP6w(bEzNiVrFKwIO-@ON`vq*3=ujjlu{Di&sg8w6sff7ovfszP#obc&!^SifD#}~)L{qD>8i@Wz4(1Yt$HZFFp)^>_CzrJrMiIUer zzjQF@CvwDj@6C`;ZmwTHCzF&SZFT?4uS)9|FZ9cos@vbnU&Tv&^Y0{`!@^db9kuJ*wXo8CxH1r@`|!YVY#&&YH-jkki`-WlbndAz`I2 zmP*7R4};@&kJn@1DPGjFhHOmH=^3W?TpPp zoxWGWM2~1$d&9Qa;B+)$NG6yPJX4vGWU@tciPdHi8IKQ?j9@d!hEuSVHG(+e$eQp` zAMhzawxn#GK+aO2h`zm<$8D$XU#Hf0ZoU{6G#xJfLM%^bAdJj~!MnXrZI*rkPz(QuAF zdC5t1WN=RakWCIOSq@|9qO+YlBb!-eY9UHRHv0+OcXK>W9;PWBo1n( z&L}%8sEw{fUq?f+;VoX-JiHw-hKX|kZ-|LC1Da89CFdX^8yErl;1LmJ7Bk!dBLw0S z5D-G8VoQKuxS%UA;w2L;dPr>)#9Vs;^794Vpt>){7~nosallHj=|;pAc2Nw0#AV%9 zu?f;U;v8Q}7j^I9+qR8ns&TJ1=%bVtbqf;5 z3`0n@Kv3u4er^>=FGhj{XV5J7l<`vS34IXFCQiLIeuMCq;5#0tWP?sO=5*Y~WJ2GjvK9Zb=@;kWe+T zLO5!`#gabR_k~6xT{NY@&XPS@pIxZM`WPE#kuY9o$tfmt&>7KjYGbXZ6#czjbx#~Y)EnV#Y z2xJinEpHKNNb_oQ+T8bd<5Efb__eT+^)6VY9pM4hmP?)i>9mj^=Bd?!nC<3(ggAISW~SM0d$2ud;M4CfqRM6W zV+7n*+O0!(Iw~VFp8trfN3Wi}_)><_b<(?j`}&uz@4x!0Uwr@j`*pwg;k(zrtXBtU zJd1DEo41E|y=UXaM0&Z~et3VzV86$A#xpWrB5$|c?B6eU>&@!-Tkjt~oJFVBCi_T8_Y9NA~lU(T!fT|I3|bK|%>EO3SX)e!trA`mUrql$x|>#62hmXBB_;iJdgTntqH|?A}Wb;pQU-HHQGvaB>%`N z*-BH1xy-~k?hBFnn)iKzHG&Zcwtc~uL{+yM#B&7rL7vy3h3>OgTN4Q9YSPlA)Fpbn zK^?L!wMg|NAhUoJRHv$_1in$;kffI66idLPn0;j*tD<#H^-IQ~C=AI*${!qafT#f0s?VRREQcHLqQLQ`vI9G z2QO<8x&zyz#GKUOHP;#oM@Q!aS}WQLOPqz$H99cv8d?ToNrzFzR8b~GRd%98Rj;;^ ztph2asPb(PdQ4Q)RDNy90@=magkqB#<`w5!Ap<8}&GEjd4~`}&nv(+>Jp5>uN0ij$ z2~sWF6CuG&c9FamDDz_@QH-~>=oQcLjF0pd-ANbm9tVaY)LfF$LhwQ)q2(qOi{J+E zeWhT=NXI7Zj2Kk|xiKb0O=bxxm2eO=%^W!3rqe`XW-=Ro))u1ug=0pZOp}C1$laWZ znJ>kMQFE|9IMK=@w@Y*6tPlt~bGBsEmQY>ah;t$_tyCmbN>C#}{nR);uEN z)o6T2W^l*@h_a$%(Vw+bnL79`1rN~BjkWVy@zG`@p)WX(Ms6e=-&|9Y)}`rOC9_7E zJTPvEM}3?l)QQdlq!q@iUlX02VE|W4VpLEPF3^=Wa+)`xx9DYD>9lTfrP`qZbxo)x zW`O3RMagC62#fqsfJ;CUd5#=Vj2svMGDCxsJhKOfQKA)1R2t=5kW1k3a+xNNl0YjC zQ&26cogY$3#L`GY(kO|XBx_-%FH>49aW@&L`NFj0D;1xqoy@6ZJ3~@#R+-|eS8YkU zOe$72PrZ2T*u9 z4sFa^UdtCSa0Cp$5^TIq>fmR8-E;5;Tuj2a>N73FPuxr@Hu#?Gi)R|b$qCVZX z?Lz$nWuuWk-Fu&ZJiNR-EDvjY|6J_f<+scIzil;M+4bkG3-;+AetWfe*=x#8H~HJ| z?)_SDh5!2c{k!F#i#}e{@5xZY^j%@LAV#2*cV$7dOhu!k(dcEt^z5jTI zb3fZ}Z`PmppIyEAe01^RV!Oq^>OPCt;_~W`wfX|*?hnh|!Mqc5%iW_#&%b-|A76w6 ABme*a literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Contents.json new file mode 100644 index 0000000000..a17c70f1fe --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 10.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Icon 10.pdf b/DuckDuckGo/Assets.xcassets/Images/PrivateSearchIcon.imageset/Icon 10.pdf new file mode 100644 index 0000000000000000000000000000000000000000..73ae44c94e97ff7b24e0784b029aa1d4e59a5c62 GIT binary patch literal 3482 zcmeHJOK%%D5We$Q@Dd;?5Q^^y5EzJ`DB2*d)kA=whxK}6E0wjbc3n96^_}5zNlJ22 z=UyIc>f7NwzL_EC)#d5=TfrP5f^ynleT)$;Lrj7HD+anK(zMBXFJr249rmB`KUK!;J|wVx-qjQHXV>Ni!k5lA0$!Gk@=gm=NCl+~VtpB4(jYbldBcx7gvCI!(? z$)xhe*&>w_Y?$-G2RqZjA%x_4T?Wht&Lcs}a}9n%9yN#hMPBy!Y{?I%F*}1zq`k8- zkDNiJ!va(Lo0vzzj91nuaM&5oVFQW8bQ$9VmCObOIf0O!QQQbXlI~n@8L1j2W7#PK zq)S%{hSM->su_}ydF`wKcjg|2OJ<5DTg2NY1z0yqMoUwOtgm2|-2yr$gK-)Xp)hvK z1(X29E~TKLL;`#QNZ?^OpbZs0Ce05ZfFguzzq~xeN+S^ zEuZvR<7JR^z5}D6(!%kuiwSMDMib@mVHVd=J`hXHeZ@5-H-k{L*p<+fkZ++J_gJ-i zTxSHcYrT~LbCaZU=N4557OVaSB9)ce8PJea0T=rg3ly)xT|ALV$fqza3Va@yyn&l|jI@{cksKs7!y zLQH)WbwQCQo$GS`Ijrcx!k?bMXVQ*oo6;_o%vm=X#ZfE`VR6d(1l^CBF2m5-(|EBQ zo3V@E&qVyU{Ll=4#@#f>;m@6^s|;tmyTt{?l1t;=?ze+q3z+NPuOIH3|5iQEZU;IL zNW*514XAJxO$@zg=vx41w*y%KwWmD{DbHjH6;4^wHk?IIH>>7H2UpZoW?(FfdGJ2zH)FQ}i^#wXUgCT`(vYBv zCxoU5(Ai&BH{dG82(IYt@k##(XT^d410aHYZ>$bCKZ_uU)%dUf+=qIhxD_rNdG7xT4PuD*B31&}+8 S&326Jz;GkTt5;_q&;J1ev)Ri4 literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/Contents.json new file mode 100644 index 0000000000..e708cce2ed --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "QandA-128.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/QandA-128.svg b/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/QandA-128.svg new file mode 100644 index 0000000000..4e2ca57aaf --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/QandA-128.imageset/QandA-128.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/DuckDuckGo/Assets.xcassets/Images/SubscriptionIcon.imageset/SubscriptionIcon.pdf b/DuckDuckGo/Assets.xcassets/Images/SubscriptionIcon.imageset/SubscriptionIcon.pdf index 934a29304855a8da122da590fa025cc53f443972..d9be8eca8e50aae5d9b27cf4e2e48715bd2c614e 100644 GIT binary patch literal 4808 zcmd^@OOG2z5`^#lEBYe9UO>%yKLA63^@QOytex3|0b>soMN4DPkXn*j$ouQ}i|S^R zoYBJa*}SMGx-u&(GBT@5k6u52`PdG*>uhiO?VDe_zW?T%e(}%WKdk%3Pd~o-b-g-( z@mu_Ty?J+d-#aE=P0*{|_Tz^$gZB6Mt=Wu**ZA8lH~SCE-Fmb7{o?zRCzGq|k8cib z(U;rZ-Gp7O_uG%V)q1Zb=9y2QZa?)u_hvBdcN*AqgifP>;R?(BVYgo1b&Kcg|J6=5K9Sl_B|<+bmN;@QEcR#0}Y3*{!gfFf-VkGT3cr?OfU$1berxU ze_rttjrA_tFOYm1V$8{-kcetbFb>V9q552M1FQ2PraliL)l^bX&03dSsR+QBg4{$uqjV}7%taH31#34LXANZpYGQ2RlZC{R&7-m2R7M^(krm_MDJjDUKQTt1WgV&eQZupnA$aRR)!S^07R*6sg5`!e<3xp1 zSE@msLNlYQHo#~MH7dwBgz6L|-%>6C3*ur0YkCI7kO7$@DG~)y4n~W?hbrBaL>?`n zlvpfmI~#*7G#DmT{I9x7SAyUm%x8l)5N}vth>fP%nNn=)RuE2Rnw^1JBVVLG#Xxf) z!F1RxOR(VmghhI2!^nWzUs2Y_I7~)64pn6`mgUWmAL!5Q*Ok=#y9^tw2?o0^%a^ao zTYN<}PS;_-V2RH#P)k$>p|%Eu#HZ{S`XghKi5a3R#hZy(XwpgHt@jiCp{Nln8tP|E zQ(|kfM0_Hu9_&jWA$o0qlmr@M6Re@yiegI@gQhL)Ds;QS z;cQH`4OF7|_9#4M*VL+*Ok_94rrt>#~xJ$k!p+d1=5uZ-Y2kTPrgu?T0ZkQ=ET2R1yr#|!U~M(L$s8X za)MiUVP!uCQ))?#D(|gLmiP=F#_BTBBR)(iQ*{8Uf^yC%EpZmZS(d5#EPey*A)Hp2 zZYXpQ8i9x}0Zq4htN2hg4#}mYqGle=Ls_BS(`8y_spG);f^aj(DBUq2+Z<>u$~B%0<-@T+ zj{=Jj$mS*Pkb`Bwq2ste=c(_bVx1#k$#OqXy+`!S zQo^!mov@-FFN}#J7E5V+ie$uT9*sX%+KM_Gjg9UjifyL96!-~-N+e+jA(&+Ik;c{( zX^^DY`H^ipR}*J35y7;SptS~Rqf_co*kqd#gZc`TbCny5HgOE-IFuABtyLO3eo zXz3Ya*2lg>Tjjb2^q?iQOUmEcEu~v;-cHwdMtY)jdV-8^6<2H0ga*?$zw8Oa;q+I2 z!sx)!uN_Bi@W(Hh+2-`Lx@#AV(bEZ~!sch#Y;^mX2htgKvbdcur=Ll?(l({tyE*G| zM)h!=o~-Rn`V*2?rcY1D7oQHVt`Ez@x_y6P+dtPoEcd^)(fI1spBHb`(^K|xw0PA^ za?@k@^4o*I7QBMLz5Vch`Nyu0FWm<|1WZr!c_;tR-IFKdwsCf>k95J*^qlANGcxo= zPvixhz1w#SsC~ZW3GtaM$>Er#a4Jo%U1z2F?Pjw*?E9bfI6rOa_43_%0)AM(y;=U* zXTCh0{>MPFIQ}=qv+d@v-msz=ID)VBzY&L?%VmSkcoH=3K#KbQ<}IQ2+QC(SjQH91 zBewQI{CkiyPrA3AxzY~z^NOdPKU*G_x7&A@wx4$EUpkt*?OKVAb(Xkx#U6b6X#xHE z%>;2pq)hHXnVeignYMc$;zYWH0?(`a5C`Rbh9HGN*d2_qob)xsX z=aBo=etWn6y8r6t?bjp4i>vJxennr)*W&u-ziV*;x%E4l@s5R@VMvC zm!E0gT-dTHJXrtJyt&+ea1rpFiPLU>JFjl0lb7>9SL^xw%?o$>_VfSNyXo)gP`C5}^mWZ!~V~8*Lq^Tq#P#;T(Q4I@GdTF*=ea|HlKd4wL%nZKN5G#p*Nz8$` zB3M-AZSoqWF^|>k6pC2fj6{PjQCN^;Bt?5g+4qLpUgZ#NKD< ziNF>`D>2uRZSSoVM!^SIY~ySgB?}eTeB!l?V>qQmalC^PsI|CSQrKaTG{~SV@;b*L zF8G*R6?jswS&PDhPtpVAbXe*Mj>V@WFd?*hmiBiVwYO0y3FnZ&r{stLX3*R_l!LgV&Dp55|!Wlf+iFqWAEZ z%5ij1dR8yxG))Ur@DKGo+7SB1# zR}pf!$NYpa-zfO?^Z`v#h68O!m8!kc2kt@Ns7LDc+N!h!ZC&BLEuzC3<)QFRZD|fT z2!e#V>wyr2otFZSn5meD2!h%uBd!dmG?C^DvM`STsDjaID3VeeJ{biH$|0svgnC5< zX3_|(r~~b(i;RMT%b6ROcCcbNvHY`)=YAC>J+FELAZWyEpQFM%5b3wmI3bfit z{$B}Tx>O$yx-1eTWVy%CDkRhxMZ^{P=>GRjnN100?Kz}+;BZ|Q|dJg0ZIy$StRE>-V6(`rO=lbr#I_FMs=zbiTVQN zM@^7yEX`QJD#ygb7aYd5fVD=;MB{8~8)R1tvoHg;A4?OB6?V-ul&A$q0b55|oJFUm zJrsh}g8``(E3VE3(Jj5DS4w_QSn>cgfp*zI-Go z#yH;n=?L4m!}pubcE5A)e&<_)=j6A4|C-&&+3IpW06)(cSF0D>Pww4lMi1|LyK=`f zF4bjpx6}E&+kU)V&pT&dE9ZypPPd!=yx~Oq1i1sxR<~e|mOLOnLGE%e=>9|J$EyoM zb(X=oyF2G}`;oQ!Rs1`U&vv3`f3}k)xH}$rpZMu&zq;ODK3w|gc78vJtJ9LJ{dOhi zm>uxpp~Z}m+Z>@voc0_cZbtHX2$|2nfC85O0*bVuJ7|Bqy1JfkCsXf-m(cNex4oI4 rxW`u)PZm;6&bQmWvxEo1CvUI*n~8pW*{yE(BOQt}O^+VE`sMZi + + + + + + + + diff --git a/DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Contents.json new file mode 100644 index 0000000000..09604788db --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Icon 11.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Icon 11.pdf b/DuckDuckGo/Assets.xcassets/Images/WebTrackingProtectionIcon.imageset/Icon 11.pdf new file mode 100644 index 0000000000000000000000000000000000000000..59f2624371964e36da805c6753e50bffc07732f8 GIT binary patch literal 4673 zcmeI0U5^~a6^8HoSJaIqh(vFFo~n`%64)V%k|@rSJH*8@9?YWgPO`H^gkPWMt?ua= z;|&t`j3ul0?W$Af^F62f#p_pJeiG(7O~HA0{Pu^*xlccJpM5s%AKvKCDPH3@f89U4 zefr)dz+0_4Jw5L4-c2{J_W!xx?Qj3|g?stc^22 zLvg!zlb=iMIiSOwQ%IF_=i1HJ=1#i@UcWCmI&FzQ@b7%s*y|(zgydqW@K9Rlz4H)~ zb)VdheKlG36)c7ls=31yQ6*XGF3chL#;F+*xy1z1di1`#GW%LW&Mu*1+J`uIXn^1fOE3N?g~$Ewl_UfE5V4OQ-79Zrrp$(R~aA6tW=lesvz zP*OoRq}Ak$u{F+MISzSfoA4j;OSxk+>pH@!9N*9^u`14%%?~H}*$S>?9g+=aDlRD* zF%r~!i+!d)@C)}1&Te3crZgtR_r{2%mIruGwy6{_AXpO3{SSf*p-$aGllg_(EaJ5>1 zE-v6E5-6@O&!+&b)v&uJ;#KhB8jCTtqLNz=*vx^97CkmZHMlKb*ad$kIn+g%)N2NXSc3D? zTtlxuyGHx5s+NJ;Mw^L138I58fpV|{(eBvVbIO560{g{ew1z&@fa=PUq0(J|da;gz ztje)=+6+-tR}8UnMx5c-(g)Xu9z%!z!RQD|8pv6l5E74b3axV*yJ>3lKq(Dc>80pE zj-+6IoD^OsgjWNHaWGA%)l9pHv!`3&*FIM|E)-3} zqixXCh)1ZNHb*0|KvQF5ojXcmkeW*>^sfV0^XQhr=#weN4a55j7NnzARkbCpBH|zJ zllsuCfa37IDs7GyMR%;?MmLsD!q$Yx>wj1j{H$(l+y4-#0B_xBZo5j*{O zu@S!>)WC}ojk6~zPQw0z3s-LuC8~(g!>*mmR7Hvs_Qm%-I}sNQLe7i`MIt0C0-COo zG(5Oe@tAsu3)+-0?0`NLapp-5XR3nCe91~HrBDkbleTuFR7U1A_|WCiEmKk?*piYX z5tsRDWHGtR{J>U9#S_8-GSM}nszD_(|jXrM5O-d~QI-oT&p;s?1-qwJ>rK>~Y;k#HN%VX^{3Pv$CyEoMRrOj`vZUv8>1_Ng@tuqdl(Ax5Q*+C*A_Ql6nD zQR0%6HfpJt22;aUm08UgZ={T;n0jS9R(Hh=$TBmZARk0;3j}oyJ9>d}#>uS1;V6xE zMIsyEFk3Mr8q{~- z2u+g4Xsno-RcaCl&Xn9tU2C=$luJh|w!$hccU1)WNoJu6XKA(^%mqDCwd7qTofl~- zB2R-fg~AU`U@JU17>)-@9PO0tjIv5uc;1Vxn3+k$IM`7UC07MP9gX5tYbNfTO5SHE zAGxETqzH3yBpGxnZ<(o)LXb%$g@odSqXIVL<{SeSq^x*hGXC>;Qic*1Wh~x>xV`+r zl>-_E2}vA-bJ>JqQW|DKw!%WVxe|Z{Du^OHfFY`p7HF98vXUVrN<*uJP`_kWDY^+Q z`)D&t)e^i$GCIli}p+|+E zae%fU&B#dx2-*Q-gVA5d!5#C+$V$-g7M7qt{f#Tr@IDk|9Hwe2WD)Cfw$)3G>PP2i z7~yJm(rtge%AfE3y5cY219ZXMJ_pd%E}sc*K7V*PKAqgx-|*Rh-_1Y&`>(ybd42bG zzXE^Tzq!Br;`o#Mdh`*EU6+TzMRxq$<>@Txc7Hnl__*7joIXt4uHs%EAD;FPoJhOf zz}I(=V2(aLpsQ~eg0^oOH-EW*gQ`vz+`98QFONU6H+-P~3FJx=?e#6{s35Y6%f zNcY)wi*|F+K-Pt TcaKlQ97-ZgFJAod?_d58P79)> literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift index b43155f629..c43f7dc27c 100644 --- a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift +++ b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift @@ -41,7 +41,7 @@ final class AutoconsentUserScript: NSObject, WKScriptMessageHandlerWithReply, Us private weak var selfTestFrameInfo: WKFrameInfo? private var topUrl: URL? - private let preferences = PrivacySecurityPreferences.shared + private let preferences = CookiePopupProtectionPreferences.shared private let management = AutoconsentManagement.shared public var messageNames: [String] { MessageName.allCases.map(\.rawValue) } @@ -209,7 +209,7 @@ extension AutoconsentUserScript { return } - if preferences.autoconsentEnabled == false { + if preferences.isAutoconsentEnabled == false { // this will only happen if the user has just declined a prompt in this tab replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection return @@ -240,7 +240,7 @@ extension AutoconsentUserScript { "rules": nil, // rules are bundled with the content script atm "config": [ "enabled": true, - "autoAction": preferences.autoconsentEnabled == true ? "optOut" : nil, + "autoAction": preferences.isAutoconsentEnabled == true ? "optOut" : nil, "disabledCmps": disabledCMPs, "enablePrehide": true, "enableCosmeticRules": true, diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index 6994afdcd0..db02dcc60b 100644 --- a/DuckDuckGo/Autoconsent/autoconsent-bundle.js +++ b/DuckDuckGo/Autoconsent/autoconsent-bundle.js @@ -1 +1 @@ -!function(){"use strict";var e=class e{static setBase(t){e.base=t}static findElement(t,o=null,c=!1){let i=null;return i=null!=o?Array.from(o.querySelectorAll(t.selector)):null!=e.base?Array.from(e.base.querySelectorAll(t.selector)):Array.from(document.querySelectorAll(t.selector)),null!=t.textFilter&&(i=i.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const c of t.textFilter)if(-1!==o.indexOf(c.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(i=i.filter((e=>{const o=window.getComputedStyle(e);let c=!0;for(const e of t.styleFilters){const t=o[e.option];c=e.negated?c&&t!==e.value:c&&t===e.value}return c}))),null!=t.displayFilter&&(i=i.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(i=i.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(i=i.filter((o=>{const c=e.base;e.setBase(o);const i=e.find(t.childFilter);return e.setBase(c),null!=i.target}))),c?i:(i.length>1&&console.warn("Multiple possible targets: ",i,t,o),i[0])}static find(t,o=!1){const c=[];if(null!=t.parent){const i=e.findElement(t.parent,null,o);if(null!=i){if(i instanceof Array)return i.forEach((i=>{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})})),c;{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})}}}else{const i=e.findElement(t.target,null,o);i instanceof Array?i.forEach((e=>{c.push({parent:null,target:e})})):c.push({parent:null,target:i})}return 0===c.length&&c.push({parent:null,target:null}),o?c:(1!==c.length&&console.warn("Multiple results found, even though multiple false",c),c[0])}};e.base=null;var t=e;function o(e){const o=t.find(e);return"css"===e.type?!!o.target:"checkbox"===e.type?!!o.target&&o.target.checked:void 0}async function c(e,n){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return i(0)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,n);case"consent":return async function(e,t){for(const i of e.consents){const e=-1!==t.indexOf(i.type);if(i.matcher&&i.toggleAction){o(i.matcher)!==e&&await c(i.toggleAction)}else e?await c(i.trueAction):await c(i.falseAction)}}(e,n);case"ifcss":return async function(e,o){const i=t.find(e);i.target?e.falseAction&&await c(e.falseAction,o):e.trueAction&&await c(e.trueAction,o)}(e,n);case"waitcss":return async function(e){await new Promise((o=>{let c=e.retries||10;const i=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&c>0?(c-=1,setTimeout(n,i)):o()};n()}))}(e);case"foreach":return async function(e,o){const i=t.find(e,!0),n=t.base;for(const n of i)n.target&&(t.setBase(n.target),await c(e.action,o));t.setBase(n)}(e,n);case"hide":return async function(e){const o=t.find(e);o.target&&o.target.classList.add("Autoconsent-Hidden")}(e);case"slide":return async function(e){const o=t.find(e),c=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=c.target.getBoundingClientRect();let i=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(i=0);const a=window.screenX+e.left+e.width/2,s=window.screenY+e.top+e.height/2,r=e.left+e.width/2,l=e.top+e.height/2,p=document.createEvent("MouseEvents");p.initMouseEvent("mousedown",!0,!0,window,0,a,s,r,l,!1,!1,!1,!1,0,o.target);const d=document.createEvent("MouseEvents");d.initMouseEvent("mousemove",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target),o.target.dispatchEvent(p),await this.waitTimeout(10),o.target.dispatchEvent(d),await this.waitTimeout(10),o.target.dispatchEvent(u)}}(e);case"close":return async function(){window.close()}();case"wait":return async function(e){await i(e.waitTime)}(e);case"eval":return async function(e){return console.log("eval!",e.code),new Promise((t=>{try{e.async?(window.eval(e.code),setTimeout((()=>{t(window.eval("window.__consentCheckResult"))}),e.timeout||250)):t(window.eval(e.code))}catch(o){console.warn("eval error",o,e.code),t(!1)}}))}(e);default:throw"Unknown action type: "+e.type}}function i(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function n(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var a={pending:new Map,sendContentMessage:null};function s(e,t){const o=n();a.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new class{constructor(e,t=1e3){this.id=e,this.promise=new Promise(((e,t)=>{this.resolve=e,this.reject=t})),this.timer=window.setTimeout((()=>{this.reject(new Error("timeout"))}),t)}}(o);return a.pending.set(c.id,c),c.promise}var r={EVAL_0:()=>console.log(1),EVAL_CONSENTMANAGER_1:()=>window.__cmp&&"object"==typeof __cmp("getCMPData"),EVAL_CONSENTMANAGER_2:()=>!__cmp("consentStatus").userChoiceExists,EVAL_CONSENTMANAGER_3:()=>__cmp("setConsent",0),EVAL_CONSENTMANAGER_4:()=>__cmp("setConsent",1),EVAL_CONSENTMANAGER_5:()=>__cmp("consentStatus").userChoiceExists,EVAL_COOKIEBOT_1:()=>!!window.Cookiebot,EVAL_COOKIEBOT_2:()=>!window.Cookiebot.hasResponse&&!0===window.Cookiebot.dialog?.visible,EVAL_COOKIEBOT_3:()=>window.Cookiebot.withdraw()||!0,EVAL_COOKIEBOT_4:()=>window.Cookiebot.hide()||!0,EVAL_COOKIEBOT_5:()=>!0===window.Cookiebot.declined,EVAL_KLARO_1:()=>{const e=globalThis.klaroConfig||globalThis.klaro?.getManager&&globalThis.klaro.getManager().config;if(!e)return!0;const t=(e.services||e.apps).filter((e=>!e.required)).map((e=>e.name));if(klaro&&klaro.getManager){const e=klaro.getManager();return t.every((t=>!e.consents[t]))}if(klaroConfig&&"cookie"===klaroConfig.storageMethod){const e=klaroConfig.cookieName||klaroConfig.storageName,o=JSON.parse(decodeURIComponent(document.cookie.split(";").find((t=>t.trim().startsWith(e))).split("=")[1]));return Object.keys(o).filter((e=>t.includes(e))).every((e=>!1===o[e]))}},EVAL_ONETRUST_1:()=>window.OnetrustActiveGroups.split(",").filter((e=>e.length>0)).length<=1,EVAL_TRUSTARC_TOP:()=>window&&window.truste&&"0"===window.truste.eu.bindMap.prefCookie,EVAL_ADROLL_0:()=>!document.cookie.includes("__adroll_fpc"),EVAL_ALMACMP_0:()=>document.cookie.includes('"name":"Google","consent":false'),EVAL_AFFINITY_SERIF_COM_0:()=>document.cookie.includes("serif_manage_cookies_viewed")&&!document.cookie.includes("serif_allow_analytics"),EVAL_ARBEITSAGENTUR_TEST:()=>document.cookie.includes("cookie_consent=denied"),EVAL_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),EVAL_BAHN_TEST:()=>1===utag.gdpr.getSelectedCategories().length,EVAL_BING_0:()=>document.cookie.includes("AL=0")&&document.cookie.includes("AD=0")&&document.cookie.includes("SM=0"),EVAL_BLOCKSY_0:()=>document.cookie.includes("blocksy_cookies_consent_accepted=no"),EVAL_BORLABS_0:()=>!JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("borlabs-cookie"))).split("=",2)[1])).consents.statistics,EVAL_BUNDESREGIERUNG_DE_0:()=>document.cookie.match("cookie-allow-tracking=0"),EVAL_CANVA_0:()=>!document.cookie.includes("gtm_fpc_engagement_event"),EVAL_CC_BANNER2_0:()=>!!document.cookie.match(/sncc=[^;]+D%3Dtrue/),EVAL_CLICKIO_0:()=>document.cookie.includes("__lxG__consent__v2_daisybit="),EVAL_CLINCH_0:()=>document.cookie.includes("ctc_rejected=1"),EVAL_COOKIECONSENT2_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COOKIECONSENT3_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COINBASE_0:()=>JSON.parse(decodeURIComponent(document.cookie.match(/cm_(eu|default)_preferences=([0-9a-zA-Z\\{\\}\\[\\]%:]*);?/)[2])).consent.length<=1,EVAL_COMPLIANZ_BANNER_0:()=>document.cookie.includes("cmplz_banner-status=dismissed"),EVAL_COOKIE_LAW_INFO_0:()=>CLI.disableAllCookies()||CLI.reject_close()||!0,EVAL_COOKIE_LAW_INFO_1:()=>-1===document.cookie.indexOf("cookielawinfo-checkbox-non-necessary=yes"),EVAL_COOKIE_LAW_INFO_DETECT:()=>!!window.CLI,EVAL_COOKIE_MANAGER_POPUP_0:()=>!1===JSON.parse(document.cookie.split(";").find((e=>e.trim().startsWith("CookieLevel"))).split("=")[1]).social,EVAL_COOKIEALERT_0:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_1:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_2:()=>!0===window.CookieConsent.declined,EVAL_COOKIEFIRST_0:()=>{return!1===(e=JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("cookiefirst"))).trim()).split("=")[1])).performance&&!1===e.functional&&!1===e.advertising;var e},EVAL_COOKIEFIRST_1:()=>document.querySelectorAll("button[data-cookiefirst-accent-color=true][role=checkbox]:not([disabled])").forEach((e=>"true"==e.getAttribute("aria-checked")&&e.click()))||!0,EVAL_COOKIEINFORMATION_0:()=>CookieInformation.declineAllCategories()||!0,EVAL_COOKIEINFORMATION_1:()=>CookieInformation.submitAllCategories()||!0,EVAL_COOKIEINFORMATION_2:()=>document.cookie.includes("CookieInformationConsent="),EVAL_COOKIEYES_0:()=>document.cookie.includes("advertisement:no"),EVAL_DAILYMOTION_0:()=>!!document.cookie.match("dm-euconsent-v2"),EVAL_DNDBEYOND_TEST:()=>document.cookie.includes("cookie-consent=denied"),EVAL_DSGVO_0:()=>!document.cookie.includes("sp_dsgvo_cookie_settings"),EVAL_DUNELM_0:()=>document.cookie.includes("cc_functional=0")&&document.cookie.includes("cc_targeting=0"),EVAL_ETSY_0:()=>document.querySelectorAll(".gdpr-overlay-body input").forEach((e=>{e.checked=!1}))||!0,EVAL_ETSY_1:()=>document.querySelector(".gdpr-overlay-view button[data-wt-overlay-close]").click()||!0,EVAL_EU_COOKIE_COMPLIANCE_0:()=>-1===document.cookie.indexOf("cookie-agreed=2"),EVAL_EU_COOKIE_LAW_0:()=>!document.cookie.includes("euCookie"),EVAL_EZOIC_0:()=>ezCMP.handleAcceptAllClick(),EVAL_EZOIC_1:()=>!!document.cookie.match(/ez-consent-tcf/),EVAL_GOOGLE_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_HEMA_TEST_0:()=>document.cookie.includes("cookies_rejected=1"),EVAL_IUBENDA_0:()=>document.querySelectorAll(".purposes-item input[type=checkbox]:not([disabled])").forEach((e=>{e.checked&&e.click()}))||!0,EVAL_IUBENDA_1:()=>!!document.cookie.match(/_iub_cs-\d+=/),EVAL_IWINK_TEST:()=>document.cookie.includes("cookie_permission_granted=no"),EVAL_JQUERY_COOKIEBAR_0:()=>!document.cookie.includes("cookies-state=accepted"),EVAL_MEDIAVINE_0:()=>document.querySelectorAll('[data-name="mediavine-gdpr-cmp"] input[type=checkbox]').forEach((e=>e.checked&&e.click()))||!0,EVAL_MICROSOFT_0:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Reject|Ablehnen")))[0].click()||!0,EVAL_MICROSOFT_1:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Accept|Annehmen")))[0].click()||!0,EVAL_MICROSOFT_2:()=>!!document.cookie.match("MSCC|GHCC"),EVAL_MOOVE_0:()=>document.querySelectorAll("#moove_gdpr_cookie_modal input").forEach((e=>{e.disabled||"moove_gdpr_strict_cookies"===e.name||(e.checked=!1)}))||!0,EVAL_ONENINETWO_0:()=>document.cookie.includes("CC_ADVERTISING=NO")&&document.cookie.includes("CC_ANALYTICS=NO"),EVAL_OPERA_0:()=>document.cookie.includes("cookie_consent_essential=true")&&!document.cookie.includes("cookie_consent_marketing=true"),EVAL_PAYPAL_0:()=>!0===document.cookie.includes("cookie_prefs"),EVAL_PRIMEBOX_0:()=>!document.cookie.includes("cb-enabled=accepted"),EVAL_PUBTECH_0:()=>document.cookie.includes("euconsent-v2")&&(document.cookie.match(/.YAAAAAAAAAAA/)||document.cookie.match(/.aAAAAAAAAAAA/)||document.cookie.match(/.YAAACFgAAAAA/)),EVAL_REDDIT_0:()=>document.cookie.includes("eu_cookie={%22opted%22:true%2C%22nonessential%22:false}"),EVAL_SIBBO_0:()=>!!window.localStorage.getItem("euconsent-v2"),EVAL_SIRDATA_UNBLOCK_SCROLL:()=>(document.documentElement.classList.forEach((e=>{e.startsWith("sd-cmp-")&&document.documentElement.classList.remove(e)})),!0),EVAL_SNIGEL_0:()=>!!document.cookie.match("snconsent"),EVAL_STEAMPOWERED_0:()=>2===JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>e.trim().startsWith("cookieSettings"))).split("=")[1])).preference_state,EVAL_SVT_TEST:()=>document.cookie.includes('cookie-consent-1={"optedIn":true,"functionality":false,"statistics":false}'),EVAL_TAKEALOT_0:()=>document.body.classList.remove("freeze")||(document.body.style="")||!0,EVAL_TARTEAUCITRON_0:()=>tarteaucitron.userInterface.respondAll(!1)||!0,EVAL_TARTEAUCITRON_1:()=>tarteaucitron.userInterface.respondAll(!0)||!0,EVAL_TARTEAUCITRON_2:()=>document.cookie.match(/tarteaucitron=[^;]*/)[0].includes("false"),EVAL_TAUNTON_TEST:()=>document.cookie.includes("taunton_user_consent_submitted=true"),EVAL_TEALIUM_0:()=>void 0!==window.utag&&"object"==typeof utag.gdpr,EVAL_TEALIUM_1:()=>utag.gdpr.setConsentValue(!1)||!0,EVAL_TEALIUM_DONOTSELL:()=>utag.gdpr.dns?.setDnsState(!1)||!0,EVAL_TEALIUM_2:()=>utag.gdpr.setConsentValue(!0)||!0,EVAL_TEALIUM_3:()=>1!==utag.gdpr.getConsentState(),EVAL_TEALIUM_DONOTSELL_CHECK:()=>1!==utag.gdpr.dns?.getDnsState(),EVAL_TESTCMP_0:()=>"button_clicked"===window.results.results[0],EVAL_TESTCMP_COSMETIC_0:()=>"banner_hidden"===window.results.results[0],EVAL_THEFREEDICTIONARY_0:()=>cmpUi.showPurposes()||cmpUi.rejectAll()||!0,EVAL_THEFREEDICTIONARY_1:()=>cmpUi.allowAll()||!0,EVAL_THEVERGE_0:()=>document.cookie.includes("_duet_gdpr_acknowledged=1"),EVAL_UBUNTU_COM_0:()=>document.cookie.includes("_cookies_accepted=essential"),EVAL_UK_COOKIE_CONSENT_0:()=>!document.cookie.includes("catAccCookies"),EVAL_USERCENTRICS_API_0:()=>"object"==typeof UC_UI,EVAL_USERCENTRICS_API_1:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_2:()=>!!UC_UI.denyAllConsents(),EVAL_USERCENTRICS_API_3:()=>!!UC_UI.acceptAllConsents(),EVAL_USERCENTRICS_API_4:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_5:()=>!0===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_API_6:()=>!1===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_BUTTON_0:()=>JSON.parse(localStorage.getItem("usercentrics")).consents.every((e=>e.isEssential||!e.consentStatus)),EVAL_WAITROSE_0:()=>Array.from(document.querySelectorAll("label[id$=cookies-deny-label]")).forEach((e=>e.click()))||!0,EVAL_WAITROSE_1:()=>document.cookie.includes("wtr_cookies_advertising=0")&&document.cookie.includes("wtr_cookies_analytics=0"),EVAL_WP_COOKIE_NOTICE_0:()=>document.cookie.includes("wpl_viewed_cookie=no"),EVAL_XE_TEST:()=>document.cookie.includes("xeConsentState={%22performance%22:false%2C%22marketing%22:false%2C%22compliance%22:false}"),EVAL_XING_0:()=>document.cookie.includes("userConsent=%7B%22marketing%22%3Afalse"),EVAL_YOUTUBE_DESKTOP_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_YOUTUBE_MOBILE_0:()=>!!document.cookie.match(/SOCS=CAE/)};var l={main:!0,frame:!1,urlPattern:""},p=class{constructor(e){this.runContext=l,this.autoconsent=e}get hasSelfTest(){throw new Error("Not Implemented")}get isIntermediate(){throw new Error("Not Implemented")}get isCosmetic(){throw new Error("Not Implemented")}mainWorldEval(e){const t=r[e];if(!t)return console.warn("Snippet not found",e),Promise.resolve(!1);const o=this.autoconsent.config.logs;if(this.autoconsent.config.isMainWorld){o.evals&&console.log("inline eval:",e,t);let c=!1;try{c=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(c)}const c=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,c),s(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...l,...this.runContext},t=window.top===window;return!(t&&!e.main)&&(!(!t&&!e.frame)&&!(e.urlPattern&&!window.location.href.match(e.urlPattern)))}detectCmp(){throw new Error("Not Implemented")}async detectPopup(){return!1}optOut(){throw new Error("Not Implemented")}optIn(){throw new Error("Not Implemented")}openCmp(){throw new Error("Not Implemented")}async test(){return Promise.resolve(!0)}click(e,t=!1){return this.autoconsent.domActions.click(e,t)}elementExists(e){return this.autoconsent.domActions.elementExists(e)}elementVisible(e,t){return this.autoconsent.domActions.elementVisible(e,t)}waitForElement(e,t){return this.autoconsent.domActions.waitForElement(e,t)}waitForVisible(e,t,o){return this.autoconsent.domActions.waitForVisible(e,t,o)}waitForThenClick(e,t,o){return this.autoconsent.domActions.waitForThenClick(e,t,o)}wait(e){return this.autoconsent.domActions.wait(e)}hide(e,t){return this.autoconsent.domActions.hide(e,t)}prehide(e){return this.autoconsent.domActions.prehide(e)}undoPrehide(){return this.autoconsent.domActions.undoPrehide()}querySingleReplySelector(e,t){return this.autoconsent.domActions.querySingleReplySelector(e,t)}querySelectorChain(e){return this.autoconsent.domActions.querySelectorChain(e)}elementSelector(e){return this.autoconsent.domActions.elementSelector(e)}},d=class extends p{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||l}get hasSelfTest(){return!!this.rule.test}get isIntermediate(){return!!this.rule.intermediate}get isCosmetic(){return!!this.rule.cosmetic}get prehideSelectors(){return this.rule.prehideSelectors}async detectCmp(){return!!this.rule.detectCmp&&this._runRulesParallel(this.rule.detectCmp)}async detectPopup(){return!!this.rule.detectPopup&&this._runRulesSequentially(this.rule.detectPopup)}async optOut(){const e=this.autoconsent.config.logs;return!!this.rule.optOut&&(e.lifecycle&&console.log("Initiated optOut()",this.rule.optOut),this._runRulesSequentially(this.rule.optOut))}async optIn(){const e=this.autoconsent.config.logs;return!!this.rule.optIn&&(e.lifecycle&&console.log("Initiated optIn()",this.rule.optIn),this._runRulesSequentially(this.rule.optIn))}async openCmp(){return!!this.rule.openCmp&&this._runRulesSequentially(this.rule.openCmp)}async test(){return this.hasSelfTest?this._runRulesSequentially(this.rule.test):super.test()}async evaluateRuleStep(e){const t=[],o=this.autoconsent.config.logs;if(e.exists&&t.push(this.elementExists(e.exists)),e.visible&&t.push(this.elementVisible(e.visible,e.check)),e.eval){const o=this.mainWorldEval(e.eval);t.push(o)}if(e.waitFor&&t.push(this.waitForElement(e.waitFor,e.timeout)),e.waitForVisible&&t.push(this.waitForVisible(e.waitForVisible,e.timeout,e.check)),e.click&&t.push(this.click(e.click,e.all)),e.waitForThenClick&&t.push(this.waitForThenClick(e.waitForThenClick,e.timeout,e.all)),e.wait&&t.push(this.wait(e.wait)),e.hide&&t.push(this.hide(e.hide,e.method)),e.if){if(!e.if.exists&&!e.if.visible)return console.error("invalid conditional rule",e.if),!1;const c=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",c),c?t.push(this._runRulesSequentially(e.then)):e.else?t.push(this._runRulesSequentially(e.else)):t.push(!0)}if(e.any){for(const t of e.any)if(await this.evaluateRuleStep(t))return!0;return!1}if(0===t.length)return o.errors&&console.warn("Unrecognized rule",e),!1;return(await Promise.all(t)).reduce(((e,t)=>e&&t),!0)}async _runRulesParallel(e){const t=e.map((e=>this.evaluateRuleStep(e)));return(await Promise.all(t)).every((e=>!!e))}async _runRulesSequentially(e){const t=this.autoconsent.config.logs;for(const o of e){t.rulesteps&&console.log("Running rule...",o);const e=await this.evaluateRuleStep(o);if(t.rulesteps&&console.log("...rule result",e),!e&&!o.optional)return!1}return!0}};function u(e="autoconsent-css-rules"){const t=`style#${e}`,o=document.querySelector(t);if(o&&o instanceof HTMLStyleElement)return o;{const t=document.head||document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");return o.id=e,t.appendChild(o),o}}function m(e,t,o="display"){const c=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=c,t.length>0)}async function h(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(h(e,t-1,o))}),o)})):Promise.resolve(c)}function k(e){if(!e)return!1;if(null!==e.offsetParent)return!0;{const t=window.getComputedStyle(e);if("fixed"===t.position&&"none"!==t.display)return!0}return!1}function b(e){const t={enabled:!0,autoAction:"optOut",disabledCmps:[],enablePrehide:!0,enableCosmeticRules:!0,detectRetries:20,isMainWorld:!1,prehideTimeout:2e3,logs:{lifecycle:!1,rulesteps:!1,evals:!1,errors:!0,messages:!1}},o=(c=t,globalThis.structuredClone?structuredClone(c):JSON.parse(JSON.stringify(c)));var c;for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}var _="#truste-show-consent",g="#truste-consent-track",y=[class extends p{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${g}`],this.runContext={main:!0,frame:!1},this._shortcutButton=null,this._optInDone=!1}get hasSelfTest(){return!1}get isIntermediate(){return!this._optInDone&&!this._shortcutButton}get isCosmetic(){return!1}async detectCmp(){const e=this.elementExists(`${_},${g}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${g}`,"all")}openFrame(){this.click(_)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(m(u(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${g}`),this.click(_),setTimeout((()=>{u().remove()}),1e4),!0)}async optIn(){return this._optInDone=!0,this.click("#truste-consent-button")}async openCmp(){return!0}async test(){return await this.mainWorldEval("EVAL_TRUSTARC_TOP")}},class extends p{constructor(){super(...arguments),this.name="TrustArc-frame",this.runContext={main:!1,frame:!0,urlPattern:"^https://consent-pref\\.trustarc\\.com/\\?"}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return!0}async detectPopup(){return this.elementVisible("#defaultpreferencemanager","any")&&this.elementVisible(".mainContent","any")}async navigateToSettings(){return await h((async()=>this.elementExists(".shp")||this.elementVisible(".advance","any")||this.elementExists(".switch span:first-child")),10,500),this.elementExists(".shp")&&this.click(".shp"),await this.waitForElement(".prefPanel",5e3),this.elementVisible(".advance","any")&&this.click(".advance"),await h((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await h((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",5e3),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',3e3),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",5e3),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",5e3),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",3e5),!0))}async optIn(){return this.click(".call")||(await this.navigateToSettings(),this.click(".switch span:nth-child(2)",!0),this.click(".submit"),this.waitForElement("#gwt-debug-close_id",3e5).then((()=>{this.click("#gwt-debug-close_id")}))),!0}},class extends p{constructor(){super(...arguments),this.name="Cybotcookiebot",this.prehideSelectors=["#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#dtcookie-container,#cookiebanner,#cb-cookieoverlay,.modal--cookie-banner,#cookiebanner_outer,#CookieBanner"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return await this.mainWorldEval("EVAL_COOKIEBOT_1")}async detectPopup(){return this.mainWorldEval("EVAL_COOKIEBOT_2")}async optOut(){await this.wait(500);let e=await this.mainWorldEval("EVAL_COOKIEBOT_3");return await this.wait(500),e=e&&await this.mainWorldEval("EVAL_COOKIEBOT_4"),e}async optIn(){return this.elementExists("#dtcookie-container")?this.click(".h-dtcookie-accept"):(this.click(".CybotCookiebotDialogBodyLevelButton:not(:checked):enabled",!0),this.click("#CybotCookiebotDialogBodyLevelButtonAccept"),this.click("#CybotCookiebotDialogBodyButtonAccept"),!0)}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_COOKIEBOT_5")}},class extends p{constructor(){super(...arguments),this.name="Sourcepoint-frame",this.prehideSelectors=["div[id^='sp_message_container_'],.message-overlay","#sp_privacy_manager_container"],this.ccpaNotice=!1,this.ccpaPopup=!1,this.runContext={main:!1,frame:!0}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){const e=new URL(location.href);return e.searchParams.has("message_id")&&"ccpa-notice.sp-prod.net"===e.hostname?(this.ccpaNotice=!0,!0):"ccpa-pm.sp-prod.net"===e.hostname?(this.ccpaPopup=!0,!0):("/index.html"===e.pathname||"/privacy-manager/index.html"===e.pathname||"/ccpa_pm/index.html"===e.pathname)&&(e.searchParams.has("message_id")||e.searchParams.has("requestUUID")||e.searchParams.has("consentUUID"))}async detectPopup(){return!!this.ccpaNotice||(this.ccpaPopup?await this.waitForElement(".priv-save-btn",2e3):(await this.waitForElement(".sp_choice_type_11,.sp_choice_type_12,.sp_choice_type_13,.sp_choice_type_ACCEPT_ALL,.sp_choice_type_SAVE_AND_EXIT",2e3),!this.elementExists(".sp_choice_type_9")))}async optIn(){return await this.waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL",2e3),!!this.click(".sp_choice_type_11")||!!this.click(".sp_choice_type_ACCEPT_ALL")}isManagerOpen(){return"/privacy-manager/index.html"===location.pathname||"/ccpa_pm/index.html"===location.pathname}async optOut(){const e=this.autoconsent.config.logs;if(this.ccpaPopup){const e=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.neutral.on .right");for(const t of e)t.click();const t=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.switch-bg.on");for(const e of t)e.click();return this.click(".priv-save-btn")}if(!this.isManagerOpen()){if(!await this.waitForElement(".sp_choice_type_12,.sp_choice_type_13"))return!1;if(!this.elementExists(".sp_choice_type_12"))return this.click(".sp_choice_type_13");this.click(".sp_choice_type_12"),await h((()=>this.isManagerOpen()),200,100)}await this.waitForElement(".type-modal",2e4),this.waitForThenClick(".ccpa-stack .pm-switch[aria-checked=true] .slider",500,!0);try{const e=".sp_choice_type_REJECT_ALL",t=".reject-toggle",o=await Promise.race([this.waitForElement(e,2e3).then((e=>e?0:-1)),this.waitForElement(t,2e3).then((e=>e?1:-1)),this.waitForElement(".pm-features",2e3).then((e=>e?2:-1))]);if(0===o)return await this.wait(1500),this.click(e);1===o?this.click(t):2===o&&(await this.waitForElement(".pm-features",1e4),this.click(".checked > span",!0),this.click(".chevron"))}catch(t){e.errors&&console.warn(t)}return this.click(".sp_choice_type_SAVE_AND_EXIT")}},class extends p{constructor(){super(...arguments),this.name="consentmanager.net",this.prehideSelectors=["#cmpbox,#cmpbox2"],this.apiAvailable=!1}get hasSelfTest(){return this.apiAvailable}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.apiAvailable=await this.mainWorldEval("EVAL_CONSENTMANAGER_1"),!!this.apiAvailable||this.elementExists("#cmpbox")}async detectPopup(){return this.apiAvailable?(await this.wait(500),await this.mainWorldEval("EVAL_CONSENTMANAGER_2")):this.elementVisible("#cmpbox .cmpmore","any")}async optOut(){return await this.wait(500),this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_3"):!!this.click(".cmpboxbtnno")||(this.elementExists(".cmpwelcomeprpsbtn")?(this.click(".cmpwelcomeprpsbtn > a[aria-checked=true]",!0),this.click(".cmpboxbtnsave"),!0):(this.click(".cmpboxbtncustom"),await this.waitForElement(".cmptblbox",2e3),this.click(".cmptdchoice > a[aria-checked=true]",!0),this.click(".cmpboxbtnyescustomchoices"),this.hide("#cmpwrapper,#cmpbox","display"),!0))}async optIn(){return this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_4"):this.click(".cmpboxbtnyes")}async test(){if(this.apiAvailable)return await this.mainWorldEval("EVAL_CONSENTMANAGER_5")}},class extends p{constructor(){super(...arguments),this.name="Evidon"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#_evidon_banner")}async detectPopup(){return this.elementVisible("#_evidon_banner","any")}async optOut(){return this.click("#_evidon-decline-button")||(m(u(),"#evidon-prefdiag-overlay,#evidon-prefdiag-background"),this.click("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),this.click("#evidon-prefdiag-decline")),!0}async optIn(){return this.click("#_evidon-accept-button")}},class extends p{constructor(){super(...arguments),this.name="Onetrust",this.prehideSelectors=["#onetrust-banner-sdk,#onetrust-consent-sdk,.onetrust-pc-dark-filter,.js-consent-banner"],this.runContext={urlPattern:"^(?!.*https://www\\.nba\\.com/)"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#onetrust-banner-sdk,#onetrust-consent-sdk")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk,#onetrust-consent-sdk","any")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies"):(this.elementExists("#onetrust-pc-btn-handler")?this.click("#onetrust-pc-btn-handler"):this.click(".ot-sdk-show-settings,button.js-cookie-settings"),await this.waitForElement("#onetrust-consent-sdk",2e3),await this.wait(1e3),this.click("#onetrust-consent-sdk input.category-switch-handler:checked,.js-editor-toggle-state:checked",!0),await this.wait(1e3),await this.waitForElement(".save-preference-btn-handler,.js-consent-save",2e3),this.click(".save-preference-btn-handler,.js-consent-save"),await this.waitForVisible("#onetrust-banner-sdk",5e3,"none"),!0)}async optIn(){return this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies")}async test(){return await h((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends p{constructor(){super(...arguments),this.name="Klaro",this.prehideSelectors=[".klaro"],this.settingsOpen=!1}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".klaro > .cookie-modal")?(this.settingsOpen=!0,!0):this.elementExists(".klaro > .cookie-notice")}async detectPopup(){return this.elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal","any")}async optOut(){return!!this.click(".klaro .cn-decline")||(this.settingsOpen||(this.click(".klaro .cn-learn-more,.klaro .cm-button-manage"),await this.waitForElement(".klaro > .cookie-modal",2e3),this.settingsOpen=!0),!!this.click(".klaro .cn-decline")||(this.click(".cm-purpose:not(.cm-toggle-all) > input:not(.half-checked,.required,.only-required),.cm-purpose:not(.cm-toggle-all) > div > input:not(.half-checked,.required,.only-required)",!0),this.click(".cm-btn-accept,.cm-button")))}async optIn(){return!!this.click(".klaro .cm-btn-accept-all")||(this.settingsOpen?(this.click(".cm-purpose:not(.cm-toggle-all) > input.half-checked",!0),this.click(".cm-btn-accept")):this.click(".klaro .cookie-notice .cm-btn-success"))}async test(){return await this.mainWorldEval("EVAL_KLARO_1")}},class extends p{constructor(){super(...arguments),this.name="Uniconsent"}get prehideSelectors(){return[".unic",".modal:has(.unic)"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".unic .unic-box,.unic .unic-bar,.unic .unic-modal")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar,.unic .unic-modal","any")}async optOut(){if(await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic button").forEach((e=>{const t=e.textContent;(t.includes("Manage Options")||t.includes("Optionen verwalten"))&&e.click()})),await this.waitForElement(".unic input[type=checkbox]",1e3)){await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic input[type=checkbox]").forEach((e=>{e.checked&&e.click()}));for(const e of document.querySelectorAll(".unic button")){const t=e.textContent;for(const o of["Confirm Choices","Save Choices","Auswahl speichern"])if(t.includes(o))return e.click(),await this.wait(500),!0}}return!1}async optIn(){return this.waitForThenClick(".unic #unic-agree")}async test(){await this.wait(1e3);return!this.elementExists(".unic .unic-box,.unic .unic-bar")}},class extends p{constructor(){super(...arguments),this.prehideSelectors=[".cmp-root"],this.name="Conversant"}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".cmp-root .cmp-receptacle")}async detectPopup(){return this.elementVisible(".cmp-root .cmp-receptacle","any")}async optOut(){if(!await this.waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))return!1;if(!await this.waitForElement(".cmp-view-tab-tabs"))return!1;await this.waitForThenClick(".cmp-view-tab-tabs > :first-child"),await this.waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");for(const e of Array.from(document.querySelectorAll(".cmp-accordion-item"))){e.querySelector(".cmp-accordion-item-title").click(),await h((()=>!!e.querySelector(".cmp-accordion-item-content.cmp-active")),10,50);const t=e.querySelector(".cmp-accordion-item-content.cmp-active");t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e=>e.click())),t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e=>e.click()))}return await this.click(".cmp-main-button:not(.cmp-main-button--primary)"),!0}async optIn(){return this.waitForThenClick(".cmp-main-button.cmp-main-button--primary")}async test(){return document.cookie.includes("cmp-data=0")}},class extends p{constructor(){super(...arguments),this.name="tiktok.com",this.runContext={urlPattern:"tiktok"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}getShadowRoot(){const e=document.querySelector("tiktok-cookie-banner");return e?e.shadowRoot:null}async detectCmp(){return this.elementExists("tiktok-cookie-banner")}async detectPopup(){return k(this.getShadowRoot().querySelector(".tiktok-cookie-banner"))}async optOut(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:first-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no decline button found"),!1)}async optIn(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:last-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no accept button found"),!1)}async test(){const e=document.cookie.match(/cookie-consent=([^;]+)/);if(!e)return!1;const t=JSON.parse(decodeURIComponent(e[1]));return Object.values(t).every((e=>"boolean"!=typeof e||!1===e))}},class extends p{constructor(){super(...arguments),this.runContext={urlPattern:"^https://(www\\.)?airbnb\\.[^/]+/"},this.prehideSelectors=["div[data-testid=main-cookies-banner-container]",'div:has(> div:first-child):has(> div:last-child):has(> section [data-testid="strictly-necessary-cookies"])']}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div[data-testid=main-cookies-banner-container]")}async detectPopup(){return this.elementVisible("div[data-testid=main-cookies-banner-container","any")}async optOut(){let e;for(await this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._snbhip0");e=document.querySelector("[data-testid=modal-container] button[aria-checked=true]:not([disabled])");)e.click();return this.waitForThenClick("button[data-testid=save-btn]")}async optIn(){return this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._148dgdpk")}async test(){return await h((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}];var w=[{name:"192.com",detectCmp:[{exists:".ont-cookies"}],detectPopup:[{visible:".ont-cookies"}],optIn:[{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-ok2"}],optOut:[{click:".ont-cookes-btn-manage"},{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-choose"}],test:[{eval:"EVAL_ONENINETWO_0"}]},{name:"1password-com",cosmetic:!0,prehideSelectors:['footer #footer-root [aria-label="Cookie Consent"]'],detectCmp:[{exists:'footer #footer-root [aria-label="Cookie Consent"]'}],detectPopup:[{visible:'footer #footer-root [aria-label="Cookie Consent"]'}],optIn:[{click:'footer #footer-root [aria-label="Cookie Consent"] button'}],optOut:[{hide:'footer #footer-root [aria-label="Cookie Consent"]'}]},{name:"abconcerts.be",vendorUrl:"https://unknown",intermediate:!1,prehideSelectors:["dialog.cookie-consent"],detectCmp:[{exists:"dialog.cookie-consent form.cookie-consent__form"}],detectPopup:[{visible:"dialog.cookie-consent form.cookie-consent__form"}],optIn:[{waitForThenClick:"dialog.cookie-consent form.cookie-consent__form button[value=yes]"}],optOut:[{if:{exists:"dialog.cookie-consent form.cookie-consent__form button[value=no]"},then:[{click:"dialog.cookie-consent form.cookie-consent__form button[value=no]"}],else:[{click:"dialog.cookie-consent form.cookie-consent__form button.cookie-consent__options-toggle"},{waitForThenClick:'dialog.cookie-consent form.cookie-consent__form button[value="save_options"]'}]}]},{name:"activobank.pt",runContext:{urlPattern:"^https://(www\\.)?activobank\\.pt"},prehideSelectors:["aside#cookies,.overlay-cookies"],detectCmp:[{exists:"#cookies .cookies-btn"}],detectPopup:[{visible:"#cookies #submitCookies"}],optIn:[{waitForThenClick:"#cookies #submitCookies"}],optOut:[{waitForThenClick:"#cookies #rejectCookies"}]},{name:"Adroll",prehideSelectors:["#adroll_consent_container"],detectCmp:[{exists:"#adroll_consent_container"}],detectPopup:[{visible:"#adroll_consent_container"}],optIn:[{waitForThenClick:"#adroll_consent_accept"}],optOut:[{waitForThenClick:"#adroll_consent_reject"}],test:[{eval:"EVAL_ADROLL_0"}]},{name:"affinity.serif.com",detectCmp:[{exists:".c-cookie-banner button[data-qa='allow-all-cookies']"}],detectPopup:[{visible:".c-cookie-banner"}],optIn:[{click:'button[data-qa="allow-all-cookies"]'}],optOut:[{click:'button[data-qa="manage-cookies"]'},{waitFor:'.c-cookie-banner ~ [role="dialog"]'},{waitForThenClick:'.c-cookie-banner ~ [role="dialog"] input[type="checkbox"][value="true"]',all:!0},{click:'.c-cookie-banner ~ [role="dialog"] .c-modal__action button'}],test:[{wait:500},{eval:"EVAL_AFFINITY_SERIF_COM_0"}]},{name:"agolde.com",cosmetic:!0,prehideSelectors:["#modal-1 div[data-micromodal-close]"],detectCmp:[{exists:"#modal-1 div[aria-labelledby=modal-1-title]"}],detectPopup:[{exists:"#modal-1 div[data-micromodal-close]"}],optIn:[{click:'button[aria-label="Close modal"]'}],optOut:[{hide:"#modal-1 div[data-micromodal-close]"}]},{name:"aliexpress",vendorUrl:"https://aliexpress.com/",runContext:{urlPattern:"^https://.*\\.aliexpress\\.com/"},prehideSelectors:["#gdpr-new-container"],detectCmp:[{exists:"#gdpr-new-container"}],detectPopup:[{visible:"#gdpr-new-container"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept"}],optOut:[{waitForThenClick:"#gdpr-new-container .btn-more"},{waitFor:"#gdpr-new-container .gdpr-dialog-switcher"},{click:"#gdpr-new-container .switcher-on",all:!0,optional:!0},{click:"#gdpr-new-container .btn-save"}]},{name:"almacmp",prehideSelectors:["#alma-cmpv2-container"],detectCmp:[{exists:"#alma-cmpv2-container"}],detectPopup:[{visible:"#alma-cmpv2-container #almacmp-modal-layer1"}],optIn:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalConfirmBtn"}],optOut:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalSettingBtn"},{waitFor:"#alma-cmpv2-container #almacmp-modal-layer2"},{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer2 #almacmp-reject-all-layer2"}],test:[{eval:"EVAL_ALMACMP_0"}]},{name:"altium.com",cosmetic:!0,prehideSelectors:[".altium-privacy-bar"],detectCmp:[{exists:".altium-privacy-bar"}],detectPopup:[{exists:".altium-privacy-bar"}],optIn:[{click:"a.altium-privacy-bar__btn"}],optOut:[{hide:".altium-privacy-bar"}]},{name:"amazon.com",prehideSelectors:['span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'],detectCmp:[{exists:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],detectPopup:[{visible:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],optIn:[{waitForVisible:"#sp-cc-accept"},{wait:500},{click:"#sp-cc-accept"}],optOut:[{waitForVisible:"#sp-cc-rejectall-link"},{wait:500},{click:"#sp-cc-rejectall-link"}]},{name:"aquasana.com",cosmetic:!0,prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{click:"#accept_consent"}],optOut:[{hide:"#consent-tracking"}]},{name:"arbeitsagentur",vendorUrl:"https://www.arbeitsagentur.de/",prehideSelectors:[".modal-open bahf-cookie-disclaimer-dpl3"],detectCmp:[{exists:"bahf-cookie-disclaimer-dpl3"}],detectPopup:[{visible:"bahf-cookie-disclaimer-dpl3"}],optIn:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-primary"]}],optOut:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-contrast"]}],test:[{eval:"EVAL_ARBEITSAGENTUR_TEST"}]},{name:"asus",vendorUrl:"https://www.asus.com/",runContext:{urlPattern:"^https://www\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info,#cookie-policy-info-bg"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{waitForThenClick:'#cookie-policy-info [data-agree="Accept Cookies"]'}],optOut:[{if:{exists:"#cookie-policy-info .btn-reject"},then:[{waitForThenClick:"#cookie-policy-info .btn-reject"}],else:[{waitForThenClick:"#cookie-policy-info .btn-setting"},{waitForThenClick:'#cookie-policy-lightbox-wrapper [data-agree="Save Settings"]'}]}]},{name:"athlinks-com",runContext:{urlPattern:"^https://(www\\.)?athlinks\\.com/"},cosmetic:!0,prehideSelectors:["#footer-container ~ div"],detectCmp:[{exists:"#footer-container ~ div"}],detectPopup:[{visible:"#footer-container > div"}],optIn:[{click:"#footer-container ~ div button"}],optOut:[{hide:"#footer-container ~ div"}]},{name:"ausopen.com",cosmetic:!0,detectCmp:[{exists:".gdpr-popup__message"}],detectPopup:[{visible:".gdpr-popup__message"}],optOut:[{hide:".gdpr-popup__message"}],optIn:[{click:".gdpr-popup__message button"}]},{name:"automattic-cmp-optout",prehideSelectors:['form[class*="cookie-banner"][method="post"]'],detectCmp:[{exists:'form[class*="cookie-banner"][method="post"]'}],detectPopup:[{visible:'form[class*="cookie-banner"][method="post"]'}],optIn:[{click:'a[class*="accept-all-button"]'}],optOut:[{click:'form[class*="cookie-banner"] div[class*="simple-options"] a[class*="customize-button"]'},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:'a[class*="accept-selection-button"]'}]},{name:"aws.amazon.com",prehideSelectors:["#awsccc-cb-content","#awsccc-cs-container","#awsccc-cs-modalOverlay","#awsccc-cs-container-inner"],detectCmp:[{exists:"#awsccc-cb-content"}],detectPopup:[{visible:"#awsccc-cb-content"}],optIn:[{click:"button[data-id=awsccc-cb-btn-accept"}],optOut:[{click:"button[data-id=awsccc-cb-btn-customize]"},{waitFor:"input[aria-checked]"},{click:"input[aria-checked=true]",all:!0,optional:!0},{click:"button[data-id=awsccc-cs-btn-save]"}]},{name:"axeptio",prehideSelectors:[".axeptio_widget"],detectCmp:[{exists:".axeptio_widget"}],detectPopup:[{visible:".axeptio_widget"}],optIn:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_acceptAll"}],optOut:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_dismiss"}],test:[{eval:"EVAL_AXEPTIO_0"}]},{name:"baden-wuerttemberg.de",prehideSelectors:[".cookie-alert.t-dark"],cosmetic:!0,detectCmp:[{exists:".cookie-alert.t-dark"}],detectPopup:[{visible:".cookie-alert.t-dark"}],optIn:[{click:".cookie-alert__form input:not([disabled]):not([checked])"},{click:".cookie-alert__button button"}],optOut:[{hide:".cookie-alert.t-dark"}]},{name:"bahn-de",vendorUrl:"https://www.bahn.de/",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?bahn\\.de/"},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:["body > div:first-child","#consent-layer"]}],detectPopup:[{visible:["body > div:first-child","#consent-layer"]}],optIn:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-all-cookies"]}],optOut:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-essential-cookies"]}],test:[{eval:"EVAL_BAHN_TEST"}]},{name:"bbb.org",runContext:{urlPattern:"^https://www\\.bbb\\.org/"},cosmetic:!0,prehideSelectors:['div[aria-label="use of cookies on bbb.org"]'],detectCmp:[{exists:'div[aria-label="use of cookies on bbb.org"]'}],detectPopup:[{visible:'div[aria-label="use of cookies on bbb.org"]'}],optIn:[{click:'div[aria-label="use of cookies on bbb.org"] button.bds-button-unstyled span.visually-hidden'}],optOut:[{hide:'div[aria-label="use of cookies on bbb.org"]'}]},{name:"bing.com",prehideSelectors:["#bnp_container"],detectCmp:[{exists:"#bnp_cookie_banner"}],detectPopup:[{visible:"#bnp_cookie_banner"}],optIn:[{click:"#bnp_btn_accept"}],optOut:[{click:"#bnp_btn_preference"},{click:"#mcp_savesettings"}],test:[{eval:"EVAL_BING_0"}]},{name:"blocksy",vendorUrl:"https://creativethemes.com/blocksy/docs/extensions/cookies-consent/",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[".cookie-notification"],detectCmp:[{exists:"#blocksy-ext-cookies-consent-styles-css"}],detectPopup:[{visible:".cookie-notification"}],optIn:[{click:".cookie-notification .ct-cookies-decline-button"}],optOut:[{waitForThenClick:".cookie-notification .ct-cookies-decline-button"}],test:[{eval:"EVAL_BLOCKSY_0"}]},{name:"borlabs",detectCmp:[{exists:"._brlbs-block-content"}],detectPopup:[{visible:"._brlbs-bar-wrap,._brlbs-box-wrap"}],optIn:[{click:"a[data-cookie-accept-all]"}],optOut:[{click:"a[data-cookie-individual]"},{waitForVisible:".cookie-preference"},{click:"input[data-borlabs-cookie-checkbox]:checked",all:!0,optional:!0},{click:"#CookiePrefSave"},{wait:500}],prehideSelectors:["#BorlabsCookieBox"],test:[{eval:"EVAL_BORLABS_0"}]},{name:"bundesregierung.de",prehideSelectors:[".bpa-cookie-banner"],detectCmp:[{exists:".bpa-cookie-banner"}],detectPopup:[{visible:".bpa-cookie-banner .bpa-module-full-hero"}],optIn:[{click:".bpa-accept-all-button"}],optOut:[{wait:500,comment:"click is not immediately recognized"},{waitForThenClick:".bpa-close-button"}],test:[{eval:"EVAL_BUNDESREGIERUNG_DE_0"}]},{name:"burpee.com",cosmetic:!0,prehideSelectors:["#notice-cookie-block"],detectCmp:[{exists:"#notice-cookie-block"}],detectPopup:[{exists:"#html-body #notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{hide:"#html-body #notice-cookie-block, #notice-cookie"}]},{name:"canva.com",prehideSelectors:['div[role="dialog"] a[data-anchor-id="cookie-policy"]'],detectCmp:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],detectPopup:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],optIn:[{click:'div[role="dialog"] button:nth-child(1)'}],optOut:[{if:{exists:'div[role="dialog"] button:nth-child(3)'},then:[{click:'div[role="dialog"] button:nth-child(2)'}],else:[{click:'div[role="dialog"] button:nth-child(2)'},{waitFor:'div[role="dialog"] a[data-anchor-id="privacy-policy"]'},{click:'div[role="dialog"] button:nth-child(2)'},{click:'div[role="dialog"] div:last-child button:only-child'}]}],test:[{eval:"EVAL_CANVA_0"}]},{name:"canyon.com",runContext:{urlPattern:"^https://www\\.canyon\\.com/"},prehideSelectors:["div.modal.cookiesModal.is-open"],detectCmp:[{exists:"div.modal.cookiesModal.is-open"}],detectPopup:[{visible:"div.modal.cookiesModal.is-open"}],optIn:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-submit"]'}],optOut:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-manage-cookies"]'},{waitForThenClick:"button#js-manage-data-privacy-save-button"}]},{name:"cc-banner-springer",prehideSelectors:[".cc-banner[data-cc-banner]"],detectCmp:[{exists:".cc-banner[data-cc-banner]"}],detectPopup:[{visible:".cc-banner[data-cc-banner]"}],optIn:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=accept]"}],optOut:[{if:{exists:".cc-banner[data-cc-banner] button[data-cc-action=reject]"},then:[{click:".cc-banner[data-cc-banner] button[data-cc-action=reject]"}],else:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=preferences]"},{waitFor:".cc-preferences[data-cc-preferences]"},{click:".cc-preferences[data-cc-preferences] input[type=radio][data-cc-action=toggle-category][value=off]",all:!0,optional:!0},{if:{exists:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"},then:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"}],else:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=save]"}]}]}],test:[{eval:"EVAL_CC_BANNER2_0"}]},{name:"cc_banner",cosmetic:!0,prehideSelectors:[".cc_banner-wrapper"],detectCmp:[{exists:".cc_banner-wrapper"}],detectPopup:[{visible:".cc_banner"}],optIn:[{click:".cc_btn_accept_all"}],optOut:[{hide:".cc_banner-wrapper"}]},{name:"ciaopeople.it",prehideSelectors:["#cp-gdpr-choices"],detectCmp:[{exists:"#cp-gdpr-choices"}],detectPopup:[{visible:"#cp-gdpr-choices"}],optIn:[{waitForThenClick:".gdpr-btm__right > button:nth-child(2)"}],optOut:[{waitForThenClick:".gdpr-top-content > button"},{waitFor:".gdpr-top-back"},{waitForThenClick:".gdpr-btm__right > button:nth-child(1)"}],test:[{visible:"#cp-gdpr-choices",check:"none"}]},{vendorUrl:"https://www.civicuk.com/cookie-control/",name:"civic-cookie-control",prehideSelectors:["#ccc-module,#ccc-overlay"],detectCmp:[{exists:"#ccc-module"}],detectPopup:[{visible:"#ccc"},{visible:"#ccc-module"}],optOut:[{click:"#ccc-reject-settings"}],optIn:[{click:"#ccc-recommended-settings"}]},{name:"click.io",prehideSelectors:["#cl-consent"],detectCmp:[{exists:"#cl-consent"}],detectPopup:[{visible:"#cl-consent"}],optIn:[{waitForThenClick:'#cl-consent [data-role="b_agree"]'}],optOut:[{waitFor:'#cl-consent [data-role="b_options"]'},{wait:500},{click:'#cl-consent [data-role="b_options"]'},{waitFor:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]'},{click:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]',all:!0},{click:'[data-role="b_save"]'}],test:[{eval:"EVAL_CLICKIO_0",comment:"TODO: this only checks if we interacted at all"}]},{name:"clinch",intermediate:!1,runContext:{frame:!1,main:!0},prehideSelectors:[".consent-modal[role=dialog]"],detectCmp:[{exists:".consent-modal[role=dialog]"}],detectPopup:[{visible:".consent-modal[role=dialog]"}],optIn:[{click:"#consent_agree"}],optOut:[{if:{exists:"#consent_reject"},then:[{click:"#consent_reject"}],else:[{click:"#manage_cookie_preferences"},{click:"#cookie_consent_preferences input:checked",all:!0,optional:!0},{click:"#consent_save"}]}],test:[{eval:"EVAL_CLINCH_0"}]},{name:"clustrmaps.com",runContext:{urlPattern:"^https://(www\\.)?clustrmaps\\.com/"},cosmetic:!0,prehideSelectors:["#gdpr-cookie-message"],detectCmp:[{exists:"#gdpr-cookie-message"}],detectPopup:[{visible:"#gdpr-cookie-message"}],optIn:[{click:"button#gdpr-cookie-accept"}],optOut:[{hide:"#gdpr-cookie-message"}]},{name:"coinbase",intermediate:!1,runContext:{frame:!0,main:!0,urlPattern:"^https://(www|help)\\.coinbase\\.com"},prehideSelectors:[],detectCmp:[{exists:"div[class^=CookieBannerContent__Container]"}],detectPopup:[{visible:"div[class^=CookieBannerContent__Container]"}],optIn:[{click:"div[class^=CookieBannerContent__CTA] :nth-last-child(1)"}],optOut:[{click:"button[class^=CookieBannerContent__Settings]"},{click:"div[class^=CookiePreferencesModal__CategoryContainer] input:checked",all:!0,optional:!0},{click:"div[class^=CookiePreferencesModal__ButtonContainer] > button"}],test:[{eval:"EVAL_COINBASE_0"}]},{name:"Complianz banner",prehideSelectors:["#cmplz-cookiebanner-container"],detectCmp:[{exists:"#cmplz-cookiebanner-container .cmplz-cookiebanner"}],detectPopup:[{visible:"#cmplz-cookiebanner-container .cmplz-cookiebanner",check:"any"}],optIn:[{waitForThenClick:".cmplz-cookiebanner .cmplz-accept"}],optOut:[{waitForThenClick:".cmplz-cookiebanner .cmplz-deny"}],test:[{eval:"EVAL_COMPLIANZ_BANNER_0"}]},{name:"Complianz categories",prehideSelectors:['.cc-type-categories[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"] .cc-dismiss'},then:[{click:".cc-dismiss"}],else:[{click:".cc-type-categories input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-save"}]}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{if:{exists:".cc-deny"},then:[{click:".cc-deny"}],else:[{hide:'[aria-describedby="cookieconsent:desc"]'}]}]},{name:"Complianz opt-both",prehideSelectors:['[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{waitForThenClick:".cc-deny"}]},{name:"Complianz optin",prehideSelectors:['.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{visible:".cc-deny"},then:[{click:".cc-deny"}],else:[{if:{visible:".cc-settings"},then:[{waitForThenClick:".cc-settings"},{waitForVisible:".cc-settings-view"},{click:".cc-settings-view input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-settings-view .cc-btn-accept-selected"}],else:[{click:".cc-dismiss"}]}]}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_DETECT"}],detectPopup:[{visible:"#cookie-law-info-bar"}],optIn:[{click:'[data-cli_action="accept_all"]'}],optOut:[{hide:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_0"}],test:[{eval:"EVAL_COOKIE_LAW_INFO_1"}]},{name:"cookie-manager-popup",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,detectCmp:[{exists:"#notice-cookie-block #allow-functional-cookies, #notice-cookie-block #btn-cookie-settings"}],detectPopup:[{visible:"#notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{if:{exists:"#allow-functional-cookies"},then:[{click:"#allow-functional-cookies"}],else:[{waitForThenClick:"#btn-cookie-settings"},{waitForVisible:".modal-body"},{click:'.modal-body input:checked, .switch[data-switch="on"]',all:!0,optional:!0},{click:'[role="dialog"] .modal-footer button'}]}],prehideSelectors:["#btn-cookie-settings"],test:[{eval:"EVAL_COOKIE_MANAGER_POPUP_0"}]},{name:"cookie-notice",prehideSelectors:["#cookie-notice"],cosmetic:!0,detectCmp:[{visible:"#cookie-notice .cookie-notice-container"}],detectPopup:[{visible:"#cookie-notice"}],optIn:[{click:"#cn-accept-cookie"}],optOut:[{hide:"#cookie-notice"}]},{name:"cookie-script",vendorUrl:"https://cookie-script.com/",prehideSelectors:["#cookiescript_injected"],detectCmp:[{exists:"#cookiescript_injected"}],detectPopup:[{visible:"#cookiescript_injected"}],optOut:[{click:"#cookiescript_reject"}],optIn:[{click:"#cookiescript_accept"}]},{name:"cookieacceptbar",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#cookieAcceptBar.cookieAcceptBar"],detectCmp:[{exists:"#cookieAcceptBar.cookieAcceptBar"}],detectPopup:[{visible:"#cookieAcceptBar.cookieAcceptBar"}],optIn:[{waitForThenClick:"#cookieAcceptBarConfirm"}],optOut:[{hide:"#cookieAcceptBar.cookieAcceptBar"}]},{name:"cookiealert",intermediate:!1,prehideSelectors:[],runContext:{frame:!0,main:!0},detectCmp:[{exists:".cookie-alert-extended"}],detectPopup:[{visible:".cookie-alert-extended-modal"}],optIn:[{click:"button[data-controller='cookie-alert/extended/button/accept']"},{eval:"EVAL_COOKIEALERT_0"}],optOut:[{click:"a[data-controller='cookie-alert/extended/detail-link']"},{click:".cookie-alert-configuration-input:checked",all:!0,optional:!0},{click:"button[data-controller='cookie-alert/extended/button/configuration']"},{eval:"EVAL_COOKIEALERT_0"}],test:[{eval:"EVAL_COOKIEALERT_2"}]},{name:"cookieconsent2",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v2.x.x of the library",prehideSelectors:["#cc--main"],detectCmp:[{exists:"#cc--main"}],detectPopup:[{visible:"#cm"},{exists:"#s-all-bn"}],optIn:[{waitForThenClick:"#s-all-bn"}],optOut:[{waitForThenClick:"#s-rall-bn"}],test:[{eval:"EVAL_COOKIECONSENT2_TEST"}]},{name:"cookieconsent3",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v3.x.x of the library",prehideSelectors:["#cc-main"],detectCmp:[{exists:"#cc-main"}],detectPopup:[{visible:"#cc-main .cm-wrapper"}],optIn:[{waitForThenClick:".cm__btn[data-role=all]"}],optOut:[{waitForThenClick:".cm__btn[data-role=necessary]"}],test:[{eval:"EVAL_COOKIECONSENT3_TEST"}]},{name:"cookiefirst.com",prehideSelectors:["#cookiefirst-root,.cookiefirst-root,[aria-labelledby=cookie-preference-panel-title]"],detectCmp:[{exists:"#cookiefirst-root,.cookiefirst-root"}],detectPopup:[{visible:"#cookiefirst-root,.cookiefirst-root"}],optIn:[{click:"button[data-cookiefirst-action=accept]"}],optOut:[{if:{exists:"button[data-cookiefirst-action=adjust]"},then:[{click:"button[data-cookiefirst-action=adjust]"},{waitForVisible:"[data-cookiefirst-widget=modal]",timeout:1e3},{eval:"EVAL_COOKIEFIRST_1"},{wait:1e3},{click:"button[data-cookiefirst-action=save]"}],else:[{click:"button[data-cookiefirst-action=reject]"}]}],test:[{eval:"EVAL_COOKIEFIRST_0"}]},{name:"Cookie Information Banner",prehideSelectors:["#cookie-information-template-wrapper"],detectCmp:[{exists:"#cookie-information-template-wrapper"}],detectPopup:[{visible:"#cookie-information-template-wrapper"}],optIn:[{eval:"EVAL_COOKIEINFORMATION_1"}],optOut:[{hide:"#cookie-information-template-wrapper",comment:"some templates don't hide the banner automatically"},{eval:"EVAL_COOKIEINFORMATION_0"}],test:[{eval:"EVAL_COOKIEINFORMATION_2"}]},{name:"cookieyes",prehideSelectors:[".cky-overlay,.cky-consent-container"],detectCmp:[{exists:".cky-consent-container"}],detectPopup:[{visible:".cky-consent-container"}],optIn:[{waitForThenClick:".cky-consent-container [data-cky-tag=accept-button]"}],optOut:[{if:{exists:".cky-consent-container [data-cky-tag=reject-button]"},then:[{waitForThenClick:".cky-consent-container [data-cky-tag=reject-button]"}],else:[{if:{exists:".cky-consent-container [data-cky-tag=settings-button]"},then:[{click:".cky-consent-container [data-cky-tag=settings-button]"},{waitFor:".cky-modal-open input[type=checkbox]"},{click:".cky-modal-open input[type=checkbox]:checked",all:!0,optional:!0},{waitForThenClick:".cky-modal [data-cky-tag=detail-save-button]"}],else:[{hide:".cky-consent-container,.cky-overlay"}]}]}],test:[{eval:"EVAL_COOKIEYES_0"}]},{name:"corona-in-zahlen.de",prehideSelectors:[".cookiealert"],detectCmp:[{exists:".cookiealert"}],detectPopup:[{visible:".cookiealert"}],optOut:[{click:".configurecookies"},{click:".confirmcookies"}],optIn:[{click:".acceptcookies"}]},{name:"crossfit-com",cosmetic:!0,prehideSelectors:['body #modal > div > div[class^="_wrapper_"]'],detectCmp:[{exists:'body #modal > div > div[class^="_wrapper_"]'}],detectPopup:[{visible:'body #modal > div > div[class^="_wrapper_"]'}],optIn:[{click:'button[aria-label="accept cookie policy"]'}],optOut:[{hide:'body #modal > div > div[class^="_wrapper_"]'}]},{name:"csu-landtag-de",runContext:{urlPattern:"^https://(www|)?\\.csu-landtag\\.de"},prehideSelectors:["#cookie-disclaimer"],detectCmp:[{exists:"#cookie-disclaimer"}],detectPopup:[{visible:"#cookie-disclaimer"}],optIn:[{click:"#cookieall"}],optOut:[{click:"#cookiesel"}]},{name:"dailymotion-us",cosmetic:!0,prehideSelectors:['div[class*="CookiePopup__desktopContainer"]:has(div[class*="CookiePopup"])'],detectCmp:[{exists:'div[class*="CookiePopup__desktopContainer"]'}],detectPopup:[{visible:'div[class*="CookiePopup__desktopContainer"]'}],optIn:[{click:'div[class*="CookiePopup__desktopContainer"] > button > span'}],optOut:[{hide:'div[class*="CookiePopup__desktopContainer"]'}]},{name:"dailymotion.com",runContext:{urlPattern:"^https://(www\\.)?dailymotion\\.com/"},prehideSelectors:['div[class*="Overlay__container"]:has(div[class*="TCF2Popup"])'],detectCmp:[{exists:'div[class*="TCF2Popup"]'}],detectPopup:[{visible:'[class*="TCF2Popup"] a[href^="https://www.dailymotion.com/legal/cookiemanagement"]'}],optIn:[{waitForThenClick:'button[class*="TCF2Popup__button"]:not([class*="TCF2Popup__personalize"])'}],optOut:[{waitForThenClick:'button[class*="TCF2ContinueWithoutAcceptingButton"]'}],test:[{eval:"EVAL_DAILYMOTION_0"}]},{name:"deepl.com",prehideSelectors:[".dl_cookieBanner_container"],detectCmp:[{exists:".dl_cookieBanner_container"}],detectPopup:[{visible:".dl_cookieBanner_container"}],optOut:[{click:".dl_cookieBanner--buttonSelected"}],optIn:[{click:".dl_cookieBanner--buttonAll"}]},{name:"delta.com",runContext:{urlPattern:"^https://www\\.delta\\.com/"},cosmetic:!0,prehideSelectors:["ngc-cookie-banner"],detectCmp:[{exists:"div.cookie-footer-container"}],detectPopup:[{visible:"div.cookie-footer-container"}],optIn:[{click:" button.cookie-close-icon"}],optOut:[{hide:"div.cookie-footer-container"}]},{name:"dmgmedia-us",prehideSelectors:["#mol-ads-cmp-iframe, div.mol-ads-cmp > form > div"],detectCmp:[{exists:"div.mol-ads-cmp > form > div"}],detectPopup:[{waitForVisible:"div.mol-ads-cmp > form > div"}],optIn:[{waitForThenClick:"button.mol-ads-cmp--btn-primary"}],optOut:[{waitForThenClick:"div.mol-ads-ccpa--message > u > a"},{waitForVisible:".mol-ads-cmp--modal-dialog"},{waitForThenClick:"a.mol-ads-cmp-footer-privacy"},{waitForThenClick:"button.mol-ads-cmp--btn-secondary"}]},{name:"dmgmedia",prehideSelectors:['[data-project="mol-fe-cmp"]'],detectCmp:[{exists:'[data-project="mol-fe-cmp"]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"]'}],optIn:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=primary]'}],optOut:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=basic]'},{waitForVisible:'[data-project="mol-fe-cmp"] div[class*="tabContent"]'},{waitForThenClick:'[data-project="mol-fe-cmp"] div[class*="toggle"][class*="enabled"]',all:!0},{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=white]'}]},{name:"dndbeyond",vendorUrl:"https://www.dndbeyond.com/",runContext:{urlPattern:"^https://(www\\.)?dndbeyond\\.com/"},prehideSelectors:["[id^=cookie-consent-banner]"],detectCmp:[{exists:"[id^=cookie-consent-banner]"}],detectPopup:[{visible:"[id^=cookie-consent-banner]"}],optIn:[{waitForThenClick:"#cookie-consent-granted"}],optOut:[{waitForThenClick:"#cookie-consent-denied"}],test:[{eval:"EVAL_DNDBEYOND_TEST"}]},{name:"Drupal",detectCmp:[{exists:"#drupalorg-crosssite-gdpr"}],detectPopup:[{visible:"#drupalorg-crosssite-gdpr"}],optOut:[{click:".no"}],optIn:[{click:".yes"}]},{name:"WP DSGVO Tools",link:"https://wordpress.org/plugins/shapepress-dsgvo/",prehideSelectors:[".sp-dsgvo"],cosmetic:!0,detectCmp:[{exists:".sp-dsgvo.sp-dsgvo-popup-overlay"}],detectPopup:[{visible:".sp-dsgvo.sp-dsgvo-popup-overlay",check:"any"}],optIn:[{click:".sp-dsgvo-privacy-btn-accept-all",all:!0}],optOut:[{hide:".sp-dsgvo.sp-dsgvo-popup-overlay"}],test:[{eval:"EVAL_DSGVO_0"}]},{name:"dunelm.com",prehideSelectors:["div[data-testid=cookie-consent-modal-backdrop]"],detectCmp:[{exists:"div[data-testid=cookie-consent-message-contents]"}],detectPopup:[{visible:"div[data-testid=cookie-consent-message-contents]"}],optIn:[{click:'[data-testid="cookie-consent-allow-all"]'}],optOut:[{click:"button[data-testid=cookie-consent-adjust-settings]"},{click:"button[data-testid=cookie-consent-preferences-save]"}],test:[{eval:"EVAL_DUNELM_0"}]},{name:"ecosia",vendorUrl:"https://www.ecosia.org/",runContext:{urlPattern:"^https://www\\.ecosia\\.org/"},prehideSelectors:[".cookie-wrapper"],detectCmp:[{exists:".cookie-wrapper > .cookie-notice"}],detectPopup:[{visible:".cookie-wrapper > .cookie-notice"}],optIn:[{waitForThenClick:"[data-test-id=cookie-notice-accept]"}],optOut:[{waitForThenClick:"[data-test-id=cookie-notice-reject]"}]},{name:"etsy",prehideSelectors:["#gdpr-single-choice-overlay","#gdpr-privacy-settings"],detectCmp:[{exists:"#gdpr-single-choice-overlay"}],detectPopup:[{visible:"#gdpr-single-choice-overlay"}],optOut:[{click:"button[data-gdpr-open-full-settings]"},{waitForVisible:".gdpr-overlay-body input",timeout:3e3},{wait:1e3},{eval:"EVAL_ETSY_0"},{eval:"EVAL_ETSY_1"}],optIn:[{click:"button[data-gdpr-single-choice-accept]"}]},{name:"eu-cookie-compliance-banner",detectCmp:[{exists:"body.eu-cookie-compliance-popup-open"}],detectPopup:[{exists:"body.eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{if:{visible:".decline-button,.eu-cookie-compliance-save-preferences-button"},then:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button"}]},{hide:".eu-cookie-compliance-banner-info, #sliding-popup"}],test:[{eval:"EVAL_EU_COOKIE_COMPLIANCE_0"}]},{name:"EU Cookie Law",prehideSelectors:[".pea_cook_wrapper,.pea_cook_more_info_popover"],cosmetic:!0,detectCmp:[{exists:".pea_cook_wrapper"}],detectPopup:[{wait:500},{visible:".pea_cook_wrapper"}],optIn:[{click:"#pea_cook_btn"}],optOut:[{hide:".pea_cook_wrapper"}],test:[{eval:"EVAL_EU_COOKIE_LAW_0"}]},{name:"europa-eu",vendorUrl:"https://ec.europa.eu/",runContext:{urlPattern:"^https://[^/]*europa\\.eu/"},prehideSelectors:["#cookie-consent-banner"],detectCmp:[{exists:".cck-container"}],detectPopup:[{visible:".cck-container"}],optIn:[{waitForThenClick:'.cck-actions-button[href="#accept"]'}],optOut:[{waitForThenClick:'.cck-actions-button[href="#refuse"]',hide:".cck-container"}]},{name:"EZoic",prehideSelectors:["#ez-cookie-dialog-wrapper"],detectCmp:[{exists:"#ez-cookie-dialog-wrapper"}],detectPopup:[{visible:"#ez-cookie-dialog-wrapper"}],optIn:[{click:"#ez-accept-all",optional:!0},{eval:"EVAL_EZOIC_0",optional:!0}],optOut:[{wait:500},{click:"#ez-manage-settings"},{waitFor:"#ez-cookie-dialog input[type=checkbox]"},{click:"#ez-cookie-dialog input[type=checkbox]:checked",all:!0},{click:"#ez-save-settings"}],test:[{eval:"EVAL_EZOIC_1"}]},{name:"facebook",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?facebook\\.com/"},prehideSelectors:['div[data-testid="cookie-policy-manage-dialog"]'],detectCmp:[{exists:'div[data-testid="cookie-policy-manage-dialog"]'}],detectPopup:[{visible:'div[data-testid="cookie-policy-manage-dialog"]'}],optIn:[{waitForThenClick:'button[data-cookiebanner="accept_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}],optOut:[{waitForThenClick:'button[data-cookiebanner="accept_only_essential_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}]},{name:"fides",vendorUrl:"https://github.com/ethyca/fides",prehideSelectors:["#fides-overlay"],detectCmp:[{exists:"#fides-overlay #fides-banner"}],detectPopup:[{visible:"#fides-overlay #fides-banner"}],optIn:[{waitForThenClick:'#fides-banner [data-testid="Accept all-btn"]'}],optOut:[{waitForThenClick:'#fides-banner [data-testid="Reject all-btn"]'}]},{name:"funding-choices",prehideSelectors:[".fc-consent-root,.fc-dialog-container,.fc-dialog-overlay,.fc-dialog-content"],detectCmp:[{exists:".fc-consent-root"}],detectPopup:[{exists:".fc-dialog-container"}],optOut:[{click:".fc-cta-do-not-consent,.fc-cta-manage-options"},{click:".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked",all:!0,optional:!0},{click:".fc-confirm-choices",optional:!0}],optIn:[{click:".fc-cta-consent"}]},{name:"geeks-for-geeks",runContext:{urlPattern:"^https://www\\.geeksforgeeks\\.org/"},cosmetic:!0,prehideSelectors:[".cookie-consent"],detectCmp:[{exists:".cookie-consent"}],detectPopup:[{visible:".cookie-consent"}],optIn:[{click:".cookie-consent button.consent-btn"}],optOut:[{hide:".cookie-consent"}]},{name:"generic-cosmetic",cosmetic:!0,prehideSelectors:["#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"],detectCmp:[{exists:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],detectPopup:[{visible:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],optIn:[],optOut:[{hide:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}]},{name:"google-consent-standalone",prehideSelectors:[],detectCmp:[{exists:'a[href^="https://policies.google.com/technologies/cookies"'},{exists:'form[action^="https://consent.google."][action$=".com/save"]'}],detectPopup:[{visible:'a[href^="https://policies.google.com/technologies/cookies"'}],optIn:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=false]) button'}],optOut:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=true]) button'}]},{name:"google.com",prehideSelectors:[".HTjtHe#xe7COe"],detectCmp:[{exists:".HTjtHe#xe7COe"},{exists:'.HTjtHe#xe7COe a[href^="https://policies.google.com/technologies/cookies"]'}],detectPopup:[{visible:".HTjtHe#xe7COe button#W0wltc"}],optIn:[{waitForThenClick:".HTjtHe#xe7COe button#L2AGLb"}],optOut:[{waitForThenClick:".HTjtHe#xe7COe button#W0wltc"}],test:[{eval:"EVAL_GOOGLE_0"}]},{name:"gov.uk",detectCmp:[{exists:"#global-cookie-message"}],detectPopup:[{exists:"#global-cookie-message"}],optIn:[{click:"button[data-accept-cookies=true]"}],optOut:[{click:"button[data-reject-cookies=true],#reject-cookies"},{click:"button[data-hide-cookie-banner=true],#hide-cookie-decision"}]},{name:"hashicorp",vendorUrl:"https://hashicorp.com/",runContext:{urlPattern:"^https://[^.]*\\.hashicorp\\.com/"},prehideSelectors:["[data-testid=consent-banner]"],detectCmp:[{exists:"[data-testid=consent-banner]"}],detectPopup:[{visible:"[data-testid=consent-banner]"}],optIn:[{waitForThenClick:"[data-testid=accept]"}],optOut:[{waitForThenClick:"[data-testid=manage-preferences]"},{waitForThenClick:"[data-testid=consent-mgr-dialog] [data-ga-button=save-preferences]"}]},{name:"healthline-media",prehideSelectors:["#modal-host > div.no-hash > div.window-wrapper"],detectCmp:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],detectPopup:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],optIn:[{click:"#modal-host > div.no-hash > div.window-wrapper > div:last-child button"}],optOut:[{if:{exists:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'},then:[{click:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'}],else:[{waitForVisible:"div#__next"},{click:"#__next div:nth-child(1) > button:first-child"}]}]},{name:"hema",prehideSelectors:[".cookie-modal"],detectCmp:[{visible:".cookie-modal .cookie-accept-btn"}],detectPopup:[{visible:".cookie-modal .cookie-accept-btn"}],optIn:[{waitForThenClick:".cookie-modal .cookie-accept-btn"}],optOut:[{waitForThenClick:".cookie-modal .js-cookie-reject-btn"}],test:[{eval:"EVAL_HEMA_TEST_0"}]},{name:"hetzner.com",runContext:{urlPattern:"^https://www\\.hetzner\\.com/"},prehideSelectors:["#CookieConsent"],detectCmp:[{exists:"#CookieConsent"}],detectPopup:[{visible:"#CookieConsent"}],optIn:[{click:"#CookieConsentGiven"}],optOut:[{click:"#CookieConsentDeclined"}]},{name:"hl.co.uk",prehideSelectors:[".cookieModalContent","#cookie-banner-overlay"],detectCmp:[{exists:"#cookie-banner-overlay"}],detectPopup:[{exists:"#cookie-banner-overlay"}],optIn:[{click:"#acceptCookieButton"}],optOut:[{click:"#manageCookie"},{hide:".cookieSettingsModal"},{waitFor:"#AOCookieToggle"},{click:"#AOCookieToggle[aria-pressed=true]",optional:!0},{waitFor:"#TPCookieToggle"},{click:"#TPCookieToggle[aria-pressed=true]",optional:!0},{click:"#updateCookieButton"}]},{name:"hu-manity",vendorUrl:"https://hu-manity.co/",prehideSelectors:["#hu.hu-wrapper"],detectCmp:[{exists:"#hu.hu-visible"}],detectPopup:[{visible:"#hu.hu-visible"}],optIn:[{waitForThenClick:"[data-hu-action=cookies-notice-consent-choices-3]"},{waitForThenClick:"#hu-cookies-save"}],optOut:[{waitForThenClick:"#hu-cookies-save"}]},{name:"hubspot",detectCmp:[{exists:"#hs-eu-cookie-confirmation"}],detectPopup:[{visible:"#hs-eu-cookie-confirmation"}],optIn:[{click:"#hs-eu-confirmation-button"}],optOut:[{click:"#hs-eu-decline-button"}]},{name:"indeed.com",cosmetic:!0,prehideSelectors:["#CookiePrivacyNotice"],detectCmp:[{exists:"#CookiePrivacyNotice"}],detectPopup:[{visible:"#CookiePrivacyNotice"}],optIn:[{click:"#CookiePrivacyNotice button[data-gnav-element-name=CookiePrivacyNoticeOk]"}],optOut:[{hide:"#CookiePrivacyNotice"}]},{name:"ing.de",runContext:{urlPattern:"^https://www\\.ing\\.de/"},cosmetic:!0,prehideSelectors:['div[slot="backdrop"]'],detectCmp:[{exists:'[data-tag-name="ing-cc-dialog-frame"]'}],detectPopup:[{visible:'[data-tag-name="ing-cc-dialog-frame"]'}],optIn:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="accept"]']}],optOut:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="more"]']}]},{name:"instagram",vendorUrl:"https://instagram.com",runContext:{urlPattern:"^https://www\\.instagram\\.com/"},prehideSelectors:[".x78zum5.xdt5ytf.xg6iff7.x1n2onr6"],detectCmp:[{exists:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],detectPopup:[{visible:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],optIn:[{waitForThenClick:"._a9--._a9_0"}],optOut:[{waitForThenClick:"._a9--._a9_1"},{wait:2e3}]},{name:"ionos.de",prehideSelectors:[".privacy-consent--backdrop",".privacy-consent--modal"],detectCmp:[{exists:".privacy-consent--modal"}],detectPopup:[{visible:".privacy-consent--modal"}],optIn:[{click:"#selectAll"}],optOut:[{click:".footer-config-link"},{click:"#confirmSelection"}]},{name:"itopvpn.com",cosmetic:!0,prehideSelectors:[".pop-cookie"],detectCmp:[{exists:".pop-cookie"}],detectPopup:[{exists:".pop-cookie"}],optIn:[{click:"#_pcookie"}],optOut:[{hide:".pop-cookie"}]},{name:"iubenda",prehideSelectors:["#iubenda-cs-banner"],detectCmp:[{exists:"#iubenda-cs-banner"}],detectPopup:[{visible:".iubenda-cs-accept-btn"}],optIn:[{click:".iubenda-cs-accept-btn"}],optOut:[{click:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{click:"#iubFooterBtn"}],test:[{eval:"EVAL_IUBENDA_1"}]},{name:"iWink",prehideSelectors:["body.cookies-request #cookie-bar"],detectCmp:[{exists:"body.cookies-request #cookie-bar"}],detectPopup:[{visible:"body.cookies-request #cookie-bar"}],optIn:[{waitForThenClick:"body.cookies-request #cookie-bar .allow-cookies"}],optOut:[{waitForThenClick:"body.cookies-request #cookie-bar .disallow-cookies"}],test:[{eval:"EVAL_IWINK_TEST"}]},{name:"jdsports",vendorUrl:"https://www.jdsports.co.uk/",runContext:{urlPattern:"^https://(www|m)\\.jdsports\\."},prehideSelectors:[".miniConsent,#PrivacyPolicyBanner"],detectCmp:[{exists:".miniConsent,#PrivacyPolicyBanner"}],detectPopup:[{visible:".miniConsent,#PrivacyPolicyBanner"}],optIn:[{waitForThenClick:".miniConsent .accept-all-cookies"}],optOut:[{if:{exists:"#PrivacyPolicyBanner"},then:[{hide:"#PrivacyPolicyBanner"}],else:[{waitForThenClick:"#cookie-settings"},{waitForThenClick:"#reject-all-cookies"}]}]},{name:"johnlewis.com",prehideSelectors:["div[class^=pecr-cookie-banner-]"],detectCmp:[{exists:"div[class^=pecr-cookie-banner-]"}],detectPopup:[{exists:"div[class^=pecr-cookie-banner-]"}],optOut:[{click:"button[data-test^=manage-cookies]"},{wait:"500"},{click:"label[data-test^=toggle][class*=checked]:not([class*=disabled])",all:!0,optional:!0},{click:"button[data-test=save-preferences]"}],optIn:[{click:"button[data-test=allow-all]"}]},{name:"jquery.cookieBar",vendorUrl:"https://github.com/kovarp/jquery.cookieBar",prehideSelectors:[".cookie-bar"],cosmetic:!0,detectCmp:[{exists:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons"}],detectPopup:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"any"}],optIn:[{click:".cookie-bar .cookie-bar__btn"}],optOut:[{hide:".cookie-bar"}],test:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"none"},{eval:"EVAL_JQUERY_COOKIEBAR_0"}]},{name:"justwatch.com",prehideSelectors:[".consent-banner"],detectCmp:[{exists:".consent-banner .consent-banner__actions"}],detectPopup:[{visible:".consent-banner .consent-banner__actions"}],optIn:[{click:".consent-banner__actions button.basic-button.primary"}],optOut:[{click:".consent-banner__actions button.basic-button.secondary"},{waitForThenClick:".consent-modal__footer button.basic-button.secondary"},{waitForThenClick:".consent-modal ion-content > div > a:nth-child(9)"},{click:"label.consent-switch input[type=checkbox]:checked",all:!0,optional:!0},{waitForVisible:".consent-modal__footer button.basic-button.primary"},{click:".consent-modal__footer button.basic-button.primary"}]},{name:"ketch",vendorUrl:"https://www.ketch.com",runContext:{frame:!1,main:!0},intermediate:!1,prehideSelectors:["#lanyard_root div[role='dialog']"],detectCmp:[{exists:"#lanyard_root div[role='dialog']"}],detectPopup:[{visible:"#lanyard_root div[role='dialog']"}],optIn:[{if:{exists:"#lanyard_root button[class='confirmButton']"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"},{click:"#lanyard_root button[class='confirmButton']"}],else:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"}]}],optOut:[{if:{exists:"#lanyard_root [aria-describedby=banner-description]"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > button[class*=secondaryButton]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1)"}]}]},{name:"kleinanzeigen-de",runContext:{urlPattern:"^https?://(www\\.)?kleinanzeigen\\.de"},prehideSelectors:["#gdpr-banner-container"],detectCmp:[{any:[{exists:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{exists:"#ConsentManagementPage"}]}],detectPopup:[{any:[{visible:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{visible:"#ConsentManagementPage"}]}],optIn:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-accept]"}],else:[{click:"#ConsentManagementPage .Button-primary"}]}],optOut:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"}],else:[{click:"#ConsentManagementPage .Button-secondary"}]}]},{name:"lightbox",prehideSelectors:[".darken-layer.open,.lightbox.lightbox--cookie-consent"],detectCmp:[{exists:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],detectPopup:[{visible:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],optOut:[{click:".cookie-consent__footer > button[type='submit']:not([data-button='selectAll'])"}],optIn:[{click:".cookie-consent__footer > button[type='submit'][data-button='selectAll']"}]},{name:"lineagrafica",vendorUrl:"https://addons.prestashop.com/en/legal/8734-eu-cookie-law-gdpr-banner-blocker.html",cosmetic:!0,prehideSelectors:["#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"],detectCmp:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],detectPopup:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],optIn:[{waitForThenClick:"#lgcookieslaw_accept"}],optOut:[{hide:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}]},{name:"linkedin.com",prehideSelectors:[".artdeco-global-alert[type=COOKIE_CONSENT]"],detectCmp:[{exists:".artdeco-global-alert[type=COOKIE_CONSENT]"}],detectPopup:[{visible:".artdeco-global-alert[type=COOKIE_CONSENT]"}],optIn:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"}],optOut:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"}],test:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT]",check:"none"}]},{name:"livejasmin",vendorUrl:"https://www.livejasmin.com/",runContext:{urlPattern:"^https://(m|www)\\.livejasmin\\.com/"},prehideSelectors:["#consent_modal"],detectCmp:[{exists:"#consent_modal"}],detectPopup:[{visible:"#consent_modal"}],optIn:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:first-of-type"}],optOut:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:nth-of-type(2)"},{waitForVisible:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent]"},{click:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] input[data-testid=PrivacyPreferenceCenterWithConsentCookieSwitch]:checked",optional:!0,all:!0},{waitForThenClick:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] button[data-testid=ButtonStyledButton]:last-child"}]},{name:"macpaw.com",cosmetic:!0,prehideSelectors:['div[data-banner="cookies"]'],detectCmp:[{exists:'div[data-banner="cookies"]'}],detectPopup:[{exists:'div[data-banner="cookies"]'}],optIn:[{click:'button[data-banner-close="cookies"]'}],optOut:[{hide:'div[data-banner="cookies"]'}]},{name:"marksandspencer.com",cosmetic:!0,detectCmp:[{exists:".navigation-cookiebbanner"}],detectPopup:[{visible:".navigation-cookiebbanner"}],optOut:[{hide:".navigation-cookiebbanner"}],optIn:[{click:".navigation-cookiebbanner__submit"}]},{name:"mediamarkt.de",prehideSelectors:["div[aria-labelledby=pwa-consent-layer-title]","div[class^=StyledConsentLayerWrapper-]"],detectCmp:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],detectPopup:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],optOut:[{click:"button[data-test^=pwa-consent-layer-deny-all]"}],optIn:[{click:"button[data-test^=pwa-consent-layer-accept-all"}]},{name:"Mediavine",prehideSelectors:['[data-name="mediavine-gdpr-cmp"]'],detectCmp:[{exists:'[data-name="mediavine-gdpr-cmp"]'}],detectPopup:[{wait:500},{visible:'[data-name="mediavine-gdpr-cmp"]'}],optIn:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [format="primary"]'}],optOut:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [data-view="manageSettings"]'},{waitFor:'[data-name="mediavine-gdpr-cmp"] input[type=checkbox]'},{eval:"EVAL_MEDIAVINE_0",optional:!0},{click:'[data-name="mediavine-gdpr-cmp"] [format="secondary"]'}]},{name:"microsoft.com",prehideSelectors:["#wcpConsentBannerCtrl"],detectCmp:[{exists:"#wcpConsentBannerCtrl"}],detectPopup:[{exists:"#wcpConsentBannerCtrl"}],optOut:[{eval:"EVAL_MICROSOFT_0"}],optIn:[{eval:"EVAL_MICROSOFT_1"}],test:[{eval:"EVAL_MICROSOFT_2"}]},{name:"midway-usa",runContext:{urlPattern:"^https://www\\.midwayusa\\.com/"},cosmetic:!0,prehideSelectors:["#cookie-container"],detectCmp:[{exists:['div[aria-label="Cookie Policy Banner"]']}],detectPopup:[{visible:"#cookie-container"}],optIn:[{click:"button#cookie-btn"}],optOut:[{hide:'div[aria-label="Cookie Policy Banner"]'}]},{name:"moneysavingexpert.com",detectCmp:[{exists:"dialog[data-testid=accept-our-cookies-dialog]"}],detectPopup:[{visible:"dialog[data-testid=accept-our-cookies-dialog]"}],optIn:[{click:"#banner-accept"}],optOut:[{click:"#banner-manage"},{click:"#pc-confirm"}]},{name:"monzo.com",prehideSelectors:[".cookie-alert, cookie-alert__content"],detectCmp:[{exists:'div.cookie-alert[role="dialog"]'},{exists:'a[href*="monzo"]'}],detectPopup:[{visible:".cookie-alert__content"}],optIn:[{click:".js-accept-cookie-policy"}],optOut:[{click:".js-decline-cookie-policy"}]},{name:"Moove",prehideSelectors:["#moove_gdpr_cookie_info_bar"],detectCmp:[{exists:"#moove_gdpr_cookie_info_bar"}],detectPopup:[{visible:"#moove_gdpr_cookie_info_bar"}],optIn:[{waitForThenClick:".moove-gdpr-infobar-allow-all"}],optOut:[{if:{exists:"#moove_gdpr_cookie_info_bar .change-settings-button"},then:[{click:"#moove_gdpr_cookie_info_bar .change-settings-button"},{waitForVisible:"#moove_gdpr_cookie_modal"},{eval:"EVAL_MOOVE_0"},{click:".moove-gdpr-modal-save-settings"}],else:[{hide:"#moove_gdpr_cookie_info_bar"}]}],test:[{visible:"#moove_gdpr_cookie_info_bar",check:"none"}]},{name:"national-lottery.co.uk",detectCmp:[{exists:".cuk_cookie_consent"}],detectPopup:[{visible:".cuk_cookie_consent",check:"any"}],optOut:[{click:".cuk_cookie_consent_manage_pref"},{click:".cuk_cookie_consent_save_pref"},{click:".cuk_cookie_consent_close"}],optIn:[{click:".cuk_cookie_consent_accept_all"}]},{name:"nba.com",runContext:{urlPattern:"^https://(www\\.)?nba.com/"},cosmetic:!0,prehideSelectors:["#onetrust-banner-sdk"],detectCmp:[{exists:"#onetrust-banner-sdk"}],detectPopup:[{visible:"#onetrust-banner-sdk"}],optIn:[{click:"#onetrust-accept-btn-handler"}],optOut:[{hide:"#onetrust-banner-sdk"}]},{name:"netflix.de",detectCmp:[{exists:"#cookie-disclosure"}],detectPopup:[{visible:".cookie-disclosure-message",check:"any"}],optIn:[{click:".btn-accept"}],optOut:[{hide:"#cookie-disclosure"},{click:".btn-reject"}]},{name:"nhs.uk",prehideSelectors:["#nhsuk-cookie-banner"],detectCmp:[{exists:"#nhsuk-cookie-banner"}],detectPopup:[{exists:"#nhsuk-cookie-banner"}],optOut:[{click:"#nhsuk-cookie-banner__link_accept"}],optIn:[{click:"#nhsuk-cookie-banner__link_accept_analytics"}]},{name:"notice-cookie",prehideSelectors:[".button--notice"],cosmetic:!0,detectCmp:[{exists:".notice--cookie"}],detectPopup:[{visible:".notice--cookie"}],optIn:[{click:".button--notice"}],optOut:[{hide:".notice--cookie"}]},{name:"nrk.no",cosmetic:!0,prehideSelectors:[".nrk-masthead__info-banner--cookie"],detectCmp:[{exists:".nrk-masthead__info-banner--cookie"}],detectPopup:[{exists:".nrk-masthead__info-banner--cookie"}],optIn:[{click:"div.nrk-masthead__info-banner--cookie button > span:has(+ svg.nrk-close)"}],optOut:[{hide:".nrk-masthead__info-banner--cookie"}]},{name:"obi.de",prehideSelectors:[".disc-cp--active"],detectCmp:[{exists:".disc-cp-modal__modal"}],detectPopup:[{visible:".disc-cp-modal__modal"}],optIn:[{click:".js-disc-cp-accept-all"}],optOut:[{click:".js-disc-cp-deny-all"}]},{name:"om",vendorUrl:"https://olli-machts.de/en/extension/cookie-manager",prehideSelectors:[".tx-om-cookie-consent"],detectCmp:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],detectPopup:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],optIn:[{waitForThenClick:"[data-omcookie-panel-save=all]"}],optOut:[{if:{exists:"[data-omcookie-panel-save=min]"},then:[{waitForThenClick:"[data-omcookie-panel-save=min]"}],else:[{click:"input[data-omcookie-panel-grp]:checked:not(:disabled)",all:!0,optional:!0},{waitForThenClick:"[data-omcookie-panel-save=save]"}]}]},{name:"onlyFans.com",prehideSelectors:["div.b-cookies-informer"],detectCmp:[{exists:"div.b-cookies-informer"}],detectPopup:[{exists:"div.b-cookies-informer"}],optIn:[{click:"div.b-cookies-informer__nav > button:nth-child(2)"}],optOut:[{click:"div.b-cookies-informer__nav > button:nth-child(1)"},{click:'div.b-cookies-informer__switchers > div:nth-child(2) > div[at-attr="checkbox"] > span.b-input-radio__container > input[type="checkbox"]'},{click:"div.b-cookies-informer__nav > button"}]},{name:"openli",vendorUrl:"https://openli.com",prehideSelectors:[".legalmonster-cleanslate"],detectCmp:[{exists:".legalmonster-cleanslate"}],detectPopup:[{visible:".legalmonster-cleanslate #lm-cookie-wall-container",check:"any"}],optIn:[{waitForThenClick:"#lm-accept-all"}],optOut:[{waitForThenClick:"#lm-accept-necessary"}]},{name:"opera.com",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:"#cookie-consent .manage-cookies__btn"}],detectPopup:[{visible:"#cookie-consent .cookie-basic-consent__btn"}],optIn:[{waitForThenClick:"#cookie-consent .cookie-basic-consent__btn"}],optOut:[{waitForThenClick:"#cookie-consent .manage-cookies__btn"},{waitForThenClick:"#cookie-consent .active.marketing_option_switch.cookie-consent__switch",all:!0},{waitForThenClick:"#cookie-consent .cookie-selection__btn"}],test:[{eval:"EVAL_OPERA_0"}]},{name:"osano",prehideSelectors:[".osano-cm-window,.osano-cm-dialog"],detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{waitForThenClick:".osano-cm-denyAll"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"ourworldindata",vendorUrl:"https://ourworldindata.org/",runContext:{urlPattern:"^https://ourworldindata\\.org/"},prehideSelectors:[".cookie-manager"],detectCmp:[{exists:".cookie-manager"}],detectPopup:[{visible:".cookie-manager .cookie-notice.open"}],optIn:[{waitForThenClick:".cookie-notice [data-test=accept]"}],optOut:[{waitForThenClick:".cookie-notice [data-test=reject]"}]},{name:"pabcogypsum",vendorUrl:"https://unknown",prehideSelectors:[".js-cookie-notice:has(#cookie_settings-form)"],detectCmp:[{exists:".js-cookie-notice #cookie_settings-form"}],detectPopup:[{visible:".js-cookie-notice #cookie_settings-form"}],optIn:[{waitForThenClick:".js-cookie-notice button[value=allow]"}],optOut:[{waitForThenClick:".js-cookie-notice button[value=disable]"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-sheet-content #formContent"},{click:"#formContent .cookiepref-11m2iee-checkbox_base input:checked",all:!0,optional:!0},{click:".confirmCookie #submitCookiesBtn"}]}]},{name:"paypal.com",prehideSelectors:["#gdprCookieBanner"],detectCmp:[{exists:"#gdprCookieBanner"}],detectPopup:[{visible:"#gdprCookieContent_wrapper"}],optIn:[{click:"#acceptAllButton"}],optOut:[{wait:200},{click:".gdprCookieBanner_decline-button"}],test:[{wait:500},{eval:"EVAL_PAYPAL_0"}]},{name:"pinetools.com",cosmetic:!0,prehideSelectors:["#aviso_cookies"],detectCmp:[{exists:"#aviso_cookies"}],detectPopup:[{exists:".lang_en #aviso_cookies"}],optIn:[{click:"#aviso_cookies .a_boton_cerrar"}],optOut:[{hide:"#aviso_cookies"}]},{name:"pmc",cosmetic:!0,prehideSelectors:["#pmc-pp-tou--notice"],detectCmp:[{exists:"#pmc-pp-tou--notice"}],detectPopup:[{visible:"#pmc-pp-tou--notice"}],optIn:[{click:"span.pmc-pp-tou--notice-close-btn"}],optOut:[{hide:"#pmc-pp-tou--notice"}]},{name:"pornhub.com",runContext:{urlPattern:"^https://(www\\.)?pornhub\\.com/"},cosmetic:!0,prehideSelectors:[".cookiesBanner"],detectCmp:[{exists:".cookiesBanner"}],detectPopup:[{visible:".cookiesBanner"}],optIn:[{click:".cookiesBanner .okButton"}],optOut:[{hide:".cookiesBanner"}]},{name:"pornpics.com",cosmetic:!0,prehideSelectors:["#cookie-contract"],detectCmp:[{exists:"#cookie-contract"}],detectPopup:[{visible:"#cookie-contract"}],optIn:[{click:"#cookie-contract .icon-cross"}],optOut:[{hide:"#cookie-contract"}]},{name:"PrimeBox CookieBar",prehideSelectors:["#cookie-bar"],detectCmp:[{exists:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy"}],detectPopup:[{visible:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy",check:"any"}],optIn:[{waitForThenClick:"#cookie-bar .cb-enable"}],optOut:[{click:"#cookie-bar .cb-disable",optional:!0},{hide:"#cookie-bar"}],test:[{eval:"EVAL_PRIMEBOX_0"}]},{name:"privacymanager.io",prehideSelectors:["#gdpr-consent-tool-wrapper",'iframe[src^="https://cmp-consent-tool.privacymanager.io"]'],runContext:{urlPattern:"^https://cmp-consent-tool\\.privacymanager\\.io/",main:!1,frame:!0},detectCmp:[{exists:"button#save"}],detectPopup:[{visible:"button#save"}],optIn:[{click:"button#save"}],optOut:[{if:{exists:"#denyAll"},then:[{click:"#denyAll"},{waitForThenClick:".okButton"}],else:[{waitForThenClick:"#manageSettings"},{waitFor:".purposes-overview-list"},{waitFor:"button#saveAndExit"},{click:"span[role=checkbox][aria-checked=true]",all:!0,optional:!0},{click:"button#saveAndExit"}]}]},{name:"productz.com",vendorUrl:"https://productz.com/",runContext:{urlPattern:"^https://productz\\.com/"},prehideSelectors:[],detectCmp:[{exists:".c-modal.is-active"}],detectPopup:[{visible:".c-modal.is-active"}],optIn:[{waitForThenClick:".c-modal.is-active .is-accept"}],optOut:[{waitForThenClick:".c-modal.is-active .is-dismiss"}]},{name:"pubtech",prehideSelectors:["#pubtech-cmp"],detectCmp:[{exists:"#pubtech-cmp"}],detectPopup:[{visible:"#pubtech-cmp #pt-actions"}],optIn:[{if:{exists:"#pt-accept-all"},then:[{click:"#pubtech-cmp #pt-actions #pt-accept-all"}],else:[{click:"#pubtech-cmp #pt-actions button:nth-of-type(2)"}]}],optOut:[{click:"#pubtech-cmp #pt-close"}],test:[{eval:"EVAL_PUBTECH_0"}]},{name:"quantcast",prehideSelectors:["#qc-cmp2-main,#qc-cmp2-container"],detectCmp:[{exists:"#qc-cmp2-container"}],detectPopup:[{visible:"#qc-cmp2-ui"}],optOut:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]'},{waitFor:"#qc-cmp2-ui"},{click:'.qc-cmp2-toggle-switch > button[aria-checked="true"]',all:!0,optional:!0},{click:'.qc-cmp2-main button[aria-label="REJECT ALL"]',optional:!0},{waitForThenClick:'.qc-cmp2-main button[aria-label="SAVE & EXIT"],.qc-cmp2-buttons-desktop > button[mode="primary"]',timeout:5e3}],optIn:[{click:'.qc-cmp2-summary-buttons > button[mode="primary"]'}]},{name:"reddit.com",runContext:{urlPattern:"^https://www\\.reddit\\.com/"},prehideSelectors:["[bundlename=reddit_cookie_banner]"],detectCmp:[{exists:"reddit-cookie-banner"}],detectPopup:[{visible:"reddit-cookie-banner"}],optIn:[{waitForThenClick:["reddit-cookie-banner","#accept-all-cookies-button > button"]}],optOut:[{waitForThenClick:["reddit-cookie-banner","#reject-nonessential-cookies-button > button"]}],test:[{eval:"EVAL_REDDIT_0"}]},{name:"rog-forum.asus.com",runContext:{urlPattern:"^https://rog-forum\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{click:'div.cookie-btn-box > div[aria-label="Accept"]'}],optOut:[{click:'div.cookie-btn-box > div[aria-label="Reject"]'},{waitForThenClick:'.cookie-policy-lightbox-bottom > div[aria-label="Save Settings"]'}]},{name:"roofingmegastore.co.uk",runContext:{urlPattern:"^https://(www\\.)?roofingmegastore\\.co\\.uk"},prehideSelectors:["#m-cookienotice"],detectCmp:[{exists:"#m-cookienotice"}],detectPopup:[{visible:"#m-cookienotice"}],optIn:[{click:"#accept-cookies"}],optOut:[{click:"#manage-cookies"},{waitForThenClick:"#accept-selected"}]},{name:"samsung.com",runContext:{urlPattern:"^https://www\\.samsung\\.com/"},cosmetic:!0,prehideSelectors:["div.cookie-bar"],detectCmp:[{exists:"div.cookie-bar"}],detectPopup:[{visible:"div.cookie-bar"}],optIn:[{click:"div.cookie-bar__manage > a"}],optOut:[{hide:"div.cookie-bar"}]},{name:"setapp.com",vendorUrl:"https://setapp.com/",cosmetic:!0,runContext:{urlPattern:"^https://setapp\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.js-cookie-banner"}],detectPopup:[{visible:".cookie-banner.js-cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner.js-cookie-banner button"}],optOut:[{hide:".cookie-banner.js-cookie-banner"}]},{name:"sibbo",prehideSelectors:["sibbo-cmp-layout"],detectCmp:[{exists:"sibbo-cmp-layout"}],detectPopup:[{visible:"sibbo-cmp-layout"}],optIn:[{click:"sibbo-cmp-layout [data-accept-all]"}],optOut:[{click:'.sibbo-panel__aside__buttons a[data-nav="purposes"]'},{click:'.sibbo-panel__main__header__actions a[data-focusable="reject-all"]'},{if:{exists:"[data-view=purposes] .sibbo-panel__main__footer__actions [data-save-and-exit]"},then:[],else:[{waitFor:'.sibbo-panel__main__footer__actions a[data-focusable="next"]:not(.sibbo-cmp-button--disabled)'},{click:'.sibbo-panel__main__footer__actions a[data-focusable="next"]'},{click:'.sibbo-panel__main div[data-view="purposesLegInt"] a[data-focusable="reject-all"]'}]},{waitFor:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"},{click:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"}],test:[{eval:"EVAL_SIBBO_0"}]},{name:"similarweb.com",cosmetic:!0,prehideSelectors:[".app-cookies-notification"],detectCmp:[{exists:".app-cookies-notification"}],detectPopup:[{exists:".app-layout .app-cookies-notification"}],optIn:[{click:"button.app-cookies-notification__dismiss"}],optOut:[{hide:".app-layout .app-cookies-notification"}]},{name:"Sirdata",cosmetic:!0,prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{hide:"#sd-cmp"},{eval:"EVAL_SIRDATA_UNBLOCK_SCROLL"}]},{name:"snigel",detectCmp:[{exists:".snigel-cmp-framework"}],detectPopup:[{visible:".snigel-cmp-framework"}],optOut:[{click:"#sn-b-custom"},{click:"#sn-b-save"}],test:[{eval:"EVAL_SNIGEL_0"}],optIn:[{click:".snigel-cmp-framework #accept-choices"}]},{name:"steampowered.com",detectCmp:[{exists:".cookiepreferences_popup"},{visible:".cookiepreferences_popup"}],detectPopup:[{visible:".cookiepreferences_popup"}],optOut:[{click:"#rejectAllButton"}],optIn:[{click:"#acceptAllButton"}],test:[{wait:1e3},{eval:"EVAL_STEAMPOWERED_0"}]},{name:"strato.de",prehideSelectors:["#cookie_initial_modal",".modal-backdrop"],runContext:{urlPattern:"^https://www\\.strato\\.de/"},detectCmp:[{exists:"#cookie_initial_modal"}],detectPopup:[{visible:"#cookie_initial_modal"}],optIn:[{click:"button#jss_consent_all_initial_modal"}],optOut:[{click:"button#jss_open_settings_modal"},{click:"button#jss_consent_checked"}]},{name:"svt.se",vendorUrl:"https://www.svt.se/",runContext:{urlPattern:"^https://www\\.svt\\.se/"},prehideSelectors:["[class*=CookieConsent__root___]"],detectCmp:[{exists:"[class*=CookieConsent__root___]"}],detectPopup:[{visible:"[class*=CookieConsent__modal___]"}],optIn:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=primary]"}],optOut:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=secondary]:nth-child(2)"}],test:[{eval:"EVAL_SVT_TEST"}]},{name:"takealot.com",cosmetic:!0,prehideSelectors:['div[class^="cookies-banner-module_"]'],detectCmp:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],detectPopup:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],optIn:[{click:'button[class*="cookies-banner-module_dismiss-button_"]'}],optOut:[{hide:'div[class^="cookies-banner-module_"]'},{if:{exists:'div[class^="cookies-banner-module_small-cookie-banner_"]'},then:[{eval:"EVAL_TAKEALOT_0"}],else:[]}]},{name:"tarteaucitron.js",prehideSelectors:["#tarteaucitronRoot"],detectCmp:[{exists:"#tarteaucitronRoot"}],detectPopup:[{visible:"#tarteaucitronRoot #tarteaucitronAlertSmall,#tarteaucitronRoot #tarteaucitronAlertBig",check:"any"}],optIn:[{eval:"EVAL_TARTEAUCITRON_1"}],optOut:[{eval:"EVAL_TARTEAUCITRON_0"}],test:[{eval:"EVAL_TARTEAUCITRON_2",comment:"sometimes there are required categories, so we check that at least something is false"}]},{name:"taunton",vendorUrl:"https://www.taunton.com/",prehideSelectors:["#taunton-user-consent__overlay"],detectCmp:[{exists:"#taunton-user-consent__overlay"}],detectPopup:[{exists:"#taunton-user-consent__overlay:not([aria-hidden=true])"}],optIn:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:not(:checked)"},{click:"#taunton-user-consent__toolbar button[type=submit]"}],optOut:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:checked",optional:!0,all:!0},{click:"#taunton-user-consent__toolbar button[type=submit]"}],test:[{eval:"EVAL_TAUNTON_TEST"}]},{name:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal"},{waitForThenClick:"#cm-acceptNone,.js-accept-essential-cookies",timeout:1e3,optional:!0}],optIn:[{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{eval:"EVAL_TEALIUM_2"}],test:[{eval:"EVAL_TEALIUM_3"},{eval:"EVAL_TEALIUM_DONOTSELL_CHECK"},{visible:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs",check:"none"}]},{name:"temu",vendorUrl:"https://temu.com",runContext:{urlPattern:"^https://[^/]*temu\\.com/"},prehideSelectors:["._2d-8vq-W,._1UdBUwni"],detectCmp:[{exists:"._3YCsmIaS"}],detectPopup:[{visible:"._3YCsmIaS"}],optIn:[{waitForThenClick:"._3fKiu5wx._3zN5SumS._3tAK973O.IYOfhWEs.VGNGF1pA"}],optOut:[{waitForThenClick:"._3fKiu5wx._1_XToJBF._3tAK973O.IYOfhWEs.VGNGF1pA"}]},{name:"Termly",prehideSelectors:["#termly-code-snippet-support"],detectCmp:[{exists:"#termly-code-snippet-support"}],detectPopup:[{visible:"#termly-code-snippet-support div"}],optIn:[{waitForThenClick:'[data-tid="banner-accept"]'}],optOut:[{if:{exists:'[data-tid="banner-decline"]'},then:[{click:'[data-tid="banner-decline"]'}],else:[{click:".t-preference-button"},{wait:500},{if:{exists:".t-declineAllButton"},then:[{click:".t-declineAllButton"}],else:[{waitForThenClick:".t-preference-modal input[type=checkbox][checked]:not([disabled])",all:!0},{waitForThenClick:".t-saveButton"}]}]}]},{name:"termsfeed",vendorUrl:"https://termsfeed.com",comment:"v4.x.x",prehideSelectors:[".termsfeed-com---nb"],detectCmp:[{exists:".termsfeed-com---nb"}],detectPopup:[{visible:".termsfeed-com---nb"}],optIn:[{waitForThenClick:".cc-nb-okagree"}],optOut:[{waitForThenClick:".cc-nb-reject"}]},{name:"termsfeed3",vendorUrl:"https://termsfeed.com",comment:"v3.x.x",cosmetic:!0,prehideSelectors:[".cc_dialog.cc_css_reboot"],detectCmp:[{exists:".cc_dialog.cc_css_reboot"}],detectPopup:[{visible:".cc_dialog.cc_css_reboot"}],optIn:[{waitForThenClick:".cc_dialog.cc_css_reboot .cc_b_ok"}],optOut:[{hide:".cc_dialog.cc_css_reboot"}]},{name:"Test page cosmetic CMP",cosmetic:!0,prehideSelectors:["#privacy-test-page-cmp-test-prehide"],detectCmp:[{exists:"#privacy-test-page-cmp-test-banner"}],detectPopup:[{visible:"#privacy-test-page-cmp-test-banner"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{hide:"#privacy-test-page-cmp-test-banner"}],test:[{wait:500},{eval:"EVAL_TESTCMP_COSMETIC_0"}]},{name:"Test page CMP",prehideSelectors:["#reject-all"],detectCmp:[{exists:"#privacy-test-page-cmp-test"}],detectPopup:[{visible:"#privacy-test-page-cmp-test"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{waitFor:"#reject-all"},{click:"#reject-all"}],test:[{eval:"EVAL_TESTCMP_0"}]},{name:"thalia.de",prehideSelectors:[".consent-banner-box"],detectCmp:[{exists:"consent-banner[component=consent-banner]"}],detectPopup:[{visible:".consent-banner-box"}],optIn:[{click:".button-zustimmen"}],optOut:[{click:"button[data-consent=disagree]"}]},{name:"thefreedictionary.com",prehideSelectors:["#cmpBanner"],detectCmp:[{exists:"#cmpBanner"}],detectPopup:[{visible:"#cmpBanner"}],optIn:[{eval:"EVAL_THEFREEDICTIONARY_1"}],optOut:[{eval:"EVAL_THEFREEDICTIONARY_0"}]},{name:"theverge",runContext:{frame:!1,main:!0,urlPattern:"^https://(www)?\\.theverge\\.com"},intermediate:!1,prehideSelectors:[".duet--cta--cookie-banner"],detectCmp:[{exists:".duet--cta--cookie-banner"}],detectPopup:[{visible:".duet--cta--cookie-banner"}],optIn:[{click:".duet--cta--cookie-banner button.tracking-12",all:!1}],optOut:[{click:".duet--cta--cookie-banner button.tracking-12 > span"}],test:[{eval:"EVAL_THEVERGE_0"}]},{name:"tidbits-com",cosmetic:!0,prehideSelectors:["#eu_cookie_law_widget-2"],detectCmp:[{exists:"#eu_cookie_law_widget-2"}],detectPopup:[{visible:"#eu_cookie_law_widget-2"}],optIn:[{click:"#eu-cookie-law form > input.accept"}],optOut:[{hide:"#eu_cookie_law_widget-2"}]},{name:"tractor-supply",runContext:{urlPattern:"^https://www\\.tractorsupply\\.com/"},cosmetic:!0,prehideSelectors:[".tsc-cookie-banner"],detectCmp:[{exists:".tsc-cookie-banner"}],detectPopup:[{visible:".tsc-cookie-banner"}],optIn:[{click:"#cookie-banner-cancel"}],optOut:[{hide:".tsc-cookie-banner"}]},{name:"trader-joes-com",cosmetic:!0,prehideSelectors:['div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'],detectCmp:[{exists:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],detectPopup:[{visible:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],optIn:[{click:'div[class^="CookiesAlert_cookiesAlert__container__"] button'}],optOut:[{hide:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}]},{name:"transcend",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#transcend-consent-manager"],detectCmp:[{exists:"#transcend-consent-manager"}],detectPopup:[{visible:"#transcend-consent-manager"}],optIn:[{waitForThenClick:["#transcend-consent-manager","#consentManagerMainDialog .inner-container button"]}],optOut:[{hide:"#transcend-consent-manager"}]},{name:"transip-nl",runContext:{urlPattern:"^https://www\\.transip\\.nl/"},prehideSelectors:["#consent-modal"],detectCmp:[{any:[{exists:"#consent-modal"},{exists:"#privacy-settings-content"}]}],detectPopup:[{any:[{visible:"#consent-modal"},{visible:"#privacy-settings-content"}]}],optIn:[{click:'button[type="submit"]'}],optOut:[{if:{exists:"#privacy-settings-content"},then:[{click:'button[type="submit"]'}],else:[{click:"div.one-modal__action-footer-column--secondary > a"}]}]},{name:"tropicfeel-com",prehideSelectors:["#shopify-section-cookies-controller"],detectCmp:[{exists:"#shopify-section-cookies-controller"}],detectPopup:[{visible:"#shopify-section-cookies-controller #cookies-controller-main-pane",check:"any"}],optIn:[{waitForThenClick:"#cookies-controller-main-pane form[data-form-allow-all] button"}],optOut:[{click:"#cookies-controller-main-pane a[data-tab-target=manage-cookies]"},{waitFor:"#manage-cookies-pane.active"},{click:"#manage-cookies-pane.active input[type=checkbox][checked]:not([disabled])",all:!0},{click:"#manage-cookies-pane.active button[type=submit]"}],test:[]},{name:"true-car",runContext:{urlPattern:"^https://www\\.truecar\\.com/"},cosmetic:!0,prehideSelectors:[['div[aria-labelledby="cookie-banner-heading"]']],detectCmp:[{exists:'div[aria-labelledby="cookie-banner-heading"]'}],detectPopup:[{visible:'div[aria-labelledby="cookie-banner-heading"]'}],optIn:[{click:'div[aria-labelledby="cookie-banner-heading"] > button[aria-label="Close"]'}],optOut:[{hide:'div[aria-labelledby="cookie-banner-heading"]'}]},{name:"truyo",prehideSelectors:["#truyo-consent-module"],detectCmp:[{exists:"#truyo-cookieBarContent"}],detectPopup:[{visible:"#truyo-consent-module"}],optIn:[{click:"button#acceptAllCookieButton"}],optOut:[{click:"button#declineAllCookieButton"}]},{name:"tumblr-com",cosmetic:!0,prehideSelectors:["#cmp-app-container"],detectCmp:[{exists:"#cmp-app-container"}],detectPopup:[{visible:"#cmp-app-container"}],optIn:[{click:"#tumblr #cmp-app-container div.components-modal__frame > iframe > html body > div > div > div.cmp__dialog-footer > div > button.components-button.white-space-normal.is-primary"}],optOut:[{hide:"#cmp-app-container"}]},{name:"twitch-mobile",vendorUrl:"https://m.twitch.tv/",cosmetic:!0,runContext:{urlPattern:"^https?://m\\.twitch\\.tv"},prehideSelectors:[],detectCmp:[{exists:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],detectPopup:[{visible:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],optIn:[{waitForThenClick:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"]) button'}],optOut:[{hide:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"])'}]},{name:"twitch.tv",runContext:{urlPattern:"^https?://(www\\.)?twitch\\.tv"},prehideSelectors:["div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"],detectCmp:[{exists:".consent-banner .consent-banner__content--gdpr-v2"}],detectPopup:[{visible:".consent-banner .consent-banner__content--gdpr-v2"}],optIn:[{click:'button[data-a-target="consent-banner-accept"]'}],optOut:[{hide:"div:has(> .consent-banner .consent-banner__content--gdpr-v2)"},{click:'button[data-a-target="consent-banner-manage-preferences"]'},{waitFor:"input[type=checkbox][data-a-target=tw-checkbox]"},{click:"input[type=checkbox][data-a-target=tw-checkbox][checked]:not([disabled])",all:!0,optional:!0},{waitForThenClick:"[data-a-target=consent-modal-save]"},{waitForVisible:".ReactModalPortal:has([data-a-target=consent-modal-save])",check:"none"}]},{name:"twitter",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?twitter\\.com/"},prehideSelectors:['[data-testid="BottomBar"]'],detectCmp:[{exists:'[data-testid="BottomBar"] div'}],detectPopup:[{visible:'[data-testid="BottomBar"] div'}],optIn:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:last-child'}],TODOtest:[{eval:"EVAL_document.cookie.includes('d_prefs=MjoxLGNvbnNlbnRfdmVyc2lvbjoy')"}]},{name:"ubuntu.com",prehideSelectors:["dialog.cookie-policy"],detectCmp:[{any:[{exists:"dialog.cookie-policy header"},{exists:'xpath///*[@id="modal"]/div/header'}]}],detectPopup:[{any:[{visible:"dialog header"},{visible:'xpath///*[@id="modal"]/div/header'}]}],optIn:[{any:[{waitForThenClick:"#cookie-policy-button-accept"},{waitForThenClick:'xpath///*[@id="cookie-policy-button-accept"]'}]}],optOut:[{any:[{waitForThenClick:"button.js-manage"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:"dialog.cookie-policy .p-switch__input:checked",optional:!0,all:!0,timeout:500},{any:[{waitForThenClick:"dialog.cookie-policy .js-save-preferences"},{waitForThenClick:'xpath///*[@id="modal"]/div/button'}]}],test:[{eval:"EVAL_UBUNTU_COM_0"}]},{name:"UK Cookie Consent",prehideSelectors:["#catapult-cookie-bar"],cosmetic:!0,detectCmp:[{exists:"#catapult-cookie-bar"}],detectPopup:[{exists:".has-cookie-bar #catapult-cookie-bar"}],optIn:[{click:"#catapultCookie"}],optOut:[{hide:"#catapult-cookie-bar"}],test:[{eval:"EVAL_UK_COOKIE_CONSENT_0"}]},{name:"urbanarmorgear-com",cosmetic:!0,prehideSelectors:['div[class^="Layout__CookieBannerContainer-"]'],detectCmp:[{exists:'div[class^="Layout__CookieBannerContainer-"]'}],detectPopup:[{visible:'div[class^="Layout__CookieBannerContainer-"]'}],optIn:[{click:'button[class^="CookieBanner__AcceptButton"]'}],optOut:[{hide:'div[class^="Layout__CookieBannerContainer-"]'}]},{name:"usercentrics-api",detectCmp:[{exists:"#usercentrics-root"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{exists:["#usercentrics-root","[data-testid=uc-container]"]},{waitForVisible:"#usercentrics-root",timeout:2e3}],optIn:[{eval:"EVAL_USERCENTRICS_API_3"},{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_5"}],optOut:[{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_2"}],test:[{eval:"EVAL_USERCENTRICS_API_6"}]},{name:"usercentrics-button",detectCmp:[{exists:"#usercentrics-button"}],detectPopup:[{visible:"#usercentrics-button #uc-btn-accept-banner"}],optIn:[{click:"#usercentrics-button #uc-btn-accept-banner"}],optOut:[{click:"#usercentrics-button #uc-btn-deny-banner"}],test:[{eval:"EVAL_USERCENTRICS_BUTTON_0"}]},{name:"uswitch.com",prehideSelectors:["#cookie-banner-wrapper"],detectCmp:[{exists:"#cookie-banner-wrapper"}],detectPopup:[{visible:"#cookie-banner-wrapper"}],optIn:[{click:"#cookie_banner_accept_mobile"}],optOut:[{click:"#cookie_banner_save"}]},{name:"vodafone.de",runContext:{urlPattern:"^https://www\\.vodafone\\.de/"},prehideSelectors:[".dip-consent,.dip-consent-container"],detectCmp:[{exists:".dip-consent-container"}],detectPopup:[{visible:".dip-consent-content"}],optOut:[{click:'.dip-consent-btn[tabindex="2"]'}],optIn:[{click:'.dip-consent-btn[tabindex="1"]'}]},{name:"waitrose.com",prehideSelectors:["div[aria-labelledby=CookieAlertModalHeading]","section[data-test=initial-waitrose-cookie-consent-banner]","section[data-test=cookie-consent-modal]"],detectCmp:[{exists:"section[data-test=initial-waitrose-cookie-consent-banner]"}],detectPopup:[{visible:"section[data-test=initial-waitrose-cookie-consent-banner]"}],optIn:[{click:"button[data-test=accept-all]"}],optOut:[{click:"button[data-test=manage-cookies]"},{wait:200},{eval:"EVAL_WAITROSE_0"},{click:"button[data-test=submit]"}],test:[{eval:"EVAL_WAITROSE_1"}]},{name:"webflow",vendorUrl:"https://webflow.com/",prehideSelectors:[".fs-cc-components"],detectCmp:[{exists:".fs-cc-components"}],detectPopup:[{visible:".fs-cc-components"},{visible:"[fs-cc=banner]"}],optIn:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=allow]"}],optOut:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=deny]"}]},{name:"wetransfer.com",detectCmp:[{exists:".welcome__cookie-notice"}],detectPopup:[{visible:".welcome__cookie-notice"}],optIn:[{click:".welcome__button--accept"}],optOut:[{click:".welcome__button--decline"}]},{name:"whitepages.com",runContext:{urlPattern:"^https://www\\.whitepages\\.com/"},cosmetic:!0,prehideSelectors:[".cookie-wrapper, .cookie-overlay"],detectCmp:[{exists:".cookie-wrapper"}],detectPopup:[{visible:".cookie-overlay"}],optIn:[{click:'button[aria-label="Got it"]'}],optOut:[{hide:".cookie-wrapper"}]},{name:"wolframalpha",vendorUrl:"https://www.wolframalpha.com",prehideSelectors:[],cosmetic:!0,runContext:{urlPattern:"^https://www\\.wolframalpha\\.com/"},detectCmp:[{exists:"section._a_yb"}],detectPopup:[{visible:"section._a_yb"}],optIn:[{waitForThenClick:"section._a_yb button"}],optOut:[{hide:"section._a_yb"}]},{name:"woo-commerce-com",prehideSelectors:[".wccom-comp-privacy-banner .wccom-privacy-banner"],detectCmp:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],detectPopup:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],optIn:[{click:".wccom-privacy-banner__content-buttons button.is-primary"}],optOut:[{click:".wccom-privacy-banner__content-buttons button.is-secondary"},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:"div.wccom-modal__footer > button"}]},{name:"WP Cookie Notice for GDPR",vendorUrl:"https://wordpress.org/plugins/gdpr-cookie-consent/",prehideSelectors:["#gdpr-cookie-consent-bar"],detectCmp:[{exists:"#gdpr-cookie-consent-bar"}],detectPopup:[{visible:"#gdpr-cookie-consent-bar"}],optIn:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_accept"}],optOut:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_reject"}],test:[{eval:"EVAL_WP_COOKIE_NOTICE_0"}]},{name:"wpcc",cosmetic:!0,prehideSelectors:[".wpcc-container"],detectCmp:[{exists:".wpcc-container"}],detectPopup:[{exists:".wpcc-container .wpcc-message"}],optIn:[{click:".wpcc-compliance .wpcc-btn"}],optOut:[{hide:".wpcc-container"}]},{name:"xe.com",vendorUrl:"https://www.xe.com/",runContext:{urlPattern:"^https://www\\.xe\\.com/"},prehideSelectors:["[class*=ConsentBanner]"],detectCmp:[{exists:"[class*=ConsentBanner]"}],detectPopup:[{visible:"[class*=ConsentBanner]"}],optIn:[{waitForThenClick:"[class*=ConsentBanner] .egnScw"}],optOut:[{wait:1e3},{waitForThenClick:"[class*=ConsentBanner] .frDWEu"},{waitForThenClick:"[class*=ConsentBanner] .hXIpFU"}],test:[{eval:"EVAL_XE_TEST"}]},{name:"xhamster-eu",prehideSelectors:[".cookies-modal"],detectCmp:[{exists:".cookies-modal"}],detectPopup:[{exists:".cookies-modal"}],optIn:[{click:"button.cmd-button-accept-all"}],optOut:[{click:"button.cmd-button-reject-all"}]},{name:"xhamster-us",runContext:{urlPattern:"^https://(www\\.)?xhamster\\d?\\.com"},cosmetic:!0,prehideSelectors:[".cookie-announce"],detectCmp:[{exists:".cookie-announce"}],detectPopup:[{visible:".cookie-announce .announce-text"}],optIn:[{click:".cookie-announce button.xh-button"}],optOut:[{hide:".cookie-announce"}]},{name:"xing.com",detectCmp:[{exists:"div[class^=cookie-consent-CookieConsent]"}],detectPopup:[{exists:"div[class^=cookie-consent-CookieConsent]"}],optIn:[{click:"#consent-accept-button"}],optOut:[{click:"#consent-settings-button"},{click:".consent-banner-button-accept-overlay"}],test:[{eval:"EVAL_XING_0"}]},{name:"xnxx-com",cosmetic:!0,prehideSelectors:["#cookies-use-alert"],detectCmp:[{exists:"#cookies-use-alert"}],detectPopup:[{visible:"#cookies-use-alert"}],optIn:[{click:"#cookies-use-alert .close"}],optOut:[{hide:"#cookies-use-alert"}]},{name:"xvideos",vendorUrl:"https://xvideos.com",runContext:{urlPattern:"^https://[^/]*xvideos\\.com/"},prehideSelectors:[],detectCmp:[{exists:".disclaimer-opened #disclaimer-cookies"}],detectPopup:[{visible:".disclaimer-opened #disclaimer-cookies"}],optIn:[{waitForThenClick:"#disclaimer-accept_cookies"}],optOut:[{waitForThenClick:"#disclaimer-reject_cookies"}]},{name:"Yahoo",runContext:{urlPattern:"^https://consent\\.yahoo\\.com/v2/"},prehideSelectors:["#reject-all"],detectCmp:[{exists:"#consent-page"}],detectPopup:[{visible:"#consent-page"}],optIn:[{waitForThenClick:"#consent-page button[value=agree]"}],optOut:[{waitForThenClick:"#consent-page button[value=reject]"}]},{name:"youporn.com",cosmetic:!0,prehideSelectors:[".euCookieModal, #js_euCookieModal"],detectCmp:[{exists:".euCookieModal"}],detectPopup:[{exists:".euCookieModal, #js_euCookieModal"}],optIn:[{click:'button[name="user_acceptCookie"]'}],optOut:[{hide:".euCookieModal"}]},{name:"youtube-desktop",prehideSelectors:["tp-yt-iron-overlay-backdrop.opened","ytd-consent-bump-v2-lightbox"],detectCmp:[{exists:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"},{exists:'ytd-consent-bump-v2-lightbox tp-yt-paper-dialog a[href^="https://consent.youtube.com/"]'}],detectPopup:[{visible:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"}],optIn:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child button"},{wait:500}],optOut:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_DESKTOP_0"}]},{name:"youtube-mobile",prehideSelectors:[".consent-bump-v2-lightbox"],detectCmp:[{exists:"ytm-consent-bump-v2-renderer"}],detectPopup:[{visible:"ytm-consent-bump-v2-renderer"}],optIn:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:first-child button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:first-child button"},{wait:500}],optOut:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:nth-child(2) button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:nth-child(2) button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_MOBILE_0"}]},{name:"zdf",prehideSelectors:["#zdf-cmp-banner-sdk"],detectCmp:[{exists:"#zdf-cmp-banner-sdk"}],detectPopup:[{visible:"#zdf-cmp-main.zdf-cmp-show"}],optIn:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-accept-btn"}],optOut:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-deny-btn"}],test:[]}],C={"didomi.io":{detectors:[{presentMatcher:{target:{selector:"#didomi-host, #didomi-notice"},type:"css"},showingMatcher:{target:{selector:"body.didomi-popup-open, .didomi-notice-banner"},type:"css"}}],methods:[{action:{target:{selector:".didomi-popup-notice-buttons .didomi-button:not(.didomi-button-highlight), .didomi-notice-banner .didomi-learn-more-button"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{retries:50,target:{selector:"#didomi-purpose-cookies"},type:"waitcss",waitTime:50},{consents:[{description:"Share (everything) with others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:last-child"},type:"click"},type:"X"},{description:"Information storage and access",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:last-child"},type:"click"},type:"D"},{description:"Content selection, offers and marketing",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:last-child"},type:"click"},type:"E"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:last-child"},type:"click"},type:"B"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:last-child"},type:"click"},type:"B"},{description:"Ad and content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection",falseAction:{parent:{childFilter:{target:{selector:"#didomi-purpose-pub-ciblee"}},selector:".didomi-consent-popup-data-processing, .didomi-components-accordion-label-container"},target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - basics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - partners and subsidiaries",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:last-child"},type:"click"},type:"F"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:last-child"},type:"click"},type:"A"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:last-child"},type:"click"},type:"A"},{description:"Content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:last-child"},type:"click"},type:"E"},{description:"Ad delivery",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:last-child"},type:"click"},type:"F"}],type:"consent"},{action:{consents:[{matcher:{childFilter:{target:{selector:":not(.didomi-components-radio__option--selected)"}},type:"css"},trueAction:{target:{selector:":nth-child(2)"},type:"click"},falseAction:{target:{selector:":first-child"},type:"click"},type:"X"}],type:"consent"},target:{selector:".didomi-components-radio"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".didomi-consent-popup-footer .didomi-consent-popup-actions"},target:{selector:".didomi-components-button:first-child"},type:"click"},name:"SAVE_CONSENT"}]},oil:{detectors:[{presentMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"},showingMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".as-js-advanced-settings"},type:"click"},{retries:"10",target:{selector:".as-oil-cpc__purpose-container"},type:"waitcss",waitTime:"250"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{consents:[{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"D"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"B"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:".as-oil__btn-optin"},type:"click"},name:"SAVE_CONSENT"},{action:{target:{selector:"div.as-oil"},type:"hide"},name:"HIDE_CMP"}]},optanon:{detectors:[{presentMatcher:{target:{selector:"#optanon-menu, .optanon-alert-box-wrapper"},type:"css"},showingMatcher:{target:{displayFilter:!0,selector:".optanon-alert-box-wrapper"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".optanon-alert-box-wrapper .optanon-toggle-display, a[onclick*='OneTrust.ToggleInfoDisplay()'], a[onclick*='Optanon.ToggleInfoDisplay()']"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".preference-menu-item #Your-privacy"},type:"click"},{target:{selector:"#optanon-vendor-consent-text"},type:"click"},{action:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},target:{selector:"#optanon-vendor-consent-list .vendor-item"},type:"foreach"},{target:{selector:".vendor-consent-back-link"},type:"click"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"D"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".optanon-save-settings-button"},target:{selector:".optanon-white-button-middle"},type:"click"},name:"SAVE_CONSENT"},{action:{actions:[{target:{selector:"#optanon-popup-wrapper"},type:"hide"},{target:{selector:"#optanon-popup-bg"},type:"hide"},{target:{selector:".optanon-alert-box-wrapper"},type:"hide"}],type:"list"},name:"HIDE_CMP"}]},quantcast2:{detectors:[{presentMatcher:{target:{selector:"[data-tracking-opt-in-overlay]"},type:"css"},showingMatcher:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"css"}}],methods:[{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{type:"wait",waitTime:500},{action:{actions:[{target:{selector:"div",textFilter:["Information storage and access"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"D"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Personalization"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Ad selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Content selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"E"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Measurement"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"B"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Other Partners"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},type:"ifcss"}],type:"list"},parent:{childFilter:{target:{selector:"input"}},selector:"[data-tracking-opt-in-overlay] > div > div"},target:{childFilter:{target:{selector:"input"}},selector:":scope > div"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-save]"},type:"click"},name:"SAVE_CONSENT"}]},springer:{detectors:[{presentMatcher:{parent:null,target:{selector:".cmp-app_gdpr"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".cmp-popup_popup"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".cmp-intro_rejectAll"},type:"click"},{type:"wait",waitTime:250},{target:{selector:".cmp-purposes_purposeItem:not(.cmp-purposes_selectedPurpose)"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{consents:[{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"D"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"}],type:"consent"},name:"DO_CONSENT"},{action:{target:{selector:".cmp-details_save"},type:"click"},name:"SAVE_CONSENT"}]},wordpressgdpr:{detectors:[{presentMatcher:{parent:null,target:{selector:".wpgdprc-consent-bar"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".wpgdprc-consent-bar"},type:"css"}}],methods:[{action:{parent:null,target:{selector:".wpgdprc-consent-bar .wpgdprc-consent-bar__settings",textFilter:null},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Eyeota"},type:"click"},{consents:[{description:"Eyeota Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Advertising"},type:"click"},{consents:[{description:"Advertising Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{parent:null,target:{selector:".wpgdprc-button",textFilter:"Save my settings"},type:"click"},name:"SAVE_CONSENT"}]}},v={autoconsent:w,consentomatic:C},f=Object.freeze({__proto__:null,autoconsent:w,consentomatic:C,default:v});const A=new class{constructor(e,t=null,o=null){if(this.id=n(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},a.sendContentMessage=e,this.sendContentMessage=e,this.rules=[],this.updateState({lifecycle:"loading"}),this.addDynamicRules(),t)this.initialize(t,o);else{o&&this.parseDeclarativeRules(o);e({type:"init",url:window.location.href}),this.updateState({lifecycle:"waitingForInitResponse"})}this.domActions=new class{constructor(e){this.autoconsentInstance=e}click(e,t=!1){const o=this.elementSelector(e);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[click]",e,t,o),o.length>0&&(t?o.forEach((e=>e.click())):o[0].click()),o.length>0}elementExists(e){return this.elementSelector(e).length>0}elementVisible(e,t){const o=this.elementSelector(e),c=new Array(o.length);return o.forEach(((e,t)=>{c[t]=k(e)})),"none"===t?c.every((e=>!e)):0!==c.length&&("any"===t?c.some((e=>e)):c.every((e=>e)))}waitForElement(e,t=1e4){const o=Math.ceil(t/200);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[waitForElement]",e),h((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return h((()=>this.elementVisible(e,o)),Math.ceil(t/200),200)}async waitForThenClick(e,t=1e4,o=!1){return await this.waitForElement(e,t),this.click(e,o)}wait(e){return new Promise((t=>{setTimeout((()=>{t(!0)}),e)}))}hide(e,t){return m(u(),e,t)}prehide(e){const t=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),m(t,e,"opacity")}undoPrehide(){const e=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[undoprehide]",e,location.href),e&&e.remove(),!!e}querySingleReplySelector(e,t=document){if(e.startsWith("aria/"))return[];if(e.startsWith("xpath/")){const o=e.slice(6),c=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let i=null;const n=[];for(;i=c.iterateNext();)n.push(i);return n}return e.startsWith("text/")||e.startsWith("pierce/")?[]:t.shadowRoot?Array.from(t.shadowRoot.querySelectorAll(e)):Array.from(t.querySelectorAll(e))}querySelectorChain(e){let t,o=document;for(const c of e){if(t=this.querySingleReplySelector(c,o),0===t.length)return[];o=t[0]}return t}elementSelector(e){return"string"==typeof e?this.querySingleReplySelector(e):this.querySelectorChain(e)}}(this)}initialize(e,t){const o=b(e);if(o.logs.lifecycle&&console.log("autoconsent init",window.location.href),this.config=o,o.enabled){if(t&&this.parseDeclarativeRules(t),this.rules=function(e,t){return e.filter((e=>(!t.disabledCmps||!t.disabledCmps.includes(e.name))&&(t.enableCosmeticRules||!e.isCosmetic)))}(this.rules,o),e.enablePrehide)if(document.documentElement)this.prehideElements();else{const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.prehideElements()};window.addEventListener("DOMContentLoaded",e)}if("loading"===document.readyState){const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.start()};window.addEventListener("DOMContentLoaded",e)}else this.start();this.updateState({lifecycle:"initialized"})}else o.logs.lifecycle&&console.log("autoconsent is disabled")}addDynamicRules(){y.forEach((e=>{this.rules.push(new e(this))}))}parseDeclarativeRules(e){Object.keys(e.consentomatic).forEach((t=>{this.addConsentomaticCMP(t,e.consentomatic[t])})),e.autoconsent.forEach((e=>{this.addDeclarativeCMP(e)}))}addDeclarativeCMP(e){this.rules.push(new d(e,this))}addConsentomaticCMP(e,t){this.rules.push(new class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=l,this.isCosmetic=!1,t.methods.forEach((e=>{e.action&&this.methods.set(e.name,e.action)})),this.hasSelfTest=!1}get isIntermediate(){return!1}checkRunContext(){return!0}async detectCmp(){return this.config.detectors.map((e=>o(e.presentMatcher))).some((e=>!!e))}async detectPopup(){return this.config.detectors.map((e=>o(e.showingMatcher))).some((e=>!!e))}async executeAction(e,t){return!this.methods.has(e)||c(this.methods.get(e),t)}async optOut(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",[]),await this.executeAction("SAVE_CONSENT"),!0}async optIn(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",["D","A","B","E","F","X"]),await this.executeAction("SAVE_CONSENT"),!0}async openCmp(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),!0}async test(){return!0}}(`com_${e}`,t))}start(){window.requestIdleCallback?window.requestIdleCallback((()=>this._start()),{timeout:500}):this._start()}async _start(){const e=this.config.logs;e.lifecycle&&console.log(`Detecting CMPs on ${window.location.href}`),this.updateState({lifecycle:"started"});const t=await this.findCmp(this.config.detectRetries);if(this.updateState({detectedCmps:t.map((e=>e.name))}),0===t.length)return e.lifecycle&&console.log("no CMP found",location.href),this.config.enablePrehide&&this.undoPrehide(),this.updateState({lifecycle:"nothingDetected"}),!1;this.updateState({lifecycle:"cmpDetected"});const o=[],c=[];for(const e of t)e.isCosmetic?c.push(e):o.push(e);let i=!1,n=await this.detectPopups(o,(async e=>{i=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(c,(async e=>{i=await this.handlePopup(e)}))),0===n.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(n.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:n.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return i}async findCmp(e){const t=this.config.logs;this.updateState({findCmpAttempts:this.state.findCmpAttempts+1});const o=[];for(const e of this.rules)try{if(!e.checkRunContext())continue;await e.detectCmp()&&(t.lifecycle&&console.log(`Found CMP: ${e.name} ${window.location.href}`),this.sendContentMessage({type:"cmpDetected",url:location.href,cmp:e.name}),o.push(e))}catch(o){t.errors&&console.warn(`error detecting ${e.name}`,o)}return 0===o.length&&e>0?(await this.domActions.wait(500),this.findCmp(e-1)):o}async detectPopup(e){if(await this.waitForPopup(e).catch((t=>(this.config.logs.errors&&console.warn(`error waiting for a popup for ${e.name}`,t),!1))))return this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),e;throw new Error("Popup is not shown")}async detectPopups(e,t){const o=e.map((e=>this.detectPopup(e)));await Promise.any(o).then((e=>{t(e)})).catch((()=>null));const c=await Promise.allSettled(o),i=[];for(const e of c)"fulfilled"===e.status&&i.push(e.value);return i}async handlePopup(e){return this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),this.foundCmp=e,"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(this.config.logs.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}async doOptOut(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptOut"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`),t=await this.foundCmp.optOut(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt out result ${t}`)):(e.errors&&console.log("no CMP to opt out"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optOutResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:this.foundCmp&&this.foundCmp.hasSelfTest,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optOutSucceeded":"optOutFailed"}),t}async doOptIn(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptIn"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`),t=await this.foundCmp.optIn(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt in result ${t}`)):(e.errors&&console.log("no CMP to opt in"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optInResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:!1,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optInSucceeded":"optInFailed"}),t}async doSelfTest(){const e=this.config.logs;let t;return this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`),t=await this.foundCmp.test()):(e.errors&&console.log("no CMP to self test"),t=!1),this.sendContentMessage({type:"selfTestResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,url:location.href}),this.updateState({selfTest:t}),t}async waitForPopup(e,t=5,o=500){const c=this.config.logs;c.lifecycle&&console.log("checking if popup is open...",e.name);const i=await e.detectPopup().catch((t=>(c.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!i&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(c.lifecycle&&console.log(e.name,"popup is "+(i?"open":"not open")),i)}prehideElements(){const e=this.config.logs,t=this.rules.reduce(((e,t)=>t.prehideSelectors?[...e,...t.prehideSelectors]:e),["#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium"]);return this.updateState({prehideOn:!0}),setTimeout((()=>{this.config.enablePrehide&&this.state.prehideOn&&!["runningOptOut","runningOptIn"].includes(this.state.lifecycle)&&(e.lifecycle&&console.log("Process is taking too long, unhiding elements"),this.undoPrehide())}),this.config.prehideTimeout||2e3),this.domActions.prehide(t.join(","))}undoPrehide(){return this.updateState({prehideOn:!1}),this.domActions.undoPrehide()}updateState(e){Object.assign(this.state,e),this.sendContentMessage({type:"report",instanceId:this.id,url:window.location.href,mainFrame:window.top===window.self,state:this.state})}async receiveMessageCallback(e){const t=this.config?.logs;switch(t?.messages&&console.log("received from background",e,window.location.href),e.type){case"initResp":this.initialize(e.config,e.rules);break;case"optIn":await this.doOptIn();break;case"optOut":await this.doOptOut();break;case"selfTest":await this.doSelfTest();break;case"evalResp":!function(e,t){const o=a.pending.get(e);o?(a.pending.delete(e),o.timer&&window.clearTimeout(o.timer),o.resolve(t)):console.warn("no eval #",e)}(e.id,e.result)}}}((e=>{window.webkit.messageHandlers[e.type]&&window.webkit.messageHandlers[e.type].postMessage(e).then((e=>{A.receiveMessageCallback(e)}))}),null,f);window.autoconsentMessageCallback=e=>{A.receiveMessageCallback(e)}}(); +!function(){"use strict";var e=class e{static setBase(t){e.base=t}static findElement(t,o=null,c=!1){let i=null;return i=null!=o?Array.from(o.querySelectorAll(t.selector)):null!=e.base?Array.from(e.base.querySelectorAll(t.selector)):Array.from(document.querySelectorAll(t.selector)),null!=t.textFilter&&(i=i.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const c of t.textFilter)if(-1!==o.indexOf(c.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(i=i.filter((e=>{const o=window.getComputedStyle(e);let c=!0;for(const e of t.styleFilters){const t=o[e.option];c=e.negated?c&&t!==e.value:c&&t===e.value}return c}))),null!=t.displayFilter&&(i=i.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(i=i.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(i=i.filter((o=>{const c=e.base;e.setBase(o);const i=e.find(t.childFilter);return e.setBase(c),null!=i.target}))),c?i:(i.length>1&&console.warn("Multiple possible targets: ",i,t,o),i[0])}static find(t,o=!1){const c=[];if(null!=t.parent){const i=e.findElement(t.parent,null,o);if(null!=i){if(i instanceof Array)return i.forEach((i=>{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})})),c;{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})}}}else{const i=e.findElement(t.target,null,o);i instanceof Array?i.forEach((e=>{c.push({parent:null,target:e})})):c.push({parent:null,target:i})}return 0===c.length&&c.push({parent:null,target:null}),o?c:(1!==c.length&&console.warn("Multiple results found, even though multiple false",c),c[0])}};e.base=null;var t=e;function o(e){const o=t.find(e);return"css"===e.type?!!o.target:"checkbox"===e.type?!!o.target&&o.target.checked:void 0}async function c(e,n){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return i(0)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,n);case"consent":return async function(e,t){for(const i of e.consents){const e=-1!==t.indexOf(i.type);if(i.matcher&&i.toggleAction){o(i.matcher)!==e&&await c(i.toggleAction)}else e?await c(i.trueAction):await c(i.falseAction)}}(e,n);case"ifcss":return async function(e,o){const i=t.find(e);i.target?e.falseAction&&await c(e.falseAction,o):e.trueAction&&await c(e.trueAction,o)}(e,n);case"waitcss":return async function(e){await new Promise((o=>{let c=e.retries||10;const i=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&c>0?(c-=1,setTimeout(n,i)):o()};n()}))}(e);case"foreach":return async function(e,o){const i=t.find(e,!0),n=t.base;for(const n of i)n.target&&(t.setBase(n.target),await c(e.action,o));t.setBase(n)}(e,n);case"hide":return async function(e){const o=t.find(e);o.target&&o.target.classList.add("Autoconsent-Hidden")}(e);case"slide":return async function(e){const o=t.find(e),c=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=c.target.getBoundingClientRect();let i=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(i=0);const a=window.screenX+e.left+e.width/2,s=window.screenY+e.top+e.height/2,r=e.left+e.width/2,l=e.top+e.height/2,p=document.createEvent("MouseEvents");p.initMouseEvent("mousedown",!0,!0,window,0,a,s,r,l,!1,!1,!1,!1,0,o.target);const d=document.createEvent("MouseEvents");d.initMouseEvent("mousemove",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target),o.target.dispatchEvent(p),await this.waitTimeout(10),o.target.dispatchEvent(d),await this.waitTimeout(10),o.target.dispatchEvent(u)}}(e);case"close":return async function(){window.close()}();case"wait":return async function(e){await i(e.waitTime)}(e);case"eval":return async function(e){return console.log("eval!",e.code),new Promise((t=>{try{e.async?(window.eval(e.code),setTimeout((()=>{t(window.eval("window.__consentCheckResult"))}),e.timeout||250)):t(window.eval(e.code))}catch(o){console.warn("eval error",o,e.code),t(!1)}}))}(e);default:throw"Unknown action type: "+e.type}}function i(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function n(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var a={pending:new Map,sendContentMessage:null};function s(e,t){const o=n();a.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new class{constructor(e,t=1e3){this.id=e,this.promise=new Promise(((e,t)=>{this.resolve=e,this.reject=t})),this.timer=window.setTimeout((()=>{this.reject(new Error("timeout"))}),t)}}(o);return a.pending.set(c.id,c),c.promise}var r={EVAL_0:()=>console.log(1),EVAL_CONSENTMANAGER_1:()=>window.__cmp&&"object"==typeof __cmp("getCMPData"),EVAL_CONSENTMANAGER_2:()=>!__cmp("consentStatus").userChoiceExists,EVAL_CONSENTMANAGER_3:()=>__cmp("setConsent",0),EVAL_CONSENTMANAGER_4:()=>__cmp("setConsent",1),EVAL_CONSENTMANAGER_5:()=>__cmp("consentStatus").userChoiceExists,EVAL_COOKIEBOT_1:()=>!!window.Cookiebot,EVAL_COOKIEBOT_2:()=>!window.Cookiebot.hasResponse&&!0===window.Cookiebot.dialog?.visible,EVAL_COOKIEBOT_3:()=>window.Cookiebot.withdraw()||!0,EVAL_COOKIEBOT_4:()=>window.Cookiebot.hide()||!0,EVAL_COOKIEBOT_5:()=>!0===window.Cookiebot.declined,EVAL_KLARO_1:()=>{const e=globalThis.klaroConfig||globalThis.klaro?.getManager&&globalThis.klaro.getManager().config;if(!e)return!0;const t=(e.services||e.apps).filter((e=>!e.required)).map((e=>e.name));if(klaro&&klaro.getManager){const e=klaro.getManager();return t.every((t=>!e.consents[t]))}if(klaroConfig&&"cookie"===klaroConfig.storageMethod){const e=klaroConfig.cookieName||klaroConfig.storageName,o=JSON.parse(decodeURIComponent(document.cookie.split(";").find((t=>t.trim().startsWith(e))).split("=")[1]));return Object.keys(o).filter((e=>t.includes(e))).every((e=>!1===o[e]))}},EVAL_ONETRUST_1:()=>window.OnetrustActiveGroups.split(",").filter((e=>e.length>0)).length<=1,EVAL_TRUSTARC_TOP:()=>window&&window.truste&&"0"===window.truste.eu.bindMap.prefCookie,EVAL_ADROLL_0:()=>!document.cookie.includes("__adroll_fpc"),EVAL_ALMACMP_0:()=>document.cookie.includes('"name":"Google","consent":false'),EVAL_AFFINITY_SERIF_COM_0:()=>document.cookie.includes("serif_manage_cookies_viewed")&&!document.cookie.includes("serif_allow_analytics"),EVAL_ARBEITSAGENTUR_TEST:()=>document.cookie.includes("cookie_consent=denied"),EVAL_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),EVAL_BAHN_TEST:()=>1===utag.gdpr.getSelectedCategories().length,EVAL_BING_0:()=>document.cookie.includes("AL=0")&&document.cookie.includes("AD=0")&&document.cookie.includes("SM=0"),EVAL_BLOCKSY_0:()=>document.cookie.includes("blocksy_cookies_consent_accepted=no"),EVAL_BORLABS_0:()=>!JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("borlabs-cookie"))).split("=",2)[1])).consents.statistics,EVAL_BUNDESREGIERUNG_DE_0:()=>document.cookie.match("cookie-allow-tracking=0"),EVAL_CANVA_0:()=>!document.cookie.includes("gtm_fpc_engagement_event"),EVAL_CC_BANNER2_0:()=>!!document.cookie.match(/sncc=[^;]+D%3Dtrue/),EVAL_CLICKIO_0:()=>document.cookie.includes("__lxG__consent__v2_daisybit="),EVAL_CLINCH_0:()=>document.cookie.includes("ctc_rejected=1"),EVAL_COOKIECONSENT2_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COOKIECONSENT3_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COINBASE_0:()=>JSON.parse(decodeURIComponent(document.cookie.match(/cm_(eu|default)_preferences=([0-9a-zA-Z\\{\\}\\[\\]%:]*);?/)[2])).consent.length<=1,EVAL_COMPLIANZ_BANNER_0:()=>document.cookie.includes("cmplz_banner-status=dismissed"),EVAL_COOKIE_LAW_INFO_0:()=>CLI.disableAllCookies()||CLI.reject_close()||!0,EVAL_COOKIE_LAW_INFO_1:()=>-1===document.cookie.indexOf("cookielawinfo-checkbox-non-necessary=yes"),EVAL_COOKIE_LAW_INFO_DETECT:()=>!!window.CLI,EVAL_COOKIE_MANAGER_POPUP_0:()=>!1===JSON.parse(document.cookie.split(";").find((e=>e.trim().startsWith("CookieLevel"))).split("=")[1]).social,EVAL_COOKIEALERT_0:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_1:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_2:()=>!0===window.CookieConsent.declined,EVAL_COOKIEFIRST_0:()=>{return!1===(e=JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("cookiefirst"))).trim()).split("=")[1])).performance&&!1===e.functional&&!1===e.advertising;var e},EVAL_COOKIEFIRST_1:()=>document.querySelectorAll("button[data-cookiefirst-accent-color=true][role=checkbox]:not([disabled])").forEach((e=>"true"==e.getAttribute("aria-checked")&&e.click()))||!0,EVAL_COOKIEINFORMATION_0:()=>CookieInformation.declineAllCategories()||!0,EVAL_COOKIEINFORMATION_1:()=>CookieInformation.submitAllCategories()||!0,EVAL_COOKIEINFORMATION_2:()=>document.cookie.includes("CookieInformationConsent="),EVAL_COOKIEYES_0:()=>document.cookie.includes("advertisement:no"),EVAL_DAILYMOTION_0:()=>!!document.cookie.match("dm-euconsent-v2"),EVAL_DNDBEYOND_TEST:()=>document.cookie.includes("cookie-consent=denied"),EVAL_DSGVO_0:()=>!document.cookie.includes("sp_dsgvo_cookie_settings"),EVAL_DUNELM_0:()=>document.cookie.includes("cc_functional=0")&&document.cookie.includes("cc_targeting=0"),EVAL_ETSY_0:()=>document.querySelectorAll(".gdpr-overlay-body input").forEach((e=>{e.checked=!1}))||!0,EVAL_ETSY_1:()=>document.querySelector(".gdpr-overlay-view button[data-wt-overlay-close]").click()||!0,EVAL_EU_COOKIE_COMPLIANCE_0:()=>-1===document.cookie.indexOf("cookie-agreed=2"),EVAL_EU_COOKIE_LAW_0:()=>!document.cookie.includes("euCookie"),EVAL_EZOIC_0:()=>ezCMP.handleAcceptAllClick(),EVAL_EZOIC_1:()=>!!document.cookie.match(/ez-consent-tcf/),EVAL_GOOGLE_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_HEMA_TEST_0:()=>document.cookie.includes("cookies_rejected=1"),EVAL_IUBENDA_0:()=>document.querySelectorAll(".purposes-item input[type=checkbox]:not([disabled])").forEach((e=>{e.checked&&e.click()}))||!0,EVAL_IUBENDA_1:()=>!!document.cookie.match(/_iub_cs-\d+=/),EVAL_IWINK_TEST:()=>document.cookie.includes("cookie_permission_granted=no"),EVAL_JQUERY_COOKIEBAR_0:()=>!document.cookie.includes("cookies-state=accepted"),EVAL_MEDIAVINE_0:()=>document.querySelectorAll('[data-name="mediavine-gdpr-cmp"] input[type=checkbox]').forEach((e=>e.checked&&e.click()))||!0,EVAL_MICROSOFT_0:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Reject|Ablehnen")))[0].click()||!0,EVAL_MICROSOFT_1:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Accept|Annehmen")))[0].click()||!0,EVAL_MICROSOFT_2:()=>!!document.cookie.match("MSCC|GHCC"),EVAL_MOOVE_0:()=>document.querySelectorAll("#moove_gdpr_cookie_modal input").forEach((e=>{e.disabled||"moove_gdpr_strict_cookies"===e.name||(e.checked=!1)}))||!0,EVAL_ONENINETWO_0:()=>document.cookie.includes("CC_ADVERTISING=NO")&&document.cookie.includes("CC_ANALYTICS=NO"),EVAL_OPERA_0:()=>document.cookie.includes("cookie_consent_essential=true")&&!document.cookie.includes("cookie_consent_marketing=true"),EVAL_PAYPAL_0:()=>!0===document.cookie.includes("cookie_prefs"),EVAL_PRIMEBOX_0:()=>!document.cookie.includes("cb-enabled=accepted"),EVAL_PUBTECH_0:()=>document.cookie.includes("euconsent-v2")&&(document.cookie.match(/.YAAAAAAAAAAA/)||document.cookie.match(/.aAAAAAAAAAAA/)||document.cookie.match(/.YAAACFgAAAAA/)),EVAL_REDDIT_0:()=>document.cookie.includes("eu_cookie={%22opted%22:true%2C%22nonessential%22:false}"),EVAL_SIBBO_0:()=>!!window.localStorage.getItem("euconsent-v2"),EVAL_SIRDATA_UNBLOCK_SCROLL:()=>(document.documentElement.classList.forEach((e=>{e.startsWith("sd-cmp-")&&document.documentElement.classList.remove(e)})),!0),EVAL_SNIGEL_0:()=>!!document.cookie.match("snconsent"),EVAL_STEAMPOWERED_0:()=>2===JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>e.trim().startsWith("cookieSettings"))).split("=")[1])).preference_state,EVAL_SVT_TEST:()=>document.cookie.includes('cookie-consent-1={"optedIn":true,"functionality":false,"statistics":false}'),EVAL_TAKEALOT_0:()=>document.body.classList.remove("freeze")||(document.body.style="")||!0,EVAL_TARTEAUCITRON_0:()=>tarteaucitron.userInterface.respondAll(!1)||!0,EVAL_TARTEAUCITRON_1:()=>tarteaucitron.userInterface.respondAll(!0)||!0,EVAL_TARTEAUCITRON_2:()=>document.cookie.match(/tarteaucitron=[^;]*/)[0].includes("false"),EVAL_TAUNTON_TEST:()=>document.cookie.includes("taunton_user_consent_submitted=true"),EVAL_TEALIUM_0:()=>void 0!==window.utag&&"object"==typeof utag.gdpr,EVAL_TEALIUM_1:()=>utag.gdpr.setConsentValue(!1)||!0,EVAL_TEALIUM_DONOTSELL:()=>utag.gdpr.dns?.setDnsState(!1)||!0,EVAL_TEALIUM_2:()=>utag.gdpr.setConsentValue(!0)||!0,EVAL_TEALIUM_3:()=>1!==utag.gdpr.getConsentState(),EVAL_TEALIUM_DONOTSELL_CHECK:()=>1!==utag.gdpr.dns?.getDnsState(),EVAL_TESTCMP_0:()=>"button_clicked"===window.results.results[0],EVAL_TESTCMP_COSMETIC_0:()=>"banner_hidden"===window.results.results[0],EVAL_THEFREEDICTIONARY_0:()=>cmpUi.showPurposes()||cmpUi.rejectAll()||!0,EVAL_THEFREEDICTIONARY_1:()=>cmpUi.allowAll()||!0,EVAL_THEVERGE_0:()=>document.cookie.includes("_duet_gdpr_acknowledged=1"),EVAL_UBUNTU_COM_0:()=>document.cookie.includes("_cookies_accepted=essential"),EVAL_UK_COOKIE_CONSENT_0:()=>!document.cookie.includes("catAccCookies"),EVAL_USERCENTRICS_API_0:()=>"object"==typeof UC_UI,EVAL_USERCENTRICS_API_1:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_2:()=>!!UC_UI.denyAllConsents(),EVAL_USERCENTRICS_API_3:()=>!!UC_UI.acceptAllConsents(),EVAL_USERCENTRICS_API_4:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_5:()=>!0===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_API_6:()=>!1===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_BUTTON_0:()=>JSON.parse(localStorage.getItem("usercentrics")).consents.every((e=>e.isEssential||!e.consentStatus)),EVAL_WAITROSE_0:()=>Array.from(document.querySelectorAll("label[id$=cookies-deny-label]")).forEach((e=>e.click()))||!0,EVAL_WAITROSE_1:()=>document.cookie.includes("wtr_cookies_advertising=0")&&document.cookie.includes("wtr_cookies_analytics=0"),EVAL_WP_COOKIE_NOTICE_0:()=>document.cookie.includes("wpl_viewed_cookie=no"),EVAL_XE_TEST:()=>document.cookie.includes("xeConsentState={%22performance%22:false%2C%22marketing%22:false%2C%22compliance%22:false}"),EVAL_XING_0:()=>document.cookie.includes("userConsent=%7B%22marketing%22%3Afalse"),EVAL_YOUTUBE_DESKTOP_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_YOUTUBE_MOBILE_0:()=>!!document.cookie.match(/SOCS=CAE/)};var l={main:!0,frame:!1,urlPattern:""},p=class{constructor(e){this.runContext=l,this.autoconsent=e}get hasSelfTest(){throw new Error("Not Implemented")}get isIntermediate(){throw new Error("Not Implemented")}get isCosmetic(){throw new Error("Not Implemented")}mainWorldEval(e){const t=r[e];if(!t)return console.warn("Snippet not found",e),Promise.resolve(!1);const o=this.autoconsent.config.logs;if(this.autoconsent.config.isMainWorld){o.evals&&console.log("inline eval:",e,t);let c=!1;try{c=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(c)}const c=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,c),s(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...l,...this.runContext},t=window.top===window;return!(t&&!e.main)&&(!(!t&&!e.frame)&&!(e.urlPattern&&!window.location.href.match(e.urlPattern)))}detectCmp(){throw new Error("Not Implemented")}async detectPopup(){return!1}optOut(){throw new Error("Not Implemented")}optIn(){throw new Error("Not Implemented")}openCmp(){throw new Error("Not Implemented")}async test(){return Promise.resolve(!0)}click(e,t=!1){return this.autoconsent.domActions.click(e,t)}elementExists(e){return this.autoconsent.domActions.elementExists(e)}elementVisible(e,t){return this.autoconsent.domActions.elementVisible(e,t)}waitForElement(e,t){return this.autoconsent.domActions.waitForElement(e,t)}waitForVisible(e,t,o){return this.autoconsent.domActions.waitForVisible(e,t,o)}waitForThenClick(e,t,o){return this.autoconsent.domActions.waitForThenClick(e,t,o)}wait(e){return this.autoconsent.domActions.wait(e)}hide(e,t){return this.autoconsent.domActions.hide(e,t)}prehide(e){return this.autoconsent.domActions.prehide(e)}undoPrehide(){return this.autoconsent.domActions.undoPrehide()}querySingleReplySelector(e,t){return this.autoconsent.domActions.querySingleReplySelector(e,t)}querySelectorChain(e){return this.autoconsent.domActions.querySelectorChain(e)}elementSelector(e){return this.autoconsent.domActions.elementSelector(e)}},d=class extends p{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||l}get hasSelfTest(){return!!this.rule.test}get isIntermediate(){return!!this.rule.intermediate}get isCosmetic(){return!!this.rule.cosmetic}get prehideSelectors(){return this.rule.prehideSelectors}async detectCmp(){return!!this.rule.detectCmp&&this._runRulesParallel(this.rule.detectCmp)}async detectPopup(){return!!this.rule.detectPopup&&this._runRulesSequentially(this.rule.detectPopup)}async optOut(){const e=this.autoconsent.config.logs;return!!this.rule.optOut&&(e.lifecycle&&console.log("Initiated optOut()",this.rule.optOut),this._runRulesSequentially(this.rule.optOut))}async optIn(){const e=this.autoconsent.config.logs;return!!this.rule.optIn&&(e.lifecycle&&console.log("Initiated optIn()",this.rule.optIn),this._runRulesSequentially(this.rule.optIn))}async openCmp(){return!!this.rule.openCmp&&this._runRulesSequentially(this.rule.openCmp)}async test(){return this.hasSelfTest?this._runRulesSequentially(this.rule.test):super.test()}async evaluateRuleStep(e){const t=[],o=this.autoconsent.config.logs;if(e.exists&&t.push(this.elementExists(e.exists)),e.visible&&t.push(this.elementVisible(e.visible,e.check)),e.eval){const o=this.mainWorldEval(e.eval);t.push(o)}if(e.waitFor&&t.push(this.waitForElement(e.waitFor,e.timeout)),e.waitForVisible&&t.push(this.waitForVisible(e.waitForVisible,e.timeout,e.check)),e.click&&t.push(this.click(e.click,e.all)),e.waitForThenClick&&t.push(this.waitForThenClick(e.waitForThenClick,e.timeout,e.all)),e.wait&&t.push(this.wait(e.wait)),e.hide&&t.push(this.hide(e.hide,e.method)),e.if){if(!e.if.exists&&!e.if.visible)return console.error("invalid conditional rule",e.if),!1;const c=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",c),c?t.push(this._runRulesSequentially(e.then)):e.else?t.push(this._runRulesSequentially(e.else)):t.push(!0)}if(e.any){for(const t of e.any)if(await this.evaluateRuleStep(t))return!0;return!1}if(0===t.length)return o.errors&&console.warn("Unrecognized rule",e),!1;return(await Promise.all(t)).reduce(((e,t)=>e&&t),!0)}async _runRulesParallel(e){const t=e.map((e=>this.evaluateRuleStep(e)));return(await Promise.all(t)).every((e=>!!e))}async _runRulesSequentially(e){const t=this.autoconsent.config.logs;for(const o of e){t.rulesteps&&console.log("Running rule...",o);const e=await this.evaluateRuleStep(o);if(t.rulesteps&&console.log("...rule result",e),!e&&!o.optional)return!1}return!0}};function u(e="autoconsent-css-rules"){const t=`style#${e}`,o=document.querySelector(t);if(o&&o instanceof HTMLStyleElement)return o;{const t=document.head||document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");return o.id=e,t.appendChild(o),o}}function m(e,t,o="display"){const c=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=c,t.length>0)}async function h(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(h(e,t-1,o))}),o)})):Promise.resolve(c)}function k(e){if(!e)return!1;if(null!==e.offsetParent)return!0;{const t=window.getComputedStyle(e);if("fixed"===t.position&&"none"!==t.display)return!0}return!1}function b(e){const t={enabled:!0,autoAction:"optOut",disabledCmps:[],enablePrehide:!0,enableCosmeticRules:!0,detectRetries:20,isMainWorld:!1,prehideTimeout:2e3,logs:{lifecycle:!1,rulesteps:!1,evals:!1,errors:!0,messages:!1}},o=(c=t,globalThis.structuredClone?structuredClone(c):JSON.parse(JSON.stringify(c)));var c;for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}var _="#truste-show-consent",g="#truste-consent-track",y=[class extends p{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${g}`],this.runContext={main:!0,frame:!1},this._shortcutButton=null,this._optInDone=!1}get hasSelfTest(){return!1}get isIntermediate(){return!this._optInDone&&!this._shortcutButton}get isCosmetic(){return!1}async detectCmp(){const e=this.elementExists(`${_},${g}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${g}`,"all")}openFrame(){this.click(_)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(m(u(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${g}`),this.click(_),setTimeout((()=>{u().remove()}),1e4),!0)}async optIn(){return this._optInDone=!0,this.click("#truste-consent-button")}async openCmp(){return!0}async test(){return await this.mainWorldEval("EVAL_TRUSTARC_TOP")}},class extends p{constructor(){super(...arguments),this.name="TrustArc-frame",this.runContext={main:!1,frame:!0,urlPattern:"^https://consent-pref\\.trustarc\\.com/\\?"}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return!0}async detectPopup(){return this.elementVisible("#defaultpreferencemanager","any")&&this.elementVisible(".mainContent","any")}async navigateToSettings(){return await h((async()=>this.elementExists(".shp")||this.elementVisible(".advance","any")||this.elementExists(".switch span:first-child")),10,500),this.elementExists(".shp")&&this.click(".shp"),await this.waitForElement(".prefPanel",5e3),this.elementVisible(".advance","any")&&this.click(".advance"),await h((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await h((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",5e3),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',3e3),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",5e3),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",5e3),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",3e5),!0))}async optIn(){return this.click(".call")||(await this.navigateToSettings(),this.click(".switch span:nth-child(2)",!0),this.click(".submit"),this.waitForElement("#gwt-debug-close_id",3e5).then((()=>{this.click("#gwt-debug-close_id")}))),!0}},class extends p{constructor(){super(...arguments),this.name="Cybotcookiebot",this.prehideSelectors=["#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#dtcookie-container,#cookiebanner,#cb-cookieoverlay,.modal--cookie-banner,#cookiebanner_outer,#CookieBanner"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return await this.mainWorldEval("EVAL_COOKIEBOT_1")}async detectPopup(){return this.mainWorldEval("EVAL_COOKIEBOT_2")}async optOut(){await this.wait(500);let e=await this.mainWorldEval("EVAL_COOKIEBOT_3");return await this.wait(500),e=e&&await this.mainWorldEval("EVAL_COOKIEBOT_4"),e}async optIn(){return this.elementExists("#dtcookie-container")?this.click(".h-dtcookie-accept"):(this.click(".CybotCookiebotDialogBodyLevelButton:not(:checked):enabled",!0),this.click("#CybotCookiebotDialogBodyLevelButtonAccept"),this.click("#CybotCookiebotDialogBodyButtonAccept"),!0)}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_COOKIEBOT_5")}},class extends p{constructor(){super(...arguments),this.name="Sourcepoint-frame",this.prehideSelectors=["div[id^='sp_message_container_'],.message-overlay","#sp_privacy_manager_container"],this.ccpaNotice=!1,this.ccpaPopup=!1,this.runContext={main:!1,frame:!0}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){const e=new URL(location.href);return e.searchParams.has("message_id")&&"ccpa-notice.sp-prod.net"===e.hostname?(this.ccpaNotice=!0,!0):"ccpa-pm.sp-prod.net"===e.hostname?(this.ccpaPopup=!0,!0):("/index.html"===e.pathname||"/privacy-manager/index.html"===e.pathname||"/ccpa_pm/index.html"===e.pathname)&&(e.searchParams.has("message_id")||e.searchParams.has("requestUUID")||e.searchParams.has("consentUUID"))}async detectPopup(){return!!this.ccpaNotice||(this.ccpaPopup?await this.waitForElement(".priv-save-btn",2e3):(await this.waitForElement(".sp_choice_type_11,.sp_choice_type_12,.sp_choice_type_13,.sp_choice_type_ACCEPT_ALL,.sp_choice_type_SAVE_AND_EXIT",2e3),!this.elementExists(".sp_choice_type_9")))}async optIn(){return await this.waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL",2e3),!!this.click(".sp_choice_type_11")||!!this.click(".sp_choice_type_ACCEPT_ALL")}isManagerOpen(){return"/privacy-manager/index.html"===location.pathname||"/ccpa_pm/index.html"===location.pathname}async optOut(){const e=this.autoconsent.config.logs;if(this.ccpaPopup){const e=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.neutral.on .right");for(const t of e)t.click();const t=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.switch-bg.on");for(const e of t)e.click();return this.click(".priv-save-btn")}if(!this.isManagerOpen()){if(!await this.waitForElement(".sp_choice_type_12,.sp_choice_type_13"))return!1;if(!this.elementExists(".sp_choice_type_12"))return this.click(".sp_choice_type_13");this.click(".sp_choice_type_12"),await h((()=>this.isManagerOpen()),200,100)}await this.waitForElement(".type-modal",2e4),this.waitForThenClick(".ccpa-stack .pm-switch[aria-checked=true] .slider",500,!0);try{const e=".sp_choice_type_REJECT_ALL",t=".reject-toggle",o=await Promise.race([this.waitForElement(e,2e3).then((e=>e?0:-1)),this.waitForElement(t,2e3).then((e=>e?1:-1)),this.waitForElement(".pm-features",2e3).then((e=>e?2:-1))]);if(0===o)return await this.wait(1500),this.click(e);1===o?this.click(t):2===o&&(await this.waitForElement(".pm-features",1e4),this.click(".checked > span",!0),this.click(".chevron"))}catch(t){e.errors&&console.warn(t)}return this.click(".sp_choice_type_SAVE_AND_EXIT")}},class extends p{constructor(){super(...arguments),this.name="consentmanager.net",this.prehideSelectors=["#cmpbox,#cmpbox2"],this.apiAvailable=!1}get hasSelfTest(){return this.apiAvailable}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.apiAvailable=await this.mainWorldEval("EVAL_CONSENTMANAGER_1"),!!this.apiAvailable||this.elementExists("#cmpbox")}async detectPopup(){return this.apiAvailable?(await this.wait(500),await this.mainWorldEval("EVAL_CONSENTMANAGER_2")):this.elementVisible("#cmpbox .cmpmore","any")}async optOut(){return await this.wait(500),this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_3"):!!this.click(".cmpboxbtnno")||(this.elementExists(".cmpwelcomeprpsbtn")?(this.click(".cmpwelcomeprpsbtn > a[aria-checked=true]",!0),this.click(".cmpboxbtnsave"),!0):(this.click(".cmpboxbtncustom"),await this.waitForElement(".cmptblbox",2e3),this.click(".cmptdchoice > a[aria-checked=true]",!0),this.click(".cmpboxbtnyescustomchoices"),this.hide("#cmpwrapper,#cmpbox","display"),!0))}async optIn(){return this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_4"):this.click(".cmpboxbtnyes")}async test(){if(this.apiAvailable)return await this.mainWorldEval("EVAL_CONSENTMANAGER_5")}},class extends p{constructor(){super(...arguments),this.name="Evidon"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#_evidon_banner")}async detectPopup(){return this.elementVisible("#_evidon_banner","any")}async optOut(){return this.click("#_evidon-decline-button")||(m(u(),"#evidon-prefdiag-overlay,#evidon-prefdiag-background"),this.click("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),this.click("#evidon-prefdiag-decline")),!0}async optIn(){return this.click("#_evidon-accept-button")}},class extends p{constructor(){super(...arguments),this.name="Onetrust",this.prehideSelectors=["#onetrust-banner-sdk,#onetrust-consent-sdk,.onetrust-pc-dark-filter,.js-consent-banner"],this.runContext={urlPattern:"^(?!.*https://www\\.nba\\.com/)"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#onetrust-banner-sdk,#onetrust-pc-sdk")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk,#onetrust-pc-sdk","any")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies"):(this.elementExists("#onetrust-pc-btn-handler")?this.click("#onetrust-pc-btn-handler"):this.click(".ot-sdk-show-settings,button.js-cookie-settings"),await this.waitForElement("#onetrust-consent-sdk",2e3),await this.wait(1e3),this.click("#onetrust-consent-sdk input.category-switch-handler:checked,.js-editor-toggle-state:checked",!0),await this.wait(1e3),await this.waitForElement(".save-preference-btn-handler,.js-consent-save",2e3),this.click(".save-preference-btn-handler,.js-consent-save"),await this.waitForVisible("#onetrust-banner-sdk",5e3,"none"),!0)}async optIn(){return this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies")}async test(){return await h((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends p{constructor(){super(...arguments),this.name="Klaro",this.prehideSelectors=[".klaro"],this.settingsOpen=!1}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".klaro > .cookie-modal")?(this.settingsOpen=!0,!0):this.elementExists(".klaro > .cookie-notice")}async detectPopup(){return this.elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal","any")}async optOut(){return!!this.click(".klaro .cn-decline")||(this.settingsOpen||(this.click(".klaro .cn-learn-more,.klaro .cm-button-manage"),await this.waitForElement(".klaro > .cookie-modal",2e3),this.settingsOpen=!0),!!this.click(".klaro .cn-decline")||(this.click(".cm-purpose:not(.cm-toggle-all) > input:not(.half-checked,.required,.only-required),.cm-purpose:not(.cm-toggle-all) > div > input:not(.half-checked,.required,.only-required)",!0),this.click(".cm-btn-accept,.cm-button")))}async optIn(){return!!this.click(".klaro .cm-btn-accept-all")||(this.settingsOpen?(this.click(".cm-purpose:not(.cm-toggle-all) > input.half-checked",!0),this.click(".cm-btn-accept")):this.click(".klaro .cookie-notice .cm-btn-success"))}async test(){return await this.mainWorldEval("EVAL_KLARO_1")}},class extends p{constructor(){super(...arguments),this.name="Uniconsent"}get prehideSelectors(){return[".unic",".modal:has(.unic)"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".unic .unic-box,.unic .unic-bar,.unic .unic-modal")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar,.unic .unic-modal","any")}async optOut(){if(await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic button").forEach((e=>{const t=e.textContent;(t.includes("Manage Options")||t.includes("Optionen verwalten"))&&e.click()})),await this.waitForElement(".unic input[type=checkbox]",1e3)){await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic input[type=checkbox]").forEach((e=>{e.checked&&e.click()}));for(const e of document.querySelectorAll(".unic button")){const t=e.textContent;for(const o of["Confirm Choices","Save Choices","Auswahl speichern"])if(t.includes(o))return e.click(),await this.wait(500),!0}}return!1}async optIn(){return this.waitForThenClick(".unic #unic-agree")}async test(){await this.wait(1e3);return!this.elementExists(".unic .unic-box,.unic .unic-bar")}},class extends p{constructor(){super(...arguments),this.prehideSelectors=[".cmp-root"],this.name="Conversant"}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".cmp-root .cmp-receptacle")}async detectPopup(){return this.elementVisible(".cmp-root .cmp-receptacle","any")}async optOut(){if(!await this.waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))return!1;if(!await this.waitForElement(".cmp-view-tab-tabs"))return!1;await this.waitForThenClick(".cmp-view-tab-tabs > :first-child"),await this.waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");for(const e of Array.from(document.querySelectorAll(".cmp-accordion-item"))){e.querySelector(".cmp-accordion-item-title").click(),await h((()=>!!e.querySelector(".cmp-accordion-item-content.cmp-active")),10,50);const t=e.querySelector(".cmp-accordion-item-content.cmp-active");t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e=>e.click())),t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e=>e.click()))}return await this.click(".cmp-main-button:not(.cmp-main-button--primary)"),!0}async optIn(){return this.waitForThenClick(".cmp-main-button.cmp-main-button--primary")}async test(){return document.cookie.includes("cmp-data=0")}},class extends p{constructor(){super(...arguments),this.name="tiktok.com",this.runContext={urlPattern:"tiktok"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}getShadowRoot(){const e=document.querySelector("tiktok-cookie-banner");return e?e.shadowRoot:null}async detectCmp(){return this.elementExists("tiktok-cookie-banner")}async detectPopup(){return k(this.getShadowRoot().querySelector(".tiktok-cookie-banner"))}async optOut(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:first-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no decline button found"),!1)}async optIn(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:last-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no accept button found"),!1)}async test(){const e=document.cookie.match(/cookie-consent=([^;]+)/);if(!e)return!1;const t=JSON.parse(decodeURIComponent(e[1]));return Object.values(t).every((e=>"boolean"!=typeof e||!1===e))}},class extends p{constructor(){super(...arguments),this.runContext={urlPattern:"^https://(www\\.)?airbnb\\.[^/]+/"},this.prehideSelectors=["div[data-testid=main-cookies-banner-container]",'div:has(> div:first-child):has(> div:last-child):has(> section [data-testid="strictly-necessary-cookies"])']}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div[data-testid=main-cookies-banner-container]")}async detectPopup(){return this.elementVisible("div[data-testid=main-cookies-banner-container","any")}async optOut(){let e;for(await this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._snbhip0");e=document.querySelector("[data-testid=modal-container] button[aria-checked=true]:not([disabled])");)e.click();return this.waitForThenClick("button[data-testid=save-btn]")}async optIn(){return this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._148dgdpk")}async test(){return await h((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}];var w=[{name:"192.com",detectCmp:[{exists:".ont-cookies"}],detectPopup:[{visible:".ont-cookies"}],optIn:[{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-ok2"}],optOut:[{click:".ont-cookes-btn-manage"},{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-choose"}],test:[{eval:"EVAL_ONENINETWO_0"}]},{name:"1password-com",cosmetic:!0,prehideSelectors:['footer #footer-root [aria-label="Cookie Consent"]'],detectCmp:[{exists:'footer #footer-root [aria-label="Cookie Consent"]'}],detectPopup:[{visible:'footer #footer-root [aria-label="Cookie Consent"]'}],optIn:[{click:'footer #footer-root [aria-label="Cookie Consent"] button'}],optOut:[{hide:'footer #footer-root [aria-label="Cookie Consent"]'}]},{name:"abconcerts.be",vendorUrl:"https://unknown",intermediate:!1,prehideSelectors:["dialog.cookie-consent"],detectCmp:[{exists:"dialog.cookie-consent form.cookie-consent__form"}],detectPopup:[{visible:"dialog.cookie-consent form.cookie-consent__form"}],optIn:[{waitForThenClick:"dialog.cookie-consent form.cookie-consent__form button[value=yes]"}],optOut:[{if:{exists:"dialog.cookie-consent form.cookie-consent__form button[value=no]"},then:[{click:"dialog.cookie-consent form.cookie-consent__form button[value=no]"}],else:[{click:"dialog.cookie-consent form.cookie-consent__form button.cookie-consent__options-toggle"},{waitForThenClick:'dialog.cookie-consent form.cookie-consent__form button[value="save_options"]'}]}]},{name:"activobank.pt",runContext:{urlPattern:"^https://(www\\.)?activobank\\.pt"},prehideSelectors:["aside#cookies,.overlay-cookies"],detectCmp:[{exists:"#cookies .cookies-btn"}],detectPopup:[{visible:"#cookies #submitCookies"}],optIn:[{waitForThenClick:"#cookies #submitCookies"}],optOut:[{waitForThenClick:"#cookies #rejectCookies"}]},{name:"Adroll",prehideSelectors:["#adroll_consent_container"],detectCmp:[{exists:"#adroll_consent_container"}],detectPopup:[{visible:"#adroll_consent_container"}],optIn:[{waitForThenClick:"#adroll_consent_accept"}],optOut:[{waitForThenClick:"#adroll_consent_reject"}],test:[{eval:"EVAL_ADROLL_0"}]},{name:"affinity.serif.com",detectCmp:[{exists:".c-cookie-banner button[data-qa='allow-all-cookies']"}],detectPopup:[{visible:".c-cookie-banner"}],optIn:[{click:'button[data-qa="allow-all-cookies"]'}],optOut:[{click:'button[data-qa="manage-cookies"]'},{waitFor:'.c-cookie-banner ~ [role="dialog"]'},{waitForThenClick:'.c-cookie-banner ~ [role="dialog"] input[type="checkbox"][value="true"]',all:!0},{click:'.c-cookie-banner ~ [role="dialog"] .c-modal__action button'}],test:[{wait:500},{eval:"EVAL_AFFINITY_SERIF_COM_0"}]},{name:"agolde.com",cosmetic:!0,prehideSelectors:["#modal-1 div[data-micromodal-close]"],detectCmp:[{exists:"#modal-1 div[aria-labelledby=modal-1-title]"}],detectPopup:[{exists:"#modal-1 div[data-micromodal-close]"}],optIn:[{click:'button[aria-label="Close modal"]'}],optOut:[{hide:"#modal-1 div[data-micromodal-close]"}]},{name:"aliexpress",vendorUrl:"https://aliexpress.com/",runContext:{urlPattern:"^https://.*\\.aliexpress\\.com/"},prehideSelectors:["#gdpr-new-container"],detectCmp:[{exists:"#gdpr-new-container"}],detectPopup:[{visible:"#gdpr-new-container"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept"}],optOut:[{waitForThenClick:"#gdpr-new-container .btn-more"},{waitFor:"#gdpr-new-container .gdpr-dialog-switcher"},{click:"#gdpr-new-container .switcher-on",all:!0,optional:!0},{click:"#gdpr-new-container .btn-save"}]},{name:"almacmp",prehideSelectors:["#alma-cmpv2-container"],detectCmp:[{exists:"#alma-cmpv2-container"}],detectPopup:[{visible:"#alma-cmpv2-container #almacmp-modal-layer1"}],optIn:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalConfirmBtn"}],optOut:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalSettingBtn"},{waitFor:"#alma-cmpv2-container #almacmp-modal-layer2"},{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer2 #almacmp-reject-all-layer2"}],test:[{eval:"EVAL_ALMACMP_0"}]},{name:"altium.com",cosmetic:!0,prehideSelectors:[".altium-privacy-bar"],detectCmp:[{exists:".altium-privacy-bar"}],detectPopup:[{exists:".altium-privacy-bar"}],optIn:[{click:"a.altium-privacy-bar__btn"}],optOut:[{hide:".altium-privacy-bar"}]},{name:"amazon.com",prehideSelectors:['span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'],detectCmp:[{exists:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],detectPopup:[{visible:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],optIn:[{waitForVisible:"#sp-cc-accept"},{wait:500},{click:"#sp-cc-accept"}],optOut:[{waitForVisible:"#sp-cc-rejectall-link"},{wait:500},{click:"#sp-cc-rejectall-link"}]},{name:"aquasana.com",cosmetic:!0,prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{click:"#accept_consent"}],optOut:[{hide:"#consent-tracking"}]},{name:"arbeitsagentur",vendorUrl:"https://www.arbeitsagentur.de/",prehideSelectors:[".modal-open bahf-cookie-disclaimer-dpl3"],detectCmp:[{exists:"bahf-cookie-disclaimer-dpl3"}],detectPopup:[{visible:"bahf-cookie-disclaimer-dpl3"}],optIn:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-primary"]}],optOut:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-contrast"]}],test:[{eval:"EVAL_ARBEITSAGENTUR_TEST"}]},{name:"asus",vendorUrl:"https://www.asus.com/",runContext:{urlPattern:"^https://www\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info,#cookie-policy-info-bg"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{waitForThenClick:'#cookie-policy-info [data-agree="Accept Cookies"]'}],optOut:[{if:{exists:"#cookie-policy-info .btn-reject"},then:[{waitForThenClick:"#cookie-policy-info .btn-reject"}],else:[{waitForThenClick:"#cookie-policy-info .btn-setting"},{waitForThenClick:'#cookie-policy-lightbox-wrapper [data-agree="Save Settings"]'}]}]},{name:"athlinks-com",runContext:{urlPattern:"^https://(www\\.)?athlinks\\.com/"},cosmetic:!0,prehideSelectors:["#footer-container ~ div"],detectCmp:[{exists:"#footer-container ~ div"}],detectPopup:[{visible:"#footer-container > div"}],optIn:[{click:"#footer-container ~ div button"}],optOut:[{hide:"#footer-container ~ div"}]},{name:"ausopen.com",cosmetic:!0,detectCmp:[{exists:".gdpr-popup__message"}],detectPopup:[{visible:".gdpr-popup__message"}],optOut:[{hide:".gdpr-popup__message"}],optIn:[{click:".gdpr-popup__message button"}]},{name:"automattic-cmp-optout",prehideSelectors:['form[class*="cookie-banner"][method="post"]'],detectCmp:[{exists:'form[class*="cookie-banner"][method="post"]'}],detectPopup:[{visible:'form[class*="cookie-banner"][method="post"]'}],optIn:[{click:'a[class*="accept-all-button"]'}],optOut:[{click:'form[class*="cookie-banner"] div[class*="simple-options"] a[class*="customize-button"]'},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:'a[class*="accept-selection-button"]'}]},{name:"aws.amazon.com",prehideSelectors:["#awsccc-cb-content","#awsccc-cs-container","#awsccc-cs-modalOverlay","#awsccc-cs-container-inner"],detectCmp:[{exists:"#awsccc-cb-content"}],detectPopup:[{visible:"#awsccc-cb-content"}],optIn:[{click:"button[data-id=awsccc-cb-btn-accept"}],optOut:[{click:"button[data-id=awsccc-cb-btn-customize]"},{waitFor:"input[aria-checked]"},{click:"input[aria-checked=true]",all:!0,optional:!0},{click:"button[data-id=awsccc-cs-btn-save]"}]},{name:"axeptio",prehideSelectors:[".axeptio_widget"],detectCmp:[{exists:".axeptio_widget"}],detectPopup:[{visible:".axeptio_widget"}],optIn:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_acceptAll"}],optOut:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_dismiss"}],test:[{eval:"EVAL_AXEPTIO_0"}]},{name:"baden-wuerttemberg.de",prehideSelectors:[".cookie-alert.t-dark"],cosmetic:!0,detectCmp:[{exists:".cookie-alert.t-dark"}],detectPopup:[{visible:".cookie-alert.t-dark"}],optIn:[{click:".cookie-alert__form input:not([disabled]):not([checked])"},{click:".cookie-alert__button button"}],optOut:[{hide:".cookie-alert.t-dark"}]},{name:"bahn-de",vendorUrl:"https://www.bahn.de/",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?bahn\\.de/"},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:["body > div:first-child","#consent-layer"]}],detectPopup:[{visible:["body > div:first-child","#consent-layer"]}],optIn:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-all-cookies"]}],optOut:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-essential-cookies"]}],test:[{eval:"EVAL_BAHN_TEST"}]},{name:"bbb.org",runContext:{urlPattern:"^https://www\\.bbb\\.org/"},cosmetic:!0,prehideSelectors:['div[aria-label="use of cookies on bbb.org"]'],detectCmp:[{exists:'div[aria-label="use of cookies on bbb.org"]'}],detectPopup:[{visible:'div[aria-label="use of cookies on bbb.org"]'}],optIn:[{click:'div[aria-label="use of cookies on bbb.org"] button.bds-button-unstyled span.visually-hidden'}],optOut:[{hide:'div[aria-label="use of cookies on bbb.org"]'}]},{name:"bing.com",prehideSelectors:["#bnp_container"],detectCmp:[{exists:"#bnp_cookie_banner"}],detectPopup:[{visible:"#bnp_cookie_banner"}],optIn:[{click:"#bnp_btn_accept"}],optOut:[{click:"#bnp_btn_preference"},{click:"#mcp_savesettings"}],test:[{eval:"EVAL_BING_0"}]},{name:"blocksy",vendorUrl:"https://creativethemes.com/blocksy/docs/extensions/cookies-consent/",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[".cookie-notification"],detectCmp:[{exists:"#blocksy-ext-cookies-consent-styles-css"}],detectPopup:[{visible:".cookie-notification"}],optIn:[{click:".cookie-notification .ct-cookies-decline-button"}],optOut:[{waitForThenClick:".cookie-notification .ct-cookies-decline-button"}],test:[{eval:"EVAL_BLOCKSY_0"}]},{name:"borlabs",detectCmp:[{exists:"._brlbs-block-content"}],detectPopup:[{visible:"._brlbs-bar-wrap,._brlbs-box-wrap"}],optIn:[{click:"a[data-cookie-accept-all]"}],optOut:[{click:"a[data-cookie-individual]"},{waitForVisible:".cookie-preference"},{click:"input[data-borlabs-cookie-checkbox]:checked",all:!0,optional:!0},{click:"#CookiePrefSave"},{wait:500}],prehideSelectors:["#BorlabsCookieBox"],test:[{eval:"EVAL_BORLABS_0"}]},{name:"bundesregierung.de",prehideSelectors:[".bpa-cookie-banner"],detectCmp:[{exists:".bpa-cookie-banner"}],detectPopup:[{visible:".bpa-cookie-banner .bpa-module-full-hero"}],optIn:[{click:".bpa-accept-all-button"}],optOut:[{wait:500,comment:"click is not immediately recognized"},{waitForThenClick:".bpa-close-button"}],test:[{eval:"EVAL_BUNDESREGIERUNG_DE_0"}]},{name:"burpee.com",cosmetic:!0,prehideSelectors:["#notice-cookie-block"],detectCmp:[{exists:"#notice-cookie-block"}],detectPopup:[{exists:"#html-body #notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{hide:"#html-body #notice-cookie-block, #notice-cookie"}]},{name:"canva.com",prehideSelectors:['div[role="dialog"] a[data-anchor-id="cookie-policy"]'],detectCmp:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],detectPopup:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],optIn:[{click:'div[role="dialog"] button:nth-child(1)'}],optOut:[{if:{exists:'div[role="dialog"] button:nth-child(3)'},then:[{click:'div[role="dialog"] button:nth-child(2)'}],else:[{click:'div[role="dialog"] button:nth-child(2)'},{waitFor:'div[role="dialog"] a[data-anchor-id="privacy-policy"]'},{click:'div[role="dialog"] button:nth-child(2)'},{click:'div[role="dialog"] div:last-child button:only-child'}]}],test:[{eval:"EVAL_CANVA_0"}]},{name:"canyon.com",runContext:{urlPattern:"^https://www\\.canyon\\.com/"},prehideSelectors:["div.modal.cookiesModal.is-open"],detectCmp:[{exists:"div.modal.cookiesModal.is-open"}],detectPopup:[{visible:"div.modal.cookiesModal.is-open"}],optIn:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-submit"]'}],optOut:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-manage-cookies"]'},{waitForThenClick:"button#js-manage-data-privacy-save-button"}]},{name:"cc-banner-springer",prehideSelectors:[".cc-banner[data-cc-banner]"],detectCmp:[{exists:".cc-banner[data-cc-banner]"}],detectPopup:[{visible:".cc-banner[data-cc-banner]"}],optIn:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=accept]"}],optOut:[{if:{exists:".cc-banner[data-cc-banner] button[data-cc-action=reject]"},then:[{click:".cc-banner[data-cc-banner] button[data-cc-action=reject]"}],else:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=preferences]"},{waitFor:".cc-preferences[data-cc-preferences]"},{click:".cc-preferences[data-cc-preferences] input[type=radio][data-cc-action=toggle-category][value=off]",all:!0,optional:!0},{if:{exists:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"},then:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"}],else:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=save]"}]}]}],test:[{eval:"EVAL_CC_BANNER2_0"}]},{name:"cc_banner",cosmetic:!0,prehideSelectors:[".cc_banner-wrapper"],detectCmp:[{exists:".cc_banner-wrapper"}],detectPopup:[{visible:".cc_banner"}],optIn:[{click:".cc_btn_accept_all"}],optOut:[{hide:".cc_banner-wrapper"}]},{name:"ciaopeople.it",prehideSelectors:["#cp-gdpr-choices"],detectCmp:[{exists:"#cp-gdpr-choices"}],detectPopup:[{visible:"#cp-gdpr-choices"}],optIn:[{waitForThenClick:".gdpr-btm__right > button:nth-child(2)"}],optOut:[{waitForThenClick:".gdpr-top-content > button"},{waitFor:".gdpr-top-back"},{waitForThenClick:".gdpr-btm__right > button:nth-child(1)"}],test:[{visible:"#cp-gdpr-choices",check:"none"}]},{vendorUrl:"https://www.civicuk.com/cookie-control/",name:"civic-cookie-control",prehideSelectors:["#ccc-module,#ccc-overlay"],detectCmp:[{exists:"#ccc-module"}],detectPopup:[{visible:"#ccc"},{visible:"#ccc-module"}],optOut:[{click:"#ccc-reject-settings"}],optIn:[{click:"#ccc-recommended-settings"}]},{name:"click.io",prehideSelectors:["#cl-consent"],detectCmp:[{exists:"#cl-consent"}],detectPopup:[{visible:"#cl-consent"}],optIn:[{waitForThenClick:'#cl-consent [data-role="b_agree"]'}],optOut:[{waitFor:'#cl-consent [data-role="b_options"]'},{wait:500},{click:'#cl-consent [data-role="b_options"]'},{waitFor:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]'},{click:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]',all:!0},{click:'[data-role="b_save"]'}],test:[{eval:"EVAL_CLICKIO_0",comment:"TODO: this only checks if we interacted at all"}]},{name:"clinch",intermediate:!1,runContext:{frame:!1,main:!0},prehideSelectors:[".consent-modal[role=dialog]"],detectCmp:[{exists:".consent-modal[role=dialog]"}],detectPopup:[{visible:".consent-modal[role=dialog]"}],optIn:[{click:"#consent_agree"}],optOut:[{if:{exists:"#consent_reject"},then:[{click:"#consent_reject"}],else:[{click:"#manage_cookie_preferences"},{click:"#cookie_consent_preferences input:checked",all:!0,optional:!0},{click:"#consent_save"}]}],test:[{eval:"EVAL_CLINCH_0"}]},{name:"clustrmaps.com",runContext:{urlPattern:"^https://(www\\.)?clustrmaps\\.com/"},cosmetic:!0,prehideSelectors:["#gdpr-cookie-message"],detectCmp:[{exists:"#gdpr-cookie-message"}],detectPopup:[{visible:"#gdpr-cookie-message"}],optIn:[{click:"button#gdpr-cookie-accept"}],optOut:[{hide:"#gdpr-cookie-message"}]},{name:"coinbase",intermediate:!1,runContext:{frame:!0,main:!0,urlPattern:"^https://(www|help)\\.coinbase\\.com"},prehideSelectors:[],detectCmp:[{exists:"div[class^=CookieBannerContent__Container]"}],detectPopup:[{visible:"div[class^=CookieBannerContent__Container]"}],optIn:[{click:"div[class^=CookieBannerContent__CTA] :nth-last-child(1)"}],optOut:[{click:"button[class^=CookieBannerContent__Settings]"},{click:"div[class^=CookiePreferencesModal__CategoryContainer] input:checked",all:!0,optional:!0},{click:"div[class^=CookiePreferencesModal__ButtonContainer] > button"}],test:[{eval:"EVAL_COINBASE_0"}]},{name:"Complianz banner",prehideSelectors:["#cmplz-cookiebanner-container"],detectCmp:[{exists:"#cmplz-cookiebanner-container .cmplz-cookiebanner"}],detectPopup:[{visible:"#cmplz-cookiebanner-container .cmplz-cookiebanner",check:"any"}],optIn:[{waitForThenClick:".cmplz-cookiebanner .cmplz-accept"}],optOut:[{waitForThenClick:".cmplz-cookiebanner .cmplz-deny"}],test:[{eval:"EVAL_COMPLIANZ_BANNER_0"}]},{name:"Complianz categories",prehideSelectors:['.cc-type-categories[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"] .cc-dismiss'},then:[{click:".cc-dismiss"}],else:[{click:".cc-type-categories input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-save"}]}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{if:{exists:".cc-deny"},then:[{click:".cc-deny"}],else:[{hide:'[aria-describedby="cookieconsent:desc"]'}]}]},{name:"Complianz opt-both",prehideSelectors:['[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{waitForThenClick:".cc-deny"}]},{name:"Complianz optin",prehideSelectors:['.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{visible:".cc-deny"},then:[{click:".cc-deny"}],else:[{if:{visible:".cc-settings"},then:[{waitForThenClick:".cc-settings"},{waitForVisible:".cc-settings-view"},{click:".cc-settings-view input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-settings-view .cc-btn-accept-selected"}],else:[{click:".cc-dismiss"}]}]}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_DETECT"}],detectPopup:[{visible:"#cookie-law-info-bar"}],optIn:[{click:'[data-cli_action="accept_all"]'}],optOut:[{hide:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_0"}],test:[{eval:"EVAL_COOKIE_LAW_INFO_1"}]},{name:"cookie-manager-popup",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,detectCmp:[{exists:"#notice-cookie-block #allow-functional-cookies, #notice-cookie-block #btn-cookie-settings"}],detectPopup:[{visible:"#notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{if:{exists:"#allow-functional-cookies"},then:[{click:"#allow-functional-cookies"}],else:[{waitForThenClick:"#btn-cookie-settings"},{waitForVisible:".modal-body"},{click:'.modal-body input:checked, .switch[data-switch="on"]',all:!0,optional:!0},{click:'[role="dialog"] .modal-footer button'}]}],prehideSelectors:["#btn-cookie-settings"],test:[{eval:"EVAL_COOKIE_MANAGER_POPUP_0"}]},{name:"cookie-notice",prehideSelectors:["#cookie-notice"],cosmetic:!0,detectCmp:[{visible:"#cookie-notice .cookie-notice-container"}],detectPopup:[{visible:"#cookie-notice"}],optIn:[{click:"#cn-accept-cookie"}],optOut:[{hide:"#cookie-notice"}]},{name:"cookie-script",vendorUrl:"https://cookie-script.com/",prehideSelectors:["#cookiescript_injected"],detectCmp:[{exists:"#cookiescript_injected"}],detectPopup:[{visible:"#cookiescript_injected"}],optOut:[{click:"#cookiescript_reject"}],optIn:[{click:"#cookiescript_accept"}]},{name:"cookieacceptbar",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#cookieAcceptBar.cookieAcceptBar"],detectCmp:[{exists:"#cookieAcceptBar.cookieAcceptBar"}],detectPopup:[{visible:"#cookieAcceptBar.cookieAcceptBar"}],optIn:[{waitForThenClick:"#cookieAcceptBarConfirm"}],optOut:[{hide:"#cookieAcceptBar.cookieAcceptBar"}]},{name:"cookiealert",intermediate:!1,prehideSelectors:[],runContext:{frame:!0,main:!0},detectCmp:[{exists:".cookie-alert-extended"}],detectPopup:[{visible:".cookie-alert-extended-modal"}],optIn:[{click:"button[data-controller='cookie-alert/extended/button/accept']"},{eval:"EVAL_COOKIEALERT_0"}],optOut:[{click:"a[data-controller='cookie-alert/extended/detail-link']"},{click:".cookie-alert-configuration-input:checked",all:!0,optional:!0},{click:"button[data-controller='cookie-alert/extended/button/configuration']"},{eval:"EVAL_COOKIEALERT_0"}],test:[{eval:"EVAL_COOKIEALERT_2"}]},{name:"cookieconsent2",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v2.x.x of the library",prehideSelectors:["#cc--main"],detectCmp:[{exists:"#cc--main"}],detectPopup:[{visible:"#cm"},{exists:"#s-all-bn"}],optIn:[{waitForThenClick:"#s-all-bn"}],optOut:[{waitForThenClick:"#s-rall-bn"}],test:[{eval:"EVAL_COOKIECONSENT2_TEST"}]},{name:"cookieconsent3",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v3.x.x of the library",prehideSelectors:["#cc-main"],detectCmp:[{exists:"#cc-main"}],detectPopup:[{visible:"#cc-main .cm-wrapper"}],optIn:[{waitForThenClick:".cm__btn[data-role=all]"}],optOut:[{waitForThenClick:".cm__btn[data-role=necessary]"}],test:[{eval:"EVAL_COOKIECONSENT3_TEST"}]},{name:"cookiefirst.com",prehideSelectors:["#cookiefirst-root,.cookiefirst-root,[aria-labelledby=cookie-preference-panel-title]"],detectCmp:[{exists:"#cookiefirst-root,.cookiefirst-root"}],detectPopup:[{visible:"#cookiefirst-root,.cookiefirst-root"}],optIn:[{click:"button[data-cookiefirst-action=accept]"}],optOut:[{if:{exists:"button[data-cookiefirst-action=adjust]"},then:[{click:"button[data-cookiefirst-action=adjust]"},{waitForVisible:"[data-cookiefirst-widget=modal]",timeout:1e3},{eval:"EVAL_COOKIEFIRST_1"},{wait:1e3},{click:"button[data-cookiefirst-action=save]"}],else:[{click:"button[data-cookiefirst-action=reject]"}]}],test:[{eval:"EVAL_COOKIEFIRST_0"}]},{name:"Cookie Information Banner",prehideSelectors:["#cookie-information-template-wrapper"],detectCmp:[{exists:"#cookie-information-template-wrapper"}],detectPopup:[{visible:"#cookie-information-template-wrapper"}],optIn:[{eval:"EVAL_COOKIEINFORMATION_1"}],optOut:[{hide:"#cookie-information-template-wrapper",comment:"some templates don't hide the banner automatically"},{eval:"EVAL_COOKIEINFORMATION_0"}],test:[{eval:"EVAL_COOKIEINFORMATION_2"}]},{name:"cookieyes",prehideSelectors:[".cky-overlay,.cky-consent-container"],detectCmp:[{exists:".cky-consent-container"}],detectPopup:[{visible:".cky-consent-container"}],optIn:[{waitForThenClick:".cky-consent-container [data-cky-tag=accept-button]"}],optOut:[{if:{exists:".cky-consent-container [data-cky-tag=reject-button]"},then:[{waitForThenClick:".cky-consent-container [data-cky-tag=reject-button]"}],else:[{if:{exists:".cky-consent-container [data-cky-tag=settings-button]"},then:[{click:".cky-consent-container [data-cky-tag=settings-button]"},{waitFor:".cky-modal-open input[type=checkbox]"},{click:".cky-modal-open input[type=checkbox]:checked",all:!0,optional:!0},{waitForThenClick:".cky-modal [data-cky-tag=detail-save-button]"}],else:[{hide:".cky-consent-container,.cky-overlay"}]}]}],test:[{eval:"EVAL_COOKIEYES_0"}]},{name:"corona-in-zahlen.de",prehideSelectors:[".cookiealert"],detectCmp:[{exists:".cookiealert"}],detectPopup:[{visible:".cookiealert"}],optOut:[{click:".configurecookies"},{click:".confirmcookies"}],optIn:[{click:".acceptcookies"}]},{name:"crossfit-com",cosmetic:!0,prehideSelectors:['body #modal > div > div[class^="_wrapper_"]'],detectCmp:[{exists:'body #modal > div > div[class^="_wrapper_"]'}],detectPopup:[{visible:'body #modal > div > div[class^="_wrapper_"]'}],optIn:[{click:'button[aria-label="accept cookie policy"]'}],optOut:[{hide:'body #modal > div > div[class^="_wrapper_"]'}]},{name:"csu-landtag-de",runContext:{urlPattern:"^https://(www|)?\\.csu-landtag\\.de"},prehideSelectors:["#cookie-disclaimer"],detectCmp:[{exists:"#cookie-disclaimer"}],detectPopup:[{visible:"#cookie-disclaimer"}],optIn:[{click:"#cookieall"}],optOut:[{click:"#cookiesel"}]},{name:"dailymotion-us",cosmetic:!0,prehideSelectors:['div[class*="CookiePopup__desktopContainer"]:has(div[class*="CookiePopup"])'],detectCmp:[{exists:'div[class*="CookiePopup__desktopContainer"]'}],detectPopup:[{visible:'div[class*="CookiePopup__desktopContainer"]'}],optIn:[{click:'div[class*="CookiePopup__desktopContainer"] > button > span'}],optOut:[{hide:'div[class*="CookiePopup__desktopContainer"]'}]},{name:"dailymotion.com",runContext:{urlPattern:"^https://(www\\.)?dailymotion\\.com/"},prehideSelectors:['div[class*="Overlay__container"]:has(div[class*="TCF2Popup"])'],detectCmp:[{exists:'div[class*="TCF2Popup"]'}],detectPopup:[{visible:'[class*="TCF2Popup"] a[href^="https://www.dailymotion.com/legal/cookiemanagement"]'}],optIn:[{waitForThenClick:'button[class*="TCF2Popup__button"]:not([class*="TCF2Popup__personalize"])'}],optOut:[{waitForThenClick:'button[class*="TCF2ContinueWithoutAcceptingButton"]'}],test:[{eval:"EVAL_DAILYMOTION_0"}]},{name:"deepl.com",prehideSelectors:[".dl_cookieBanner_container"],detectCmp:[{exists:".dl_cookieBanner_container"}],detectPopup:[{visible:".dl_cookieBanner_container"}],optOut:[{click:".dl_cookieBanner--buttonSelected"}],optIn:[{click:".dl_cookieBanner--buttonAll"}]},{name:"delta.com",runContext:{urlPattern:"^https://www\\.delta\\.com/"},cosmetic:!0,prehideSelectors:["ngc-cookie-banner"],detectCmp:[{exists:"div.cookie-footer-container"}],detectPopup:[{visible:"div.cookie-footer-container"}],optIn:[{click:" button.cookie-close-icon"}],optOut:[{hide:"div.cookie-footer-container"}]},{name:"dmgmedia-us",prehideSelectors:["#mol-ads-cmp-iframe, div.mol-ads-cmp > form > div"],detectCmp:[{exists:"div.mol-ads-cmp > form > div"}],detectPopup:[{waitForVisible:"div.mol-ads-cmp > form > div"}],optIn:[{waitForThenClick:"button.mol-ads-cmp--btn-primary"}],optOut:[{waitForThenClick:"div.mol-ads-ccpa--message > u > a"},{waitForVisible:".mol-ads-cmp--modal-dialog"},{waitForThenClick:"a.mol-ads-cmp-footer-privacy"},{waitForThenClick:"button.mol-ads-cmp--btn-secondary"}]},{name:"dmgmedia",prehideSelectors:['[data-project="mol-fe-cmp"]'],detectCmp:[{exists:'[data-project="mol-fe-cmp"]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"]'}],optIn:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=primary]'}],optOut:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=basic]'},{waitForVisible:'[data-project="mol-fe-cmp"] div[class*="tabContent"]'},{waitForThenClick:'[data-project="mol-fe-cmp"] div[class*="toggle"][class*="enabled"]',all:!0},{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=white]'}]},{name:"dndbeyond",vendorUrl:"https://www.dndbeyond.com/",runContext:{urlPattern:"^https://(www\\.)?dndbeyond\\.com/"},prehideSelectors:["[id^=cookie-consent-banner]"],detectCmp:[{exists:"[id^=cookie-consent-banner]"}],detectPopup:[{visible:"[id^=cookie-consent-banner]"}],optIn:[{waitForThenClick:"#cookie-consent-granted"}],optOut:[{waitForThenClick:"#cookie-consent-denied"}],test:[{eval:"EVAL_DNDBEYOND_TEST"}]},{name:"Drupal",detectCmp:[{exists:"#drupalorg-crosssite-gdpr"}],detectPopup:[{visible:"#drupalorg-crosssite-gdpr"}],optOut:[{click:".no"}],optIn:[{click:".yes"}]},{name:"WP DSGVO Tools",link:"https://wordpress.org/plugins/shapepress-dsgvo/",prehideSelectors:[".sp-dsgvo"],cosmetic:!0,detectCmp:[{exists:".sp-dsgvo.sp-dsgvo-popup-overlay"}],detectPopup:[{visible:".sp-dsgvo.sp-dsgvo-popup-overlay",check:"any"}],optIn:[{click:".sp-dsgvo-privacy-btn-accept-all",all:!0}],optOut:[{hide:".sp-dsgvo.sp-dsgvo-popup-overlay"}],test:[{eval:"EVAL_DSGVO_0"}]},{name:"dunelm.com",prehideSelectors:["div[data-testid=cookie-consent-modal-backdrop]"],detectCmp:[{exists:"div[data-testid=cookie-consent-message-contents]"}],detectPopup:[{visible:"div[data-testid=cookie-consent-message-contents]"}],optIn:[{click:'[data-testid="cookie-consent-allow-all"]'}],optOut:[{click:"button[data-testid=cookie-consent-adjust-settings]"},{click:"button[data-testid=cookie-consent-preferences-save]"}],test:[{eval:"EVAL_DUNELM_0"}]},{name:"ecosia",vendorUrl:"https://www.ecosia.org/",runContext:{urlPattern:"^https://www\\.ecosia\\.org/"},prehideSelectors:[".cookie-wrapper"],detectCmp:[{exists:".cookie-wrapper > .cookie-notice"}],detectPopup:[{visible:".cookie-wrapper > .cookie-notice"}],optIn:[{waitForThenClick:"[data-test-id=cookie-notice-accept]"}],optOut:[{waitForThenClick:"[data-test-id=cookie-notice-reject]"}]},{name:"etsy",prehideSelectors:["#gdpr-single-choice-overlay","#gdpr-privacy-settings"],detectCmp:[{exists:"#gdpr-single-choice-overlay"}],detectPopup:[{visible:"#gdpr-single-choice-overlay"}],optOut:[{click:"button[data-gdpr-open-full-settings]"},{waitForVisible:".gdpr-overlay-body input",timeout:3e3},{wait:1e3},{eval:"EVAL_ETSY_0"},{eval:"EVAL_ETSY_1"}],optIn:[{click:"button[data-gdpr-single-choice-accept]"}]},{name:"eu-cookie-compliance-banner",detectCmp:[{exists:"body.eu-cookie-compliance-popup-open"}],detectPopup:[{exists:"body.eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{if:{visible:".decline-button,.eu-cookie-compliance-save-preferences-button"},then:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button"}]},{hide:".eu-cookie-compliance-banner-info, #sliding-popup"}],test:[{eval:"EVAL_EU_COOKIE_COMPLIANCE_0"}]},{name:"EU Cookie Law",prehideSelectors:[".pea_cook_wrapper,.pea_cook_more_info_popover"],cosmetic:!0,detectCmp:[{exists:".pea_cook_wrapper"}],detectPopup:[{wait:500},{visible:".pea_cook_wrapper"}],optIn:[{click:"#pea_cook_btn"}],optOut:[{hide:".pea_cook_wrapper"}],test:[{eval:"EVAL_EU_COOKIE_LAW_0"}]},{name:"europa-eu",vendorUrl:"https://ec.europa.eu/",runContext:{urlPattern:"^https://[^/]*europa\\.eu/"},prehideSelectors:["#cookie-consent-banner"],detectCmp:[{exists:".cck-container"}],detectPopup:[{visible:".cck-container"}],optIn:[{waitForThenClick:'.cck-actions-button[href="#accept"]'}],optOut:[{waitForThenClick:'.cck-actions-button[href="#refuse"]',hide:".cck-container"}]},{name:"EZoic",prehideSelectors:["#ez-cookie-dialog-wrapper"],detectCmp:[{exists:"#ez-cookie-dialog-wrapper"}],detectPopup:[{visible:"#ez-cookie-dialog-wrapper"}],optIn:[{click:"#ez-accept-all",optional:!0},{eval:"EVAL_EZOIC_0",optional:!0}],optOut:[{wait:500},{click:"#ez-manage-settings"},{waitFor:"#ez-cookie-dialog input[type=checkbox]"},{click:"#ez-cookie-dialog input[type=checkbox]:checked",all:!0},{click:"#ez-save-settings"}],test:[{eval:"EVAL_EZOIC_1"}]},{name:"facebook",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?facebook\\.com/"},prehideSelectors:['div[data-testid="cookie-policy-manage-dialog"]'],detectCmp:[{exists:'div[data-testid="cookie-policy-manage-dialog"]'}],detectPopup:[{visible:'div[data-testid="cookie-policy-manage-dialog"]'}],optIn:[{waitForThenClick:'button[data-cookiebanner="accept_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}],optOut:[{waitForThenClick:'button[data-cookiebanner="accept_only_essential_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}]},{name:"fides",vendorUrl:"https://github.com/ethyca/fides",prehideSelectors:["#fides-overlay"],detectCmp:[{exists:"#fides-overlay #fides-banner"}],detectPopup:[{visible:"#fides-overlay #fides-banner"}],optIn:[{waitForThenClick:'#fides-banner [data-testid="Accept all-btn"]'}],optOut:[{waitForThenClick:'#fides-banner [data-testid="Reject all-btn"]'}]},{name:"funding-choices",prehideSelectors:[".fc-consent-root,.fc-dialog-container,.fc-dialog-overlay,.fc-dialog-content"],detectCmp:[{exists:".fc-consent-root"}],detectPopup:[{exists:".fc-dialog-container"}],optOut:[{click:".fc-cta-do-not-consent,.fc-cta-manage-options"},{click:".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked",all:!0,optional:!0},{click:".fc-confirm-choices",optional:!0}],optIn:[{click:".fc-cta-consent"}]},{name:"geeks-for-geeks",runContext:{urlPattern:"^https://www\\.geeksforgeeks\\.org/"},cosmetic:!0,prehideSelectors:[".cookie-consent"],detectCmp:[{exists:".cookie-consent"}],detectPopup:[{visible:".cookie-consent"}],optIn:[{click:".cookie-consent button.consent-btn"}],optOut:[{hide:".cookie-consent"}]},{name:"generic-cosmetic",cosmetic:!0,prehideSelectors:["#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"],detectCmp:[{exists:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],detectPopup:[{visible:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],optIn:[],optOut:[{hide:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}]},{name:"google-consent-standalone",prehideSelectors:[],detectCmp:[{exists:'a[href^="https://policies.google.com/technologies/cookies"'},{exists:'form[action^="https://consent.google."][action$=".com/save"]'}],detectPopup:[{visible:'a[href^="https://policies.google.com/technologies/cookies"'}],optIn:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=false]) button'}],optOut:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=true]) button'}]},{name:"google.com",prehideSelectors:[".HTjtHe#xe7COe"],detectCmp:[{exists:".HTjtHe#xe7COe"},{exists:'.HTjtHe#xe7COe a[href^="https://policies.google.com/technologies/cookies"]'}],detectPopup:[{visible:".HTjtHe#xe7COe button#W0wltc"}],optIn:[{waitForThenClick:".HTjtHe#xe7COe button#L2AGLb"}],optOut:[{waitForThenClick:".HTjtHe#xe7COe button#W0wltc"}],test:[{eval:"EVAL_GOOGLE_0"}]},{name:"gov.uk",detectCmp:[{exists:"#global-cookie-message"}],detectPopup:[{exists:"#global-cookie-message"}],optIn:[{click:"button[data-accept-cookies=true]"}],optOut:[{click:"button[data-reject-cookies=true],#reject-cookies"},{click:"button[data-hide-cookie-banner=true],#hide-cookie-decision"}]},{name:"hashicorp",vendorUrl:"https://hashicorp.com/",runContext:{urlPattern:"^https://[^.]*\\.hashicorp\\.com/"},prehideSelectors:["[data-testid=consent-banner]"],detectCmp:[{exists:"[data-testid=consent-banner]"}],detectPopup:[{visible:"[data-testid=consent-banner]"}],optIn:[{waitForThenClick:"[data-testid=accept]"}],optOut:[{waitForThenClick:"[data-testid=manage-preferences]"},{waitForThenClick:"[data-testid=consent-mgr-dialog] [data-ga-button=save-preferences]"}]},{name:"healthline-media",prehideSelectors:["#modal-host > div.no-hash > div.window-wrapper"],detectCmp:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],detectPopup:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],optIn:[{click:"#modal-host > div.no-hash > div.window-wrapper > div:last-child button"}],optOut:[{if:{exists:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'},then:[{click:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'}],else:[{waitForVisible:"div#__next"},{click:"#__next div:nth-child(1) > button:first-child"}]}]},{name:"hema",prehideSelectors:[".cookie-modal"],detectCmp:[{visible:".cookie-modal .cookie-accept-btn"}],detectPopup:[{visible:".cookie-modal .cookie-accept-btn"}],optIn:[{waitForThenClick:".cookie-modal .cookie-accept-btn"}],optOut:[{waitForThenClick:".cookie-modal .js-cookie-reject-btn"}],test:[{eval:"EVAL_HEMA_TEST_0"}]},{name:"hetzner.com",runContext:{urlPattern:"^https://www\\.hetzner\\.com/"},prehideSelectors:["#CookieConsent"],detectCmp:[{exists:"#CookieConsent"}],detectPopup:[{visible:"#CookieConsent"}],optIn:[{click:"#CookieConsentGiven"}],optOut:[{click:"#CookieConsentDeclined"}]},{name:"hl.co.uk",prehideSelectors:[".cookieModalContent","#cookie-banner-overlay"],detectCmp:[{exists:"#cookie-banner-overlay"}],detectPopup:[{exists:"#cookie-banner-overlay"}],optIn:[{click:"#acceptCookieButton"}],optOut:[{click:"#manageCookie"},{hide:".cookieSettingsModal"},{waitFor:"#AOCookieToggle"},{click:"#AOCookieToggle[aria-pressed=true]",optional:!0},{waitFor:"#TPCookieToggle"},{click:"#TPCookieToggle[aria-pressed=true]",optional:!0},{click:"#updateCookieButton"}]},{name:"hu-manity",vendorUrl:"https://hu-manity.co/",prehideSelectors:["#hu.hu-wrapper"],detectCmp:[{exists:"#hu.hu-visible"}],detectPopup:[{visible:"#hu.hu-visible"}],optIn:[{waitForThenClick:"[data-hu-action=cookies-notice-consent-choices-3]"},{waitForThenClick:"#hu-cookies-save"}],optOut:[{waitForThenClick:"#hu-cookies-save"}]},{name:"hubspot",detectCmp:[{exists:"#hs-eu-cookie-confirmation"}],detectPopup:[{visible:"#hs-eu-cookie-confirmation"}],optIn:[{click:"#hs-eu-confirmation-button"}],optOut:[{click:"#hs-eu-decline-button"}]},{name:"indeed.com",cosmetic:!0,prehideSelectors:["#CookiePrivacyNotice"],detectCmp:[{exists:"#CookiePrivacyNotice"}],detectPopup:[{visible:"#CookiePrivacyNotice"}],optIn:[{click:"#CookiePrivacyNotice button[data-gnav-element-name=CookiePrivacyNoticeOk]"}],optOut:[{hide:"#CookiePrivacyNotice"}]},{name:"ing.de",runContext:{urlPattern:"^https://www\\.ing\\.de/"},cosmetic:!0,prehideSelectors:['div[slot="backdrop"]'],detectCmp:[{exists:'[data-tag-name="ing-cc-dialog-frame"]'}],detectPopup:[{visible:'[data-tag-name="ing-cc-dialog-frame"]'}],optIn:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="accept"]']}],optOut:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="more"]']}]},{name:"instagram",vendorUrl:"https://instagram.com",runContext:{urlPattern:"^https://www\\.instagram\\.com/"},prehideSelectors:[".x78zum5.xdt5ytf.xg6iff7.x1n2onr6"],detectCmp:[{exists:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],detectPopup:[{visible:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],optIn:[{waitForThenClick:"._a9--._a9_0"}],optOut:[{waitForThenClick:"._a9--._a9_1"},{wait:2e3}]},{name:"ionos.de",prehideSelectors:[".privacy-consent--backdrop",".privacy-consent--modal"],detectCmp:[{exists:".privacy-consent--modal"}],detectPopup:[{visible:".privacy-consent--modal"}],optIn:[{click:"#selectAll"}],optOut:[{click:".footer-config-link"},{click:"#confirmSelection"}]},{name:"itopvpn.com",cosmetic:!0,prehideSelectors:[".pop-cookie"],detectCmp:[{exists:".pop-cookie"}],detectPopup:[{exists:".pop-cookie"}],optIn:[{click:"#_pcookie"}],optOut:[{hide:".pop-cookie"}]},{name:"iubenda",prehideSelectors:["#iubenda-cs-banner"],detectCmp:[{exists:"#iubenda-cs-banner"}],detectPopup:[{visible:".iubenda-cs-accept-btn"}],optIn:[{click:".iubenda-cs-accept-btn"}],optOut:[{click:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{click:"#iubFooterBtn"}],test:[{eval:"EVAL_IUBENDA_1"}]},{name:"iWink",prehideSelectors:["body.cookies-request #cookie-bar"],detectCmp:[{exists:"body.cookies-request #cookie-bar"}],detectPopup:[{visible:"body.cookies-request #cookie-bar"}],optIn:[{waitForThenClick:"body.cookies-request #cookie-bar .allow-cookies"}],optOut:[{waitForThenClick:"body.cookies-request #cookie-bar .disallow-cookies"}],test:[{eval:"EVAL_IWINK_TEST"}]},{name:"jdsports",vendorUrl:"https://www.jdsports.co.uk/",runContext:{urlPattern:"^https://(www|m)\\.jdsports\\."},prehideSelectors:[".miniConsent,#PrivacyPolicyBanner"],detectCmp:[{exists:".miniConsent,#PrivacyPolicyBanner"}],detectPopup:[{visible:".miniConsent,#PrivacyPolicyBanner"}],optIn:[{waitForThenClick:".miniConsent .accept-all-cookies"}],optOut:[{if:{exists:"#PrivacyPolicyBanner"},then:[{hide:"#PrivacyPolicyBanner"}],else:[{waitForThenClick:"#cookie-settings"},{waitForThenClick:"#reject-all-cookies"}]}]},{name:"johnlewis.com",prehideSelectors:["div[class^=pecr-cookie-banner-]"],detectCmp:[{exists:"div[class^=pecr-cookie-banner-]"}],detectPopup:[{exists:"div[class^=pecr-cookie-banner-]"}],optOut:[{click:"button[data-test^=manage-cookies]"},{wait:"500"},{click:"label[data-test^=toggle][class*=checked]:not([class*=disabled])",all:!0,optional:!0},{click:"button[data-test=save-preferences]"}],optIn:[{click:"button[data-test=allow-all]"}]},{name:"jquery.cookieBar",vendorUrl:"https://github.com/kovarp/jquery.cookieBar",prehideSelectors:[".cookie-bar"],cosmetic:!0,detectCmp:[{exists:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons"}],detectPopup:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"any"}],optIn:[{click:".cookie-bar .cookie-bar__btn"}],optOut:[{hide:".cookie-bar"}],test:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"none"},{eval:"EVAL_JQUERY_COOKIEBAR_0"}]},{name:"justwatch.com",prehideSelectors:[".consent-banner"],detectCmp:[{exists:".consent-banner .consent-banner__actions"}],detectPopup:[{visible:".consent-banner .consent-banner__actions"}],optIn:[{click:".consent-banner__actions button.basic-button.primary"}],optOut:[{click:".consent-banner__actions button.basic-button.secondary"},{waitForThenClick:".consent-modal__footer button.basic-button.secondary"},{waitForThenClick:".consent-modal ion-content > div > a:nth-child(9)"},{click:"label.consent-switch input[type=checkbox]:checked",all:!0,optional:!0},{waitForVisible:".consent-modal__footer button.basic-button.primary"},{click:".consent-modal__footer button.basic-button.primary"}]},{name:"ketch",vendorUrl:"https://www.ketch.com",runContext:{frame:!1,main:!0},intermediate:!1,prehideSelectors:["#lanyard_root div[role='dialog']"],detectCmp:[{exists:"#lanyard_root div[role='dialog']"}],detectPopup:[{visible:"#lanyard_root div[role='dialog']"}],optIn:[{if:{exists:"#lanyard_root button[class='confirmButton']"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"},{click:"#lanyard_root button[class='confirmButton']"}],else:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"}]}],optOut:[{if:{exists:"#lanyard_root [aria-describedby=banner-description]"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > button[class*=secondaryButton]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1)"}]}]},{name:"kleinanzeigen-de",runContext:{urlPattern:"^https?://(www\\.)?kleinanzeigen\\.de"},prehideSelectors:["#gdpr-banner-container"],detectCmp:[{any:[{exists:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{exists:"#ConsentManagementPage"}]}],detectPopup:[{any:[{visible:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{visible:"#ConsentManagementPage"}]}],optIn:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-accept]"}],else:[{click:"#ConsentManagementPage .Button-primary"}]}],optOut:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"}],else:[{click:"#ConsentManagementPage .Button-secondary"}]}]},{name:"lightbox",prehideSelectors:[".darken-layer.open,.lightbox.lightbox--cookie-consent"],detectCmp:[{exists:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],detectPopup:[{visible:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],optOut:[{click:".cookie-consent__footer > button[type='submit']:not([data-button='selectAll'])"}],optIn:[{click:".cookie-consent__footer > button[type='submit'][data-button='selectAll']"}]},{name:"lineagrafica",vendorUrl:"https://addons.prestashop.com/en/legal/8734-eu-cookie-law-gdpr-banner-blocker.html",cosmetic:!0,prehideSelectors:["#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"],detectCmp:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],detectPopup:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],optIn:[{waitForThenClick:"#lgcookieslaw_accept"}],optOut:[{hide:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}]},{name:"linkedin.com",prehideSelectors:[".artdeco-global-alert[type=COOKIE_CONSENT]"],detectCmp:[{exists:".artdeco-global-alert[type=COOKIE_CONSENT]"}],detectPopup:[{visible:".artdeco-global-alert[type=COOKIE_CONSENT]"}],optIn:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"}],optOut:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"}],test:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT]",check:"none"}]},{name:"livejasmin",vendorUrl:"https://www.livejasmin.com/",runContext:{urlPattern:"^https://(m|www)\\.livejasmin\\.com/"},prehideSelectors:["#consent_modal"],detectCmp:[{exists:"#consent_modal"}],detectPopup:[{visible:"#consent_modal"}],optIn:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:first-of-type"}],optOut:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:nth-of-type(2)"},{waitForVisible:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent]"},{click:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] input[data-testid=PrivacyPreferenceCenterWithConsentCookieSwitch]:checked",optional:!0,all:!0},{waitForThenClick:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] button[data-testid=ButtonStyledButton]:last-child"}]},{name:"macpaw.com",cosmetic:!0,prehideSelectors:['div[data-banner="cookies"]'],detectCmp:[{exists:'div[data-banner="cookies"]'}],detectPopup:[{exists:'div[data-banner="cookies"]'}],optIn:[{click:'button[data-banner-close="cookies"]'}],optOut:[{hide:'div[data-banner="cookies"]'}]},{name:"marksandspencer.com",cosmetic:!0,detectCmp:[{exists:".navigation-cookiebbanner"}],detectPopup:[{visible:".navigation-cookiebbanner"}],optOut:[{hide:".navigation-cookiebbanner"}],optIn:[{click:".navigation-cookiebbanner__submit"}]},{name:"mediamarkt.de",prehideSelectors:["div[aria-labelledby=pwa-consent-layer-title]","div[class^=StyledConsentLayerWrapper-]"],detectCmp:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],detectPopup:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],optOut:[{click:"button[data-test^=pwa-consent-layer-deny-all]"}],optIn:[{click:"button[data-test^=pwa-consent-layer-accept-all"}]},{name:"Mediavine",prehideSelectors:['[data-name="mediavine-gdpr-cmp"]'],detectCmp:[{exists:'[data-name="mediavine-gdpr-cmp"]'}],detectPopup:[{wait:500},{visible:'[data-name="mediavine-gdpr-cmp"]'}],optIn:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [format="primary"]'}],optOut:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [data-view="manageSettings"]'},{waitFor:'[data-name="mediavine-gdpr-cmp"] input[type=checkbox]'},{eval:"EVAL_MEDIAVINE_0",optional:!0},{click:'[data-name="mediavine-gdpr-cmp"] [format="secondary"]'}]},{name:"microsoft.com",prehideSelectors:["#wcpConsentBannerCtrl"],detectCmp:[{exists:"#wcpConsentBannerCtrl"}],detectPopup:[{exists:"#wcpConsentBannerCtrl"}],optOut:[{eval:"EVAL_MICROSOFT_0"}],optIn:[{eval:"EVAL_MICROSOFT_1"}],test:[{eval:"EVAL_MICROSOFT_2"}]},{name:"midway-usa",runContext:{urlPattern:"^https://www\\.midwayusa\\.com/"},cosmetic:!0,prehideSelectors:["#cookie-container"],detectCmp:[{exists:['div[aria-label="Cookie Policy Banner"]']}],detectPopup:[{visible:"#cookie-container"}],optIn:[{click:"button#cookie-btn"}],optOut:[{hide:'div[aria-label="Cookie Policy Banner"]'}]},{name:"moneysavingexpert.com",detectCmp:[{exists:"dialog[data-testid=accept-our-cookies-dialog]"}],detectPopup:[{visible:"dialog[data-testid=accept-our-cookies-dialog]"}],optIn:[{click:"#banner-accept"}],optOut:[{click:"#banner-manage"},{click:"#pc-confirm"}]},{name:"monzo.com",prehideSelectors:[".cookie-alert, cookie-alert__content"],detectCmp:[{exists:'div.cookie-alert[role="dialog"]'},{exists:'a[href*="monzo"]'}],detectPopup:[{visible:".cookie-alert__content"}],optIn:[{click:".js-accept-cookie-policy"}],optOut:[{click:".js-decline-cookie-policy"}]},{name:"Moove",prehideSelectors:["#moove_gdpr_cookie_info_bar"],detectCmp:[{exists:"#moove_gdpr_cookie_info_bar"}],detectPopup:[{visible:"#moove_gdpr_cookie_info_bar"}],optIn:[{waitForThenClick:".moove-gdpr-infobar-allow-all"}],optOut:[{if:{exists:"#moove_gdpr_cookie_info_bar .change-settings-button"},then:[{click:"#moove_gdpr_cookie_info_bar .change-settings-button"},{waitForVisible:"#moove_gdpr_cookie_modal"},{eval:"EVAL_MOOVE_0"},{click:".moove-gdpr-modal-save-settings"}],else:[{hide:"#moove_gdpr_cookie_info_bar"}]}],test:[{visible:"#moove_gdpr_cookie_info_bar",check:"none"}]},{name:"national-lottery.co.uk",detectCmp:[{exists:".cuk_cookie_consent"}],detectPopup:[{visible:".cuk_cookie_consent",check:"any"}],optOut:[{click:".cuk_cookie_consent_manage_pref"},{click:".cuk_cookie_consent_save_pref"},{click:".cuk_cookie_consent_close"}],optIn:[{click:".cuk_cookie_consent_accept_all"}]},{name:"nba.com",runContext:{urlPattern:"^https://(www\\.)?nba.com/"},cosmetic:!0,prehideSelectors:["#onetrust-banner-sdk"],detectCmp:[{exists:"#onetrust-banner-sdk"}],detectPopup:[{visible:"#onetrust-banner-sdk"}],optIn:[{click:"#onetrust-accept-btn-handler"}],optOut:[{hide:"#onetrust-banner-sdk"}]},{name:"netflix.de",detectCmp:[{exists:"#cookie-disclosure"}],detectPopup:[{visible:".cookie-disclosure-message",check:"any"}],optIn:[{click:".btn-accept"}],optOut:[{hide:"#cookie-disclosure"},{click:".btn-reject"}]},{name:"nhs.uk",prehideSelectors:["#nhsuk-cookie-banner"],detectCmp:[{exists:"#nhsuk-cookie-banner"}],detectPopup:[{exists:"#nhsuk-cookie-banner"}],optOut:[{click:"#nhsuk-cookie-banner__link_accept"}],optIn:[{click:"#nhsuk-cookie-banner__link_accept_analytics"}]},{name:"notice-cookie",prehideSelectors:[".button--notice"],cosmetic:!0,detectCmp:[{exists:".notice--cookie"}],detectPopup:[{visible:".notice--cookie"}],optIn:[{click:".button--notice"}],optOut:[{hide:".notice--cookie"}]},{name:"nrk.no",cosmetic:!0,prehideSelectors:[".nrk-masthead__info-banner--cookie"],detectCmp:[{exists:".nrk-masthead__info-banner--cookie"}],detectPopup:[{exists:".nrk-masthead__info-banner--cookie"}],optIn:[{click:"div.nrk-masthead__info-banner--cookie button > span:has(+ svg.nrk-close)"}],optOut:[{hide:".nrk-masthead__info-banner--cookie"}]},{name:"obi.de",prehideSelectors:[".disc-cp--active"],detectCmp:[{exists:".disc-cp-modal__modal"}],detectPopup:[{visible:".disc-cp-modal__modal"}],optIn:[{click:".js-disc-cp-accept-all"}],optOut:[{click:".js-disc-cp-deny-all"}]},{name:"om",vendorUrl:"https://olli-machts.de/en/extension/cookie-manager",prehideSelectors:[".tx-om-cookie-consent"],detectCmp:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],detectPopup:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],optIn:[{waitForThenClick:"[data-omcookie-panel-save=all]"}],optOut:[{if:{exists:"[data-omcookie-panel-save=min]"},then:[{waitForThenClick:"[data-omcookie-panel-save=min]"}],else:[{click:"input[data-omcookie-panel-grp]:checked:not(:disabled)",all:!0,optional:!0},{waitForThenClick:"[data-omcookie-panel-save=save]"}]}]},{name:"onlyFans.com",prehideSelectors:["div.b-cookies-informer"],detectCmp:[{exists:"div.b-cookies-informer"}],detectPopup:[{exists:"div.b-cookies-informer"}],optIn:[{click:"div.b-cookies-informer__nav > button:nth-child(2)"}],optOut:[{click:"div.b-cookies-informer__nav > button:nth-child(1)"},{click:'div.b-cookies-informer__switchers > div:nth-child(2) > div[at-attr="checkbox"] > span.b-input-radio__container > input[type="checkbox"]'},{click:"div.b-cookies-informer__nav > button"}]},{name:"openli",vendorUrl:"https://openli.com",prehideSelectors:[".legalmonster-cleanslate"],detectCmp:[{exists:".legalmonster-cleanslate"}],detectPopup:[{visible:".legalmonster-cleanslate #lm-cookie-wall-container",check:"any"}],optIn:[{waitForThenClick:"#lm-accept-all"}],optOut:[{waitForThenClick:"#lm-accept-necessary"}]},{name:"opera.com",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:"#cookie-consent .manage-cookies__btn"}],detectPopup:[{visible:"#cookie-consent .cookie-basic-consent__btn"}],optIn:[{waitForThenClick:"#cookie-consent .cookie-basic-consent__btn"}],optOut:[{waitForThenClick:"#cookie-consent .manage-cookies__btn"},{waitForThenClick:"#cookie-consent .active.marketing_option_switch.cookie-consent__switch",all:!0},{waitForThenClick:"#cookie-consent .cookie-selection__btn"}],test:[{eval:"EVAL_OPERA_0"}]},{name:"osano",prehideSelectors:[".osano-cm-window,.osano-cm-dialog"],detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{waitForThenClick:".osano-cm-denyAll"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"ourworldindata",vendorUrl:"https://ourworldindata.org/",runContext:{urlPattern:"^https://ourworldindata\\.org/"},prehideSelectors:[".cookie-manager"],detectCmp:[{exists:".cookie-manager"}],detectPopup:[{visible:".cookie-manager .cookie-notice.open"}],optIn:[{waitForThenClick:".cookie-notice [data-test=accept]"}],optOut:[{waitForThenClick:".cookie-notice [data-test=reject]"}]},{name:"pabcogypsum",vendorUrl:"https://unknown",prehideSelectors:[".js-cookie-notice:has(#cookie_settings-form)"],detectCmp:[{exists:".js-cookie-notice #cookie_settings-form"}],detectPopup:[{visible:".js-cookie-notice #cookie_settings-form"}],optIn:[{waitForThenClick:".js-cookie-notice button[value=allow]"}],optOut:[{waitForThenClick:".js-cookie-notice button[value=disable]"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-sheet-content #formContent"},{click:"#formContent .cookiepref-11m2iee-checkbox_base input:checked",all:!0,optional:!0},{click:".confirmCookie #submitCookiesBtn"}]}]},{name:"paypal.com",prehideSelectors:["#gdprCookieBanner"],detectCmp:[{exists:"#gdprCookieBanner"}],detectPopup:[{visible:"#gdprCookieContent_wrapper"}],optIn:[{click:"#acceptAllButton"}],optOut:[{wait:200},{click:".gdprCookieBanner_decline-button"}],test:[{wait:500},{eval:"EVAL_PAYPAL_0"}]},{name:"pinetools.com",cosmetic:!0,prehideSelectors:["#aviso_cookies"],detectCmp:[{exists:"#aviso_cookies"}],detectPopup:[{exists:".lang_en #aviso_cookies"}],optIn:[{click:"#aviso_cookies .a_boton_cerrar"}],optOut:[{hide:"#aviso_cookies"}]},{name:"pmc",cosmetic:!0,prehideSelectors:["#pmc-pp-tou--notice"],detectCmp:[{exists:"#pmc-pp-tou--notice"}],detectPopup:[{visible:"#pmc-pp-tou--notice"}],optIn:[{click:"span.pmc-pp-tou--notice-close-btn"}],optOut:[{hide:"#pmc-pp-tou--notice"}]},{name:"pornhub.com",runContext:{urlPattern:"^https://(www\\.)?pornhub\\.com/"},cosmetic:!0,prehideSelectors:[".cookiesBanner"],detectCmp:[{exists:".cookiesBanner"}],detectPopup:[{visible:".cookiesBanner"}],optIn:[{click:".cookiesBanner .okButton"}],optOut:[{hide:".cookiesBanner"}]},{name:"pornpics.com",cosmetic:!0,prehideSelectors:["#cookie-contract"],detectCmp:[{exists:"#cookie-contract"}],detectPopup:[{visible:"#cookie-contract"}],optIn:[{click:"#cookie-contract .icon-cross"}],optOut:[{hide:"#cookie-contract"}]},{name:"PrimeBox CookieBar",prehideSelectors:["#cookie-bar"],detectCmp:[{exists:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy"}],detectPopup:[{visible:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy",check:"any"}],optIn:[{waitForThenClick:"#cookie-bar .cb-enable"}],optOut:[{click:"#cookie-bar .cb-disable",optional:!0},{hide:"#cookie-bar"}],test:[{eval:"EVAL_PRIMEBOX_0"}]},{name:"privacymanager.io",prehideSelectors:["#gdpr-consent-tool-wrapper",'iframe[src^="https://cmp-consent-tool.privacymanager.io"]'],runContext:{urlPattern:"^https://cmp-consent-tool\\.privacymanager\\.io/",main:!1,frame:!0},detectCmp:[{exists:"button#save"}],detectPopup:[{visible:"button#save"}],optIn:[{click:"button#save"}],optOut:[{if:{exists:"#denyAll"},then:[{click:"#denyAll"},{waitForThenClick:".okButton"}],else:[{waitForThenClick:"#manageSettings"},{waitFor:".purposes-overview-list"},{waitFor:"button#saveAndExit"},{click:"span[role=checkbox][aria-checked=true]",all:!0,optional:!0},{click:"button#saveAndExit"}]}]},{name:"productz.com",vendorUrl:"https://productz.com/",runContext:{urlPattern:"^https://productz\\.com/"},prehideSelectors:[],detectCmp:[{exists:".c-modal.is-active"}],detectPopup:[{visible:".c-modal.is-active"}],optIn:[{waitForThenClick:".c-modal.is-active .is-accept"}],optOut:[{waitForThenClick:".c-modal.is-active .is-dismiss"}]},{name:"pubtech",prehideSelectors:["#pubtech-cmp"],detectCmp:[{exists:"#pubtech-cmp"}],detectPopup:[{visible:"#pubtech-cmp #pt-actions"}],optIn:[{if:{exists:"#pt-accept-all"},then:[{click:"#pubtech-cmp #pt-actions #pt-accept-all"}],else:[{click:"#pubtech-cmp #pt-actions button:nth-of-type(2)"}]}],optOut:[{click:"#pubtech-cmp #pt-close"}],test:[{eval:"EVAL_PUBTECH_0"}]},{name:"quantcast",prehideSelectors:["#qc-cmp2-main,#qc-cmp2-container"],detectCmp:[{exists:"#qc-cmp2-container"}],detectPopup:[{visible:"#qc-cmp2-ui"}],optOut:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]'},{waitFor:"#qc-cmp2-ui"},{click:'.qc-cmp2-toggle-switch > button[aria-checked="true"]',all:!0,optional:!0},{click:'.qc-cmp2-main button[aria-label="REJECT ALL"]',optional:!0},{waitForThenClick:'.qc-cmp2-main button[aria-label="SAVE & EXIT"],.qc-cmp2-buttons-desktop > button[mode="primary"]',timeout:5e3}],optIn:[{click:'.qc-cmp2-summary-buttons > button[mode="primary"]'}]},{name:"reddit.com",runContext:{urlPattern:"^https://www\\.reddit\\.com/"},prehideSelectors:["[bundlename=reddit_cookie_banner]"],detectCmp:[{exists:"reddit-cookie-banner"}],detectPopup:[{visible:"reddit-cookie-banner"}],optIn:[{waitForThenClick:["reddit-cookie-banner","#accept-all-cookies-button > button"]}],optOut:[{waitForThenClick:["reddit-cookie-banner","#reject-nonessential-cookies-button > button"]}],test:[{eval:"EVAL_REDDIT_0"}]},{name:"rog-forum.asus.com",runContext:{urlPattern:"^https://rog-forum\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{click:'div.cookie-btn-box > div[aria-label="Accept"]'}],optOut:[{click:'div.cookie-btn-box > div[aria-label="Reject"]'},{waitForThenClick:'.cookie-policy-lightbox-bottom > div[aria-label="Save Settings"]'}]},{name:"roofingmegastore.co.uk",runContext:{urlPattern:"^https://(www\\.)?roofingmegastore\\.co\\.uk"},prehideSelectors:["#m-cookienotice"],detectCmp:[{exists:"#m-cookienotice"}],detectPopup:[{visible:"#m-cookienotice"}],optIn:[{click:"#accept-cookies"}],optOut:[{click:"#manage-cookies"},{waitForThenClick:"#accept-selected"}]},{name:"samsung.com",runContext:{urlPattern:"^https://www\\.samsung\\.com/"},cosmetic:!0,prehideSelectors:["div.cookie-bar"],detectCmp:[{exists:"div.cookie-bar"}],detectPopup:[{visible:"div.cookie-bar"}],optIn:[{click:"div.cookie-bar__manage > a"}],optOut:[{hide:"div.cookie-bar"}]},{name:"setapp.com",vendorUrl:"https://setapp.com/",cosmetic:!0,runContext:{urlPattern:"^https://setapp\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.js-cookie-banner"}],detectPopup:[{visible:".cookie-banner.js-cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner.js-cookie-banner button"}],optOut:[{hide:".cookie-banner.js-cookie-banner"}]},{name:"sibbo",prehideSelectors:["sibbo-cmp-layout"],detectCmp:[{exists:"sibbo-cmp-layout"}],detectPopup:[{visible:"sibbo-cmp-layout"}],optIn:[{click:"sibbo-cmp-layout [data-accept-all]"}],optOut:[{click:'.sibbo-panel__aside__buttons a[data-nav="purposes"]'},{click:'.sibbo-panel__main__header__actions a[data-focusable="reject-all"]'},{if:{exists:"[data-view=purposes] .sibbo-panel__main__footer__actions [data-save-and-exit]"},then:[],else:[{waitFor:'.sibbo-panel__main__footer__actions a[data-focusable="next"]:not(.sibbo-cmp-button--disabled)'},{click:'.sibbo-panel__main__footer__actions a[data-focusable="next"]'},{click:'.sibbo-panel__main div[data-view="purposesLegInt"] a[data-focusable="reject-all"]'}]},{waitFor:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"},{click:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"}],test:[{eval:"EVAL_SIBBO_0"}]},{name:"similarweb.com",cosmetic:!0,prehideSelectors:[".app-cookies-notification"],detectCmp:[{exists:".app-cookies-notification"}],detectPopup:[{exists:".app-layout .app-cookies-notification"}],optIn:[{click:"button.app-cookies-notification__dismiss"}],optOut:[{hide:".app-layout .app-cookies-notification"}]},{name:"Sirdata",cosmetic:!1,prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:["#sd-cmp","xpath///span[contains(., 'Do not accept') or contains(., 'Acceptera inte') or contains(., 'No aceptar') or contains(., 'Ikke acceptere') or contains(., 'Nicht akzeptieren') or contains(., 'Не приемам') or contains(., 'Να μην γίνει αποδοχή') or contains(., 'Niet accepteren') or contains(., 'Nepřijímat') or contains(., 'Nie akceptuj') or contains(., 'Nu acceptați') or contains(., 'Não aceitar') or contains(., 'Continuer sans accepter') or contains(., 'Non accettare') or contains(., 'Nem fogad el')]"]}]},{name:"snigel",detectCmp:[{exists:".snigel-cmp-framework"}],detectPopup:[{visible:".snigel-cmp-framework"}],optOut:[{click:"#sn-b-custom"},{click:"#sn-b-save"}],test:[{eval:"EVAL_SNIGEL_0"}],optIn:[{click:".snigel-cmp-framework #accept-choices"}]},{name:"steampowered.com",detectCmp:[{exists:".cookiepreferences_popup"},{visible:".cookiepreferences_popup"}],detectPopup:[{visible:".cookiepreferences_popup"}],optOut:[{click:"#rejectAllButton"}],optIn:[{click:"#acceptAllButton"}],test:[{wait:1e3},{eval:"EVAL_STEAMPOWERED_0"}]},{name:"strato.de",prehideSelectors:["#cookie_initial_modal",".modal-backdrop"],runContext:{urlPattern:"^https://www\\.strato\\.de/"},detectCmp:[{exists:"#cookie_initial_modal"}],detectPopup:[{visible:"#cookie_initial_modal"}],optIn:[{click:"button#jss_consent_all_initial_modal"}],optOut:[{click:"button#jss_open_settings_modal"},{click:"button#jss_consent_checked"}]},{name:"svt.se",vendorUrl:"https://www.svt.se/",runContext:{urlPattern:"^https://www\\.svt\\.se/"},prehideSelectors:["[class*=CookieConsent__root___]"],detectCmp:[{exists:"[class*=CookieConsent__root___]"}],detectPopup:[{visible:"[class*=CookieConsent__modal___]"}],optIn:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=primary]"}],optOut:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=secondary]:nth-child(2)"}],test:[{eval:"EVAL_SVT_TEST"}]},{name:"takealot.com",cosmetic:!0,prehideSelectors:['div[class^="cookies-banner-module_"]'],detectCmp:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],detectPopup:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],optIn:[{click:'button[class*="cookies-banner-module_dismiss-button_"]'}],optOut:[{hide:'div[class^="cookies-banner-module_"]'},{if:{exists:'div[class^="cookies-banner-module_small-cookie-banner_"]'},then:[{eval:"EVAL_TAKEALOT_0"}],else:[]}]},{name:"tarteaucitron.js",prehideSelectors:["#tarteaucitronRoot"],detectCmp:[{exists:"#tarteaucitronRoot"}],detectPopup:[{visible:"#tarteaucitronRoot #tarteaucitronAlertSmall,#tarteaucitronRoot #tarteaucitronAlertBig",check:"any"}],optIn:[{eval:"EVAL_TARTEAUCITRON_1"}],optOut:[{eval:"EVAL_TARTEAUCITRON_0"}],test:[{eval:"EVAL_TARTEAUCITRON_2",comment:"sometimes there are required categories, so we check that at least something is false"}]},{name:"taunton",vendorUrl:"https://www.taunton.com/",prehideSelectors:["#taunton-user-consent__overlay"],detectCmp:[{exists:"#taunton-user-consent__overlay"}],detectPopup:[{exists:"#taunton-user-consent__overlay:not([aria-hidden=true])"}],optIn:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:not(:checked)"},{click:"#taunton-user-consent__toolbar button[type=submit]"}],optOut:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:checked",optional:!0,all:!0},{click:"#taunton-user-consent__toolbar button[type=submit]"}],test:[{eval:"EVAL_TAUNTON_TEST"}]},{name:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal"},{waitForThenClick:"#cm-acceptNone,.js-accept-essential-cookies",timeout:1e3,optional:!0}],optIn:[{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{eval:"EVAL_TEALIUM_2"}],test:[{eval:"EVAL_TEALIUM_3"},{eval:"EVAL_TEALIUM_DONOTSELL_CHECK"},{visible:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs",check:"none"}]},{name:"temu",vendorUrl:"https://temu.com",runContext:{urlPattern:"^https://[^/]*temu\\.com/"},prehideSelectors:["._2d-8vq-W,._1UdBUwni"],detectCmp:[{exists:"._3YCsmIaS"}],detectPopup:[{visible:"._3YCsmIaS"}],optIn:[{waitForThenClick:"._3fKiu5wx._3zN5SumS._3tAK973O.IYOfhWEs.VGNGF1pA"}],optOut:[{waitForThenClick:"._3fKiu5wx._1_XToJBF._3tAK973O.IYOfhWEs.VGNGF1pA"}]},{name:"Termly",prehideSelectors:["#termly-code-snippet-support"],detectCmp:[{exists:"#termly-code-snippet-support"}],detectPopup:[{visible:"#termly-code-snippet-support div"}],optIn:[{waitForThenClick:'[data-tid="banner-accept"]'}],optOut:[{if:{exists:'[data-tid="banner-decline"]'},then:[{click:'[data-tid="banner-decline"]'}],else:[{click:".t-preference-button"},{wait:500},{if:{exists:".t-declineAllButton"},then:[{click:".t-declineAllButton"}],else:[{waitForThenClick:".t-preference-modal input[type=checkbox][checked]:not([disabled])",all:!0},{waitForThenClick:".t-saveButton"}]}]}]},{name:"termsfeed",vendorUrl:"https://termsfeed.com",comment:"v4.x.x",prehideSelectors:[".termsfeed-com---nb"],detectCmp:[{exists:".termsfeed-com---nb"}],detectPopup:[{visible:".termsfeed-com---nb"}],optIn:[{waitForThenClick:".cc-nb-okagree"}],optOut:[{waitForThenClick:".cc-nb-reject"}]},{name:"termsfeed3",vendorUrl:"https://termsfeed.com",comment:"v3.x.x",cosmetic:!0,prehideSelectors:[".cc_dialog.cc_css_reboot"],detectCmp:[{exists:".cc_dialog.cc_css_reboot"}],detectPopup:[{visible:".cc_dialog.cc_css_reboot"}],optIn:[{waitForThenClick:".cc_dialog.cc_css_reboot .cc_b_ok"}],optOut:[{hide:".cc_dialog.cc_css_reboot"}]},{name:"Test page cosmetic CMP",cosmetic:!0,prehideSelectors:["#privacy-test-page-cmp-test-prehide"],detectCmp:[{exists:"#privacy-test-page-cmp-test-banner"}],detectPopup:[{visible:"#privacy-test-page-cmp-test-banner"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{hide:"#privacy-test-page-cmp-test-banner"}],test:[{wait:500},{eval:"EVAL_TESTCMP_COSMETIC_0"}]},{name:"Test page CMP",prehideSelectors:["#reject-all"],detectCmp:[{exists:"#privacy-test-page-cmp-test"}],detectPopup:[{visible:"#privacy-test-page-cmp-test"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{waitFor:"#reject-all"},{click:"#reject-all"}],test:[{eval:"EVAL_TESTCMP_0"}]},{name:"thalia.de",prehideSelectors:[".consent-banner-box"],detectCmp:[{exists:"consent-banner[component=consent-banner]"}],detectPopup:[{visible:".consent-banner-box"}],optIn:[{click:".button-zustimmen"}],optOut:[{click:"button[data-consent=disagree]"}]},{name:"thefreedictionary.com",prehideSelectors:["#cmpBanner"],detectCmp:[{exists:"#cmpBanner"}],detectPopup:[{visible:"#cmpBanner"}],optIn:[{eval:"EVAL_THEFREEDICTIONARY_1"}],optOut:[{eval:"EVAL_THEFREEDICTIONARY_0"}]},{name:"theverge",runContext:{frame:!1,main:!0,urlPattern:"^https://(www)?\\.theverge\\.com"},intermediate:!1,prehideSelectors:[".duet--cta--cookie-banner"],detectCmp:[{exists:".duet--cta--cookie-banner"}],detectPopup:[{visible:".duet--cta--cookie-banner"}],optIn:[{click:".duet--cta--cookie-banner button.tracking-12",all:!1}],optOut:[{click:".duet--cta--cookie-banner button.tracking-12 > span"}],test:[{eval:"EVAL_THEVERGE_0"}]},{name:"tidbits-com",cosmetic:!0,prehideSelectors:["#eu_cookie_law_widget-2"],detectCmp:[{exists:"#eu_cookie_law_widget-2"}],detectPopup:[{visible:"#eu_cookie_law_widget-2"}],optIn:[{click:"#eu-cookie-law form > input.accept"}],optOut:[{hide:"#eu_cookie_law_widget-2"}]},{name:"tractor-supply",runContext:{urlPattern:"^https://www\\.tractorsupply\\.com/"},cosmetic:!0,prehideSelectors:[".tsc-cookie-banner"],detectCmp:[{exists:".tsc-cookie-banner"}],detectPopup:[{visible:".tsc-cookie-banner"}],optIn:[{click:"#cookie-banner-cancel"}],optOut:[{hide:".tsc-cookie-banner"}]},{name:"trader-joes-com",cosmetic:!0,prehideSelectors:['div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'],detectCmp:[{exists:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],detectPopup:[{visible:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],optIn:[{click:'div[class^="CookiesAlert_cookiesAlert__container__"] button'}],optOut:[{hide:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}]},{name:"transcend",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#transcend-consent-manager"],detectCmp:[{exists:"#transcend-consent-manager"}],detectPopup:[{visible:"#transcend-consent-manager"}],optIn:[{waitForThenClick:["#transcend-consent-manager","#consentManagerMainDialog .inner-container button"]}],optOut:[{hide:"#transcend-consent-manager"}]},{name:"transip-nl",runContext:{urlPattern:"^https://www\\.transip\\.nl/"},prehideSelectors:["#consent-modal"],detectCmp:[{any:[{exists:"#consent-modal"},{exists:"#privacy-settings-content"}]}],detectPopup:[{any:[{visible:"#consent-modal"},{visible:"#privacy-settings-content"}]}],optIn:[{click:'button[type="submit"]'}],optOut:[{if:{exists:"#privacy-settings-content"},then:[{click:'button[type="submit"]'}],else:[{click:"div.one-modal__action-footer-column--secondary > a"}]}]},{name:"tropicfeel-com",prehideSelectors:["#shopify-section-cookies-controller"],detectCmp:[{exists:"#shopify-section-cookies-controller"}],detectPopup:[{visible:"#shopify-section-cookies-controller #cookies-controller-main-pane",check:"any"}],optIn:[{waitForThenClick:"#cookies-controller-main-pane form[data-form-allow-all] button"}],optOut:[{click:"#cookies-controller-main-pane a[data-tab-target=manage-cookies]"},{waitFor:"#manage-cookies-pane.active"},{click:"#manage-cookies-pane.active input[type=checkbox][checked]:not([disabled])",all:!0},{click:"#manage-cookies-pane.active button[type=submit]"}],test:[]},{name:"true-car",runContext:{urlPattern:"^https://www\\.truecar\\.com/"},cosmetic:!0,prehideSelectors:[['div[aria-labelledby="cookie-banner-heading"]']],detectCmp:[{exists:'div[aria-labelledby="cookie-banner-heading"]'}],detectPopup:[{visible:'div[aria-labelledby="cookie-banner-heading"]'}],optIn:[{click:'div[aria-labelledby="cookie-banner-heading"] > button[aria-label="Close"]'}],optOut:[{hide:'div[aria-labelledby="cookie-banner-heading"]'}]},{name:"truyo",prehideSelectors:["#truyo-consent-module"],detectCmp:[{exists:"#truyo-cookieBarContent"}],detectPopup:[{visible:"#truyo-consent-module"}],optIn:[{click:"button#acceptAllCookieButton"}],optOut:[{click:"button#declineAllCookieButton"}]},{name:"tumblr-com",cosmetic:!0,prehideSelectors:["#cmp-app-container"],detectCmp:[{exists:"#cmp-app-container"}],detectPopup:[{visible:"#cmp-app-container"}],optIn:[{click:"#tumblr #cmp-app-container div.components-modal__frame > iframe > html body > div > div > div.cmp__dialog-footer > div > button.components-button.white-space-normal.is-primary"}],optOut:[{hide:"#cmp-app-container"}]},{name:"twitch-mobile",vendorUrl:"https://m.twitch.tv/",cosmetic:!0,runContext:{urlPattern:"^https?://m\\.twitch\\.tv"},prehideSelectors:[],detectCmp:[{exists:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],detectPopup:[{visible:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],optIn:[{waitForThenClick:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"]) button'}],optOut:[{hide:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"])'}]},{name:"twitch.tv",runContext:{urlPattern:"^https?://(www\\.)?twitch\\.tv"},prehideSelectors:["div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"],detectCmp:[{exists:".consent-banner .consent-banner__content--gdpr-v2"}],detectPopup:[{visible:".consent-banner .consent-banner__content--gdpr-v2"}],optIn:[{click:'button[data-a-target="consent-banner-accept"]'}],optOut:[{hide:"div:has(> .consent-banner .consent-banner__content--gdpr-v2)"},{click:'button[data-a-target="consent-banner-manage-preferences"]'},{waitFor:"input[type=checkbox][data-a-target=tw-checkbox]"},{click:"input[type=checkbox][data-a-target=tw-checkbox][checked]:not([disabled])",all:!0,optional:!0},{waitForThenClick:"[data-a-target=consent-modal-save]"},{waitForVisible:".ReactModalPortal:has([data-a-target=consent-modal-save])",check:"none"}]},{name:"twitter",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?twitter\\.com/"},prehideSelectors:['[data-testid="BottomBar"]'],detectCmp:[{exists:'[data-testid="BottomBar"] div'}],detectPopup:[{visible:'[data-testid="BottomBar"] div'}],optIn:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:last-child'}],TODOtest:[{eval:"EVAL_document.cookie.includes('d_prefs=MjoxLGNvbnNlbnRfdmVyc2lvbjoy')"}]},{name:"ubuntu.com",prehideSelectors:["dialog.cookie-policy"],detectCmp:[{any:[{exists:"dialog.cookie-policy header"},{exists:'xpath///*[@id="modal"]/div/header'}]}],detectPopup:[{any:[{visible:"dialog header"},{visible:'xpath///*[@id="modal"]/div/header'}]}],optIn:[{any:[{waitForThenClick:"#cookie-policy-button-accept"},{waitForThenClick:'xpath///*[@id="cookie-policy-button-accept"]'}]}],optOut:[{any:[{waitForThenClick:"button.js-manage"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:"dialog.cookie-policy .p-switch__input:checked",optional:!0,all:!0,timeout:500},{any:[{waitForThenClick:"dialog.cookie-policy .js-save-preferences"},{waitForThenClick:'xpath///*[@id="modal"]/div/button'}]}],test:[{eval:"EVAL_UBUNTU_COM_0"}]},{name:"UK Cookie Consent",prehideSelectors:["#catapult-cookie-bar"],cosmetic:!0,detectCmp:[{exists:"#catapult-cookie-bar"}],detectPopup:[{exists:".has-cookie-bar #catapult-cookie-bar"}],optIn:[{click:"#catapultCookie"}],optOut:[{hide:"#catapult-cookie-bar"}],test:[{eval:"EVAL_UK_COOKIE_CONSENT_0"}]},{name:"urbanarmorgear-com",cosmetic:!0,prehideSelectors:['div[class^="Layout__CookieBannerContainer-"]'],detectCmp:[{exists:'div[class^="Layout__CookieBannerContainer-"]'}],detectPopup:[{visible:'div[class^="Layout__CookieBannerContainer-"]'}],optIn:[{click:'button[class^="CookieBanner__AcceptButton"]'}],optOut:[{hide:'div[class^="Layout__CookieBannerContainer-"]'}]},{name:"usercentrics-api",detectCmp:[{exists:"#usercentrics-root"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{exists:["#usercentrics-root","[data-testid=uc-container]"]},{waitForVisible:"#usercentrics-root",timeout:2e3}],optIn:[{eval:"EVAL_USERCENTRICS_API_3"},{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_5"}],optOut:[{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_2"}],test:[{eval:"EVAL_USERCENTRICS_API_6"}]},{name:"usercentrics-button",detectCmp:[{exists:"#usercentrics-button"}],detectPopup:[{visible:"#usercentrics-button #uc-btn-accept-banner"}],optIn:[{click:"#usercentrics-button #uc-btn-accept-banner"}],optOut:[{click:"#usercentrics-button #uc-btn-deny-banner"}],test:[{eval:"EVAL_USERCENTRICS_BUTTON_0"}]},{name:"uswitch.com",prehideSelectors:["#cookie-banner-wrapper"],detectCmp:[{exists:"#cookie-banner-wrapper"}],detectPopup:[{visible:"#cookie-banner-wrapper"}],optIn:[{click:"#cookie_banner_accept_mobile"}],optOut:[{click:"#cookie_banner_save"}]},{name:"vodafone.de",runContext:{urlPattern:"^https://www\\.vodafone\\.de/"},prehideSelectors:[".dip-consent,.dip-consent-container"],detectCmp:[{exists:".dip-consent-container"}],detectPopup:[{visible:".dip-consent-content"}],optOut:[{click:'.dip-consent-btn[tabindex="2"]'}],optIn:[{click:'.dip-consent-btn[tabindex="1"]'}]},{name:"waitrose.com",prehideSelectors:["div[aria-labelledby=CookieAlertModalHeading]","section[data-test=initial-waitrose-cookie-consent-banner]","section[data-test=cookie-consent-modal]"],detectCmp:[{exists:"section[data-test=initial-waitrose-cookie-consent-banner]"}],detectPopup:[{visible:"section[data-test=initial-waitrose-cookie-consent-banner]"}],optIn:[{click:"button[data-test=accept-all]"}],optOut:[{click:"button[data-test=manage-cookies]"},{wait:200},{eval:"EVAL_WAITROSE_0"},{click:"button[data-test=submit]"}],test:[{eval:"EVAL_WAITROSE_1"}]},{name:"webflow",vendorUrl:"https://webflow.com/",prehideSelectors:[".fs-cc-components"],detectCmp:[{exists:".fs-cc-components"}],detectPopup:[{visible:".fs-cc-components"},{visible:"[fs-cc=banner]"}],optIn:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=allow]"}],optOut:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=deny]"}]},{name:"wetransfer.com",detectCmp:[{exists:".welcome__cookie-notice"}],detectPopup:[{visible:".welcome__cookie-notice"}],optIn:[{click:".welcome__button--accept"}],optOut:[{click:".welcome__button--decline"}]},{name:"whitepages.com",runContext:{urlPattern:"^https://www\\.whitepages\\.com/"},cosmetic:!0,prehideSelectors:[".cookie-wrapper, .cookie-overlay"],detectCmp:[{exists:".cookie-wrapper"}],detectPopup:[{visible:".cookie-overlay"}],optIn:[{click:'button[aria-label="Got it"]'}],optOut:[{hide:".cookie-wrapper"}]},{name:"wolframalpha",vendorUrl:"https://www.wolframalpha.com",prehideSelectors:[],cosmetic:!0,runContext:{urlPattern:"^https://www\\.wolframalpha\\.com/"},detectCmp:[{exists:"section._a_yb"}],detectPopup:[{visible:"section._a_yb"}],optIn:[{waitForThenClick:"section._a_yb button"}],optOut:[{hide:"section._a_yb"}]},{name:"woo-commerce-com",prehideSelectors:[".wccom-comp-privacy-banner .wccom-privacy-banner"],detectCmp:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],detectPopup:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],optIn:[{click:".wccom-privacy-banner__content-buttons button.is-primary"}],optOut:[{click:".wccom-privacy-banner__content-buttons button.is-secondary"},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:"div.wccom-modal__footer > button"}]},{name:"WP Cookie Notice for GDPR",vendorUrl:"https://wordpress.org/plugins/gdpr-cookie-consent/",prehideSelectors:["#gdpr-cookie-consent-bar"],detectCmp:[{exists:"#gdpr-cookie-consent-bar"}],detectPopup:[{visible:"#gdpr-cookie-consent-bar"}],optIn:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_accept"}],optOut:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_reject"}],test:[{eval:"EVAL_WP_COOKIE_NOTICE_0"}]},{name:"wpcc",cosmetic:!0,prehideSelectors:[".wpcc-container"],detectCmp:[{exists:".wpcc-container"}],detectPopup:[{exists:".wpcc-container .wpcc-message"}],optIn:[{click:".wpcc-compliance .wpcc-btn"}],optOut:[{hide:".wpcc-container"}]},{name:"xe.com",vendorUrl:"https://www.xe.com/",runContext:{urlPattern:"^https://www\\.xe\\.com/"},prehideSelectors:["[class*=ConsentBanner]"],detectCmp:[{exists:"[class*=ConsentBanner]"}],detectPopup:[{visible:"[class*=ConsentBanner]"}],optIn:[{waitForThenClick:"[class*=ConsentBanner] .egnScw"}],optOut:[{wait:1e3},{waitForThenClick:"[class*=ConsentBanner] .frDWEu"},{waitForThenClick:"[class*=ConsentBanner] .hXIpFU"}],test:[{eval:"EVAL_XE_TEST"}]},{name:"xhamster-eu",prehideSelectors:[".cookies-modal"],detectCmp:[{exists:".cookies-modal"}],detectPopup:[{exists:".cookies-modal"}],optIn:[{click:"button.cmd-button-accept-all"}],optOut:[{click:"button.cmd-button-reject-all"}]},{name:"xhamster-us",runContext:{urlPattern:"^https://(www\\.)?xhamster\\d?\\.com"},cosmetic:!0,prehideSelectors:[".cookie-announce"],detectCmp:[{exists:".cookie-announce"}],detectPopup:[{visible:".cookie-announce .announce-text"}],optIn:[{click:".cookie-announce button.xh-button"}],optOut:[{hide:".cookie-announce"}]},{name:"xing.com",detectCmp:[{exists:"div[class^=cookie-consent-CookieConsent]"}],detectPopup:[{exists:"div[class^=cookie-consent-CookieConsent]"}],optIn:[{click:"#consent-accept-button"}],optOut:[{click:"#consent-settings-button"},{click:".consent-banner-button-accept-overlay"}],test:[{eval:"EVAL_XING_0"}]},{name:"xnxx-com",cosmetic:!0,prehideSelectors:["#cookies-use-alert"],detectCmp:[{exists:"#cookies-use-alert"}],detectPopup:[{visible:"#cookies-use-alert"}],optIn:[{click:"#cookies-use-alert .close"}],optOut:[{hide:"#cookies-use-alert"}]},{name:"xvideos",vendorUrl:"https://xvideos.com",runContext:{urlPattern:"^https://[^/]*xvideos\\.com/"},prehideSelectors:[],detectCmp:[{exists:".disclaimer-opened #disclaimer-cookies"}],detectPopup:[{visible:".disclaimer-opened #disclaimer-cookies"}],optIn:[{waitForThenClick:"#disclaimer-accept_cookies"}],optOut:[{waitForThenClick:"#disclaimer-reject_cookies"}]},{name:"Yahoo",runContext:{urlPattern:"^https://consent\\.yahoo\\.com/v2/"},prehideSelectors:["#reject-all"],detectCmp:[{exists:"#consent-page"}],detectPopup:[{visible:"#consent-page"}],optIn:[{waitForThenClick:"#consent-page button[value=agree]"}],optOut:[{waitForThenClick:"#consent-page button[value=reject]"}]},{name:"youporn.com",cosmetic:!0,prehideSelectors:[".euCookieModal, #js_euCookieModal"],detectCmp:[{exists:".euCookieModal"}],detectPopup:[{exists:".euCookieModal, #js_euCookieModal"}],optIn:[{click:'button[name="user_acceptCookie"]'}],optOut:[{hide:".euCookieModal"}]},{name:"youtube-desktop",prehideSelectors:["tp-yt-iron-overlay-backdrop.opened","ytd-consent-bump-v2-lightbox"],detectCmp:[{exists:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"},{exists:'ytd-consent-bump-v2-lightbox tp-yt-paper-dialog a[href^="https://consent.youtube.com/"]'}],detectPopup:[{visible:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"}],optIn:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child button"},{wait:500}],optOut:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_DESKTOP_0"}]},{name:"youtube-mobile",prehideSelectors:[".consent-bump-v2-lightbox"],detectCmp:[{exists:"ytm-consent-bump-v2-renderer"}],detectPopup:[{visible:"ytm-consent-bump-v2-renderer"}],optIn:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:first-child button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:first-child button"},{wait:500}],optOut:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:nth-child(2) button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:nth-child(2) button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_MOBILE_0"}]},{name:"zdf",prehideSelectors:["#zdf-cmp-banner-sdk"],detectCmp:[{exists:"#zdf-cmp-banner-sdk"}],detectPopup:[{visible:"#zdf-cmp-main.zdf-cmp-show"}],optIn:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-accept-btn"}],optOut:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-deny-btn"}],test:[]}],C={"didomi.io":{detectors:[{presentMatcher:{target:{selector:"#didomi-host, #didomi-notice"},type:"css"},showingMatcher:{target:{selector:"body.didomi-popup-open, .didomi-notice-banner"},type:"css"}}],methods:[{action:{target:{selector:".didomi-popup-notice-buttons .didomi-button:not(.didomi-button-highlight), .didomi-notice-banner .didomi-learn-more-button"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{retries:50,target:{selector:"#didomi-purpose-cookies"},type:"waitcss",waitTime:50},{consents:[{description:"Share (everything) with others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:last-child"},type:"click"},type:"X"},{description:"Information storage and access",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:last-child"},type:"click"},type:"D"},{description:"Content selection, offers and marketing",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:last-child"},type:"click"},type:"E"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:last-child"},type:"click"},type:"B"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:last-child"},type:"click"},type:"B"},{description:"Ad and content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection",falseAction:{parent:{childFilter:{target:{selector:"#didomi-purpose-pub-ciblee"}},selector:".didomi-consent-popup-data-processing, .didomi-components-accordion-label-container"},target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - basics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - partners and subsidiaries",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:last-child"},type:"click"},type:"F"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:last-child"},type:"click"},type:"A"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:last-child"},type:"click"},type:"A"},{description:"Content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:last-child"},type:"click"},type:"E"},{description:"Ad delivery",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:last-child"},type:"click"},type:"F"}],type:"consent"},{action:{consents:[{matcher:{childFilter:{target:{selector:":not(.didomi-components-radio__option--selected)"}},type:"css"},trueAction:{target:{selector:":nth-child(2)"},type:"click"},falseAction:{target:{selector:":first-child"},type:"click"},type:"X"}],type:"consent"},target:{selector:".didomi-components-radio"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".didomi-consent-popup-footer .didomi-consent-popup-actions"},target:{selector:".didomi-components-button:first-child"},type:"click"},name:"SAVE_CONSENT"}]},oil:{detectors:[{presentMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"},showingMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".as-js-advanced-settings"},type:"click"},{retries:"10",target:{selector:".as-oil-cpc__purpose-container"},type:"waitcss",waitTime:"250"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{consents:[{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"D"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"B"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:".as-oil__btn-optin"},type:"click"},name:"SAVE_CONSENT"},{action:{target:{selector:"div.as-oil"},type:"hide"},name:"HIDE_CMP"}]},optanon:{detectors:[{presentMatcher:{target:{selector:"#optanon-menu, .optanon-alert-box-wrapper"},type:"css"},showingMatcher:{target:{displayFilter:!0,selector:".optanon-alert-box-wrapper"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".optanon-alert-box-wrapper .optanon-toggle-display, a[onclick*='OneTrust.ToggleInfoDisplay()'], a[onclick*='Optanon.ToggleInfoDisplay()']"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".preference-menu-item #Your-privacy"},type:"click"},{target:{selector:"#optanon-vendor-consent-text"},type:"click"},{action:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},target:{selector:"#optanon-vendor-consent-list .vendor-item"},type:"foreach"},{target:{selector:".vendor-consent-back-link"},type:"click"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"D"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".optanon-save-settings-button"},target:{selector:".optanon-white-button-middle"},type:"click"},name:"SAVE_CONSENT"},{action:{actions:[{target:{selector:"#optanon-popup-wrapper"},type:"hide"},{target:{selector:"#optanon-popup-bg"},type:"hide"},{target:{selector:".optanon-alert-box-wrapper"},type:"hide"}],type:"list"},name:"HIDE_CMP"}]},quantcast2:{detectors:[{presentMatcher:{target:{selector:"[data-tracking-opt-in-overlay]"},type:"css"},showingMatcher:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"css"}}],methods:[{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{type:"wait",waitTime:500},{action:{actions:[{target:{selector:"div",textFilter:["Information storage and access"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"D"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Personalization"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Ad selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Content selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"E"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Measurement"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"B"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Other Partners"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},type:"ifcss"}],type:"list"},parent:{childFilter:{target:{selector:"input"}},selector:"[data-tracking-opt-in-overlay] > div > div"},target:{childFilter:{target:{selector:"input"}},selector:":scope > div"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-save]"},type:"click"},name:"SAVE_CONSENT"}]},springer:{detectors:[{presentMatcher:{parent:null,target:{selector:".cmp-app_gdpr"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".cmp-popup_popup"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".cmp-intro_rejectAll"},type:"click"},{type:"wait",waitTime:250},{target:{selector:".cmp-purposes_purposeItem:not(.cmp-purposes_selectedPurpose)"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{consents:[{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"D"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"}],type:"consent"},name:"DO_CONSENT"},{action:{target:{selector:".cmp-details_save"},type:"click"},name:"SAVE_CONSENT"}]},wordpressgdpr:{detectors:[{presentMatcher:{parent:null,target:{selector:".wpgdprc-consent-bar"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".wpgdprc-consent-bar"},type:"css"}}],methods:[{action:{parent:null,target:{selector:".wpgdprc-consent-bar .wpgdprc-consent-bar__settings",textFilter:null},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Eyeota"},type:"click"},{consents:[{description:"Eyeota Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Advertising"},type:"click"},{consents:[{description:"Advertising Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{parent:null,target:{selector:".wpgdprc-button",textFilter:"Save my settings"},type:"click"},name:"SAVE_CONSENT"}]}},v={autoconsent:w,consentomatic:C},f=Object.freeze({__proto__:null,autoconsent:w,consentomatic:C,default:v});const A=new class{constructor(e,t=null,o=null){if(this.id=n(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},a.sendContentMessage=e,this.sendContentMessage=e,this.rules=[],this.updateState({lifecycle:"loading"}),this.addDynamicRules(),t)this.initialize(t,o);else{o&&this.parseDeclarativeRules(o);e({type:"init",url:window.location.href}),this.updateState({lifecycle:"waitingForInitResponse"})}this.domActions=new class{constructor(e){this.autoconsentInstance=e}click(e,t=!1){const o=this.elementSelector(e);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[click]",e,t,o),o.length>0&&(t?o.forEach((e=>e.click())):o[0].click()),o.length>0}elementExists(e){return this.elementSelector(e).length>0}elementVisible(e,t){const o=this.elementSelector(e),c=new Array(o.length);return o.forEach(((e,t)=>{c[t]=k(e)})),"none"===t?c.every((e=>!e)):0!==c.length&&("any"===t?c.some((e=>e)):c.every((e=>e)))}waitForElement(e,t=1e4){const o=Math.ceil(t/200);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[waitForElement]",e),h((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return h((()=>this.elementVisible(e,o)),Math.ceil(t/200),200)}async waitForThenClick(e,t=1e4,o=!1){return await this.waitForElement(e,t),this.click(e,o)}wait(e){return new Promise((t=>{setTimeout((()=>{t(!0)}),e)}))}hide(e,t){return m(u(),e,t)}prehide(e){const t=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),m(t,e,"opacity")}undoPrehide(){const e=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[undoprehide]",e,location.href),e&&e.remove(),!!e}querySingleReplySelector(e,t=document){if(e.startsWith("aria/"))return[];if(e.startsWith("xpath/")){const o=e.slice(6),c=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let i=null;const n=[];for(;i=c.iterateNext();)n.push(i);return n}return e.startsWith("text/")||e.startsWith("pierce/")?[]:t.shadowRoot?Array.from(t.shadowRoot.querySelectorAll(e)):Array.from(t.querySelectorAll(e))}querySelectorChain(e){let t,o=document;for(const c of e){if(t=this.querySingleReplySelector(c,o),0===t.length)return[];o=t[0]}return t}elementSelector(e){return"string"==typeof e?this.querySingleReplySelector(e):this.querySelectorChain(e)}}(this)}initialize(e,t){const o=b(e);if(o.logs.lifecycle&&console.log("autoconsent init",window.location.href),this.config=o,o.enabled){if(t&&this.parseDeclarativeRules(t),this.rules=function(e,t){return e.filter((e=>(!t.disabledCmps||!t.disabledCmps.includes(e.name))&&(t.enableCosmeticRules||!e.isCosmetic)))}(this.rules,o),e.enablePrehide)if(document.documentElement)this.prehideElements();else{const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.prehideElements()};window.addEventListener("DOMContentLoaded",e)}if("loading"===document.readyState){const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.start()};window.addEventListener("DOMContentLoaded",e)}else this.start();this.updateState({lifecycle:"initialized"})}else o.logs.lifecycle&&console.log("autoconsent is disabled")}addDynamicRules(){y.forEach((e=>{this.rules.push(new e(this))}))}parseDeclarativeRules(e){Object.keys(e.consentomatic).forEach((t=>{this.addConsentomaticCMP(t,e.consentomatic[t])})),e.autoconsent.forEach((e=>{this.addDeclarativeCMP(e)}))}addDeclarativeCMP(e){this.rules.push(new d(e,this))}addConsentomaticCMP(e,t){this.rules.push(new class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=l,this.isCosmetic=!1,t.methods.forEach((e=>{e.action&&this.methods.set(e.name,e.action)})),this.hasSelfTest=!1}get isIntermediate(){return!1}checkRunContext(){return!0}async detectCmp(){return this.config.detectors.map((e=>o(e.presentMatcher))).some((e=>!!e))}async detectPopup(){return this.config.detectors.map((e=>o(e.showingMatcher))).some((e=>!!e))}async executeAction(e,t){return!this.methods.has(e)||c(this.methods.get(e),t)}async optOut(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",[]),await this.executeAction("SAVE_CONSENT"),!0}async optIn(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",["D","A","B","E","F","X"]),await this.executeAction("SAVE_CONSENT"),!0}async openCmp(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),!0}async test(){return!0}}(`com_${e}`,t))}start(){window.requestIdleCallback?window.requestIdleCallback((()=>this._start()),{timeout:500}):this._start()}async _start(){const e=this.config.logs;e.lifecycle&&console.log(`Detecting CMPs on ${window.location.href}`),this.updateState({lifecycle:"started"});const t=await this.findCmp(this.config.detectRetries);if(this.updateState({detectedCmps:t.map((e=>e.name))}),0===t.length)return e.lifecycle&&console.log("no CMP found",location.href),this.config.enablePrehide&&this.undoPrehide(),this.updateState({lifecycle:"nothingDetected"}),!1;this.updateState({lifecycle:"cmpDetected"});const o=[],c=[];for(const e of t)e.isCosmetic?c.push(e):o.push(e);let i=!1,n=await this.detectPopups(o,(async e=>{i=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(c,(async e=>{i=await this.handlePopup(e)}))),0===n.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(n.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:n.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return i}async findCmp(e){const t=this.config.logs;this.updateState({findCmpAttempts:this.state.findCmpAttempts+1});const o=[];for(const e of this.rules)try{if(!e.checkRunContext())continue;await e.detectCmp()&&(t.lifecycle&&console.log(`Found CMP: ${e.name} ${window.location.href}`),this.sendContentMessage({type:"cmpDetected",url:location.href,cmp:e.name}),o.push(e))}catch(o){t.errors&&console.warn(`error detecting ${e.name}`,o)}return 0===o.length&&e>0?(await this.domActions.wait(500),this.findCmp(e-1)):o}async detectPopup(e){if(await this.waitForPopup(e).catch((t=>(this.config.logs.errors&&console.warn(`error waiting for a popup for ${e.name}`,t),!1))))return this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),e;throw new Error("Popup is not shown")}async detectPopups(e,t){const o=e.map((e=>this.detectPopup(e)));await Promise.any(o).then((e=>{t(e)})).catch((()=>null));const c=await Promise.allSettled(o),i=[];for(const e of c)"fulfilled"===e.status&&i.push(e.value);return i}async handlePopup(e){return this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),this.foundCmp=e,"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(this.config.logs.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}async doOptOut(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptOut"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`),t=await this.foundCmp.optOut(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt out result ${t}`)):(e.errors&&console.log("no CMP to opt out"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optOutResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:this.foundCmp&&this.foundCmp.hasSelfTest,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optOutSucceeded":"optOutFailed"}),t}async doOptIn(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptIn"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`),t=await this.foundCmp.optIn(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt in result ${t}`)):(e.errors&&console.log("no CMP to opt in"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optInResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:!1,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optInSucceeded":"optInFailed"}),t}async doSelfTest(){const e=this.config.logs;let t;return this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`),t=await this.foundCmp.test()):(e.errors&&console.log("no CMP to self test"),t=!1),this.sendContentMessage({type:"selfTestResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,url:location.href}),this.updateState({selfTest:t}),t}async waitForPopup(e,t=5,o=500){const c=this.config.logs;c.lifecycle&&console.log("checking if popup is open...",e.name);const i=await e.detectPopup().catch((t=>(c.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!i&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(c.lifecycle&&console.log(e.name,"popup is "+(i?"open":"not open")),i)}prehideElements(){const e=this.config.logs,t=this.rules.reduce(((e,t)=>t.prehideSelectors?[...e,...t.prehideSelectors]:e),["#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium"]);return this.updateState({prehideOn:!0}),setTimeout((()=>{this.config.enablePrehide&&this.state.prehideOn&&!["runningOptOut","runningOptIn"].includes(this.state.lifecycle)&&(e.lifecycle&&console.log("Process is taking too long, unhiding elements"),this.undoPrehide())}),this.config.prehideTimeout||2e3),this.domActions.prehide(t.join(","))}undoPrehide(){return this.updateState({prehideOn:!1}),this.domActions.undoPrehide()}updateState(e){Object.assign(this.state,e),this.sendContentMessage({type:"report",instanceId:this.id,url:window.location.href,mainFrame:window.top===window.self,state:this.state})}async receiveMessageCallback(e){const t=this.config?.logs;switch(t?.messages&&console.log("received from background",e,window.location.href),e.type){case"initResp":this.initialize(e.config,e.rules);break;case"optIn":await this.doOptIn();break;case"optOut":await this.doOptOut();break;case"selfTest":await this.doSelfTest();break;case"evalResp":!function(e,t){const o=a.pending.get(e);o?(a.pending.delete(e),o.timer&&window.clearTimeout(o.timer),o.resolve(t)):console.warn("no eval #",e)}(e.id,e.result)}}}((e=>{window.webkit.messageHandlers[e.type]&&window.webkit.messageHandlers[e.type].postMessage(e).then((e=>{A.receiveMessageCallback(e)}))}),null,f);window.autoconsentMessageCallback=e=>{A.receiveMessageCallback(e)}}(); diff --git a/DuckDuckGo/Autofill/ContentOverlayViewController.swift b/DuckDuckGo/Autofill/ContentOverlayViewController.swift index 93b95b830c..d90b35f71d 100644 --- a/DuckDuckGo/Autofill/ContentOverlayViewController.swift +++ b/DuckDuckGo/Autofill/ContentOverlayViewController.swift @@ -253,6 +253,7 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], withTrigger trigger: AutofillUserScript.GetTriggerType, + onAccountSelected account: @escaping (SecureVaultModels.WebsiteAccount?) -> Void, completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) { // no-op on macOS } @@ -343,7 +344,8 @@ extension ContentOverlayViewController: SecureVaultManagerDelegate { } public func secureVaultManager(_: SecureVaultManager, didRequestRuntimeConfigurationForDomain domain: String, completionHandler: @escaping (String?) -> Void) { - let properties = ContentScopeProperties(gpcEnabled: PrivacySecurityPreferences.shared.gpcEnabled, + let isGPCEnabled = WebTrackingProtectionPreferences.shared.isGPCEnabled + let properties = ContentScopeProperties(gpcEnabled: isGPCEnabled, sessionKey: topAutofillUserScript?.sessionKey ?? "", featureToggles: ContentScopeFeatureToggles.supportedFeaturesOnMacOS(privacyConfigurationManager.privacyConfig)) diff --git a/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift b/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift new file mode 100644 index 0000000000..82e5748c72 --- /dev/null +++ b/DuckDuckGo/Bookmarks/Extensions/Bookmarks+Tab.swift @@ -0,0 +1,41 @@ +// +// Bookmarks+Tab.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension Tab { + + @MainActor + static func withContentOfBookmark(folder: BookmarkFolder, burnerMode: BurnerMode) -> [Tab] { + folder.children.compactMap { entity in + guard let url = (entity as? Bookmark)?.urlObject else { return nil } + return Tab(content: .url(url, source: .bookmark), shouldLoadInBackground: true, burnerMode: burnerMode) + } + } + +} + +extension TabCollection { + + @MainActor + static func withContentOfBookmark(folder: BookmarkFolder, burnerMode: BurnerMode) -> TabCollection { + let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: burnerMode) + return TabCollection(tabs: tabs) + } + +} diff --git a/DuckDuckGo/Bookmarks/Model/Bookmark.swift b/DuckDuckGo/Bookmarks/Model/Bookmark.swift index 99ca656b3f..4d3cf399d8 100644 --- a/DuckDuckGo/Bookmarks/Model/Bookmark.swift +++ b/DuckDuckGo/Bookmarks/Model/Bookmark.swift @@ -19,29 +19,36 @@ import Cocoa import Bookmarks -internal class BaseBookmarkEntity: Identifiable { +internal class BaseBookmarkEntity: Identifiable, Equatable, Hashable { static func singleEntity(with uuid: String) -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() - request.predicate = NSPredicate(format: "%K == %@ AND %K == NO", #keyPath(BookmarkEntity.uuid), uuid, #keyPath(BookmarkEntity.isPendingDeletion)) + request.predicate = NSPredicate(format: "%K == %@ AND %K == NO AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.uuid), uuid, + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) return request } static func favorite(with uuid: String, favoritesFolder: BookmarkEntity) -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() - request.predicate = NSPredicate(format: "%K == %@ AND %K CONTAINS %@ AND %K == NO AND %K == NO", + request.predicate = NSPredicate(format: "%K == %@ AND %K CONTAINS %@ AND %K == NO AND %K == NO AND (%K == NO OR %K == nil)", #keyPath(BookmarkEntity.uuid), uuid as CVarArg, #keyPath(BookmarkEntity.favoriteFolders), favoritesFolder, #keyPath(BookmarkEntity.isFolder), - #keyPath(BookmarkEntity.isPendingDeletion)) + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) return request } static func entities(with identifiers: [String]) -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() - request.predicate = NSPredicate(format: "%K IN %@ AND %K == NO", #keyPath(BookmarkEntity.uuid), identifiers, #keyPath(BookmarkEntity.isPendingDeletion)) + request.predicate = NSPredicate(format: "%K IN %@ AND %K == NO AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.uuid), identifiers, + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) return request } @@ -92,13 +99,33 @@ internal class BaseBookmarkEntity: Identifiable { } } + // Subclasses needs to override to check equality on their properties + func isEqual(to instance: BaseBookmarkEntity) -> Bool { + id == instance.id && + title == instance.title && + isFolder == instance.isFolder + } + + static func == (lhs: BaseBookmarkEntity, rhs: BaseBookmarkEntity) -> Bool { + return type(of: lhs) == type(of: rhs) && lhs.isEqual(to: rhs) + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(title) + hasher.combine(isFolder) + } + } final class BookmarkFolder: BaseBookmarkEntity { static func bookmarkFoldersFetchRequest() -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() - request.predicate = NSPredicate(format: "%K == YES AND %K == NO", #keyPath(BookmarkEntity.isFolder), #keyPath(BookmarkEntity.isPendingDeletion)) + request.predicate = NSPredicate(format: "%K == YES AND %K == NO AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.isFolder), + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) return request } @@ -129,13 +156,48 @@ final class BookmarkFolder: BaseBookmarkEntity { super.init(id: id, title: title, isFolder: true) } + + override func isEqual(to instance: BaseBookmarkEntity) -> Bool { + guard let folder = instance as? BookmarkFolder else { + return false + } + return id == folder.id && + title == folder.title && + isFolder == folder.isFolder && + isParentFolderEqual(lhs: parentFolderUUID, rhs: folder.parentFolderUUID) && + children == folder.children + } + + override func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(title) + hasher.combine(isFolder) + hasher.combine(parentFolderUUID) + hasher.combine(children) + } + + // In some cases a bookmark folder that is child of the root folder has its `parentFolderUUID` set to `bookmarks_root`. In some other cases is nil. Making sure that comparing a `nil` and a `bookmarks_root` does not return false. Probably would be good idea to remove the optionality of `parentFolderUUID` in the future and set it to `bookmarks_root` when needed. + private func isParentFolderEqual(lhs: String?, rhs: String?) -> Bool { + switch (lhs, rhs) { + case (.none, .none): + return true + case (.some(let lhsValue), .some(let rhsValue)): + return lhsValue == rhsValue + case (.some(let value), .none), (.none, .some(let value)): + return value == "bookmarks_root" + } + } + } final class Bookmark: BaseBookmarkEntity { static func bookmarksFetchRequest() -> NSFetchRequest { let request = BookmarkEntity.fetchRequest() - request.predicate = NSPredicate(format: "%K == NO AND %K == NO", #keyPath(BookmarkEntity.isFolder), #keyPath(BookmarkEntity.isPendingDeletion)) + request.predicate = NSPredicate(format: "%K == NO AND %K == NO AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.isFolder), + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) return request } @@ -183,12 +245,33 @@ final class Bookmark: BaseBookmarkEntity { parentFolderUUID: bookmark.parentFolderUUID) } -} + convenience init(from bookmark: Bookmark, withNewUrl url: String, title: String, isFavorite: Bool) { + self.init(id: bookmark.id, + url: url, + title: title, + isFavorite: isFavorite, + parentFolderUUID: bookmark.parentFolderUUID) + } -extension BaseBookmarkEntity: Equatable { + override func isEqual(to instance: BaseBookmarkEntity) -> Bool { + guard let bookmark = instance as? Bookmark else { + return false + } + return id == bookmark.id && + title == bookmark.title && + isFolder == bookmark.isFolder && + url == bookmark.url && + isFavorite == bookmark.isFavorite && + parentFolderUUID == bookmark.parentFolderUUID + } - static func == (lhs: BaseBookmarkEntity, rhs: BaseBookmarkEntity) -> Bool { - return lhs.id == rhs.id + override func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(title) + hasher.combine(isFolder) + hasher.combine(url) + hasher.combine(isFavorite) + hasher.combine(parentFolderUUID) } } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkFolderInfo.swift b/DuckDuckGo/Bookmarks/Model/BookmarkFolderInfo.swift new file mode 100644 index 0000000000..a4f8004f71 --- /dev/null +++ b/DuckDuckGo/Bookmarks/Model/BookmarkFolderInfo.swift @@ -0,0 +1,32 @@ +// +// BookmarkFolderInfo.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol BookmarksEntityIdentifiable { + var entityId: String { get } + var parentId: String? { get } +} + +struct BookmarkEntityInfo: Equatable, BookmarksEntityIdentifiable { + let entity: BaseBookmarkEntity + let parent: BookmarkFolder? + + var entityId: String { entity.id } + var parentId: String? { parent?.id } +} diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkList.swift b/DuckDuckGo/Bookmarks/Model/BookmarkList.swift index 2fe7281adb..dcae056327 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkList.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkList.swift @@ -19,10 +19,11 @@ import Foundation import BrowserServicesKit import Common +import Suggestions struct BookmarkList { - struct IdentifiableBookmark: Equatable, BrowserServicesKit.Bookmark { + struct IdentifiableBookmark: Equatable, Suggestions.Bookmark { let id: String let url: String let urlObject: URL? @@ -124,6 +125,13 @@ struct BookmarkList { } } + mutating func update(bookmark: Bookmark, newURL: String, newTitle: String, newIsFavorite: Bool) -> Bookmark? { + guard !bookmark.isFolder else { return nil } + + let newBookmark = Bookmark(from: bookmark, withNewUrl: newURL, title: newTitle, isFavorite: newIsFavorite) + return updateBookmarkList(newBookmark: newBookmark, oldBookmark: bookmark) + } + mutating func updateUrl(of bookmark: Bookmark, to newURL: String) -> Bookmark? { guard !bookmark.isFolder else { return nil } @@ -131,12 +139,25 @@ struct BookmarkList { os_log("BookmarkList: Update failed, new url already in bookmark list") return nil } + + let newBookmark = Bookmark(from: bookmark, with: newURL) + return updateBookmarkList(newBookmark: newBookmark, oldBookmark: bookmark) + } + + func bookmarks() -> [IdentifiableBookmark] { + return allBookmarkURLsOrdered + } + +} + +private extension BookmarkList { + + mutating private func updateBookmarkList(newBookmark: Bookmark, oldBookmark bookmark: Bookmark) -> Bookmark? { guard itemsDict[bookmark.url] != nil, let index = allBookmarkURLsOrdered.firstIndex(of: IdentifiableBookmark(from: bookmark)) else { os_log("BookmarkList: Update failed, no such item in bookmark list") return nil } - let newBookmark = Bookmark(from: bookmark, with: newURL) let newIdentifiableBookmark = IdentifiableBookmark(from: newBookmark) allBookmarkURLsOrdered.remove(at: index) @@ -146,13 +167,8 @@ struct BookmarkList { let updatedBookmarks = existingBookmarks.filter { $0.id != bookmark.id } itemsDict[bookmark.url] = updatedBookmarks - itemsDict[newURL] = (itemsDict[newURL] ?? []) + [bookmark] + itemsDict[newBookmark.url] = (itemsDict[newBookmark.url] ?? []) + [newBookmark] return newBookmark } - - func bookmarks() -> [IdentifiableBookmark] { - return allBookmarkURLsOrdered - } - } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift index ee19960da1..b3172da78e 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkManager.swift @@ -35,7 +35,9 @@ protocol BookmarkManager: AnyObject { func remove(folder: BookmarkFolder) func remove(objectsWithUUIDs uuids: [String]) func update(bookmark: Bookmark) + func update(bookmark: Bookmark, withURL url: URL, title: String, isFavorite: Bool) func update(folder: BookmarkFolder) + func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) @discardableResult func updateUrl(of bookmark: Bookmark, to newUrl: URL) -> Bookmark? func add(bookmark: Bookmark, to parent: BookmarkFolder?, completion: @escaping (Error?) -> Void) func add(objectsWithUUIDs uuids: [String], to parent: BookmarkFolder?, completion: @escaping (Error?) -> Void) @@ -211,12 +213,35 @@ final class LocalBookmarkManager: BookmarkManager { } + func update(bookmark: Bookmark, withURL url: URL, title: String, isFavorite: Bool) { + guard list != nil else { return } + guard getBookmark(forUrl: bookmark.url) != nil else { + os_log("LocalBookmarkManager: Failed to update bookmark url - not in the list.", type: .error) + return + } + + guard let newBookmark = list?.update(bookmark: bookmark, newURL: url.absoluteString, newTitle: title, newIsFavorite: isFavorite) else { + os_log("LocalBookmarkManager: Failed to update URL of bookmark.", type: .error) + return + } + + bookmarkStore.update(bookmark: newBookmark) + loadBookmarks() + requestSync() + } + func update(folder: BookmarkFolder) { bookmarkStore.update(folder: folder) loadBookmarks() requestSync() } + func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) { + bookmarkStore.update(folder: folder, andMoveToParent: parent) + loadBookmarks() + requestSync() + } + func updateUrl(of bookmark: Bookmark, to newUrl: URL) -> Bookmark? { guard list != nil else { return nil } guard getBookmark(forUrl: bookmark.url) != nil else { diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkNode.swift b/DuckDuckGo/Bookmarks/Model/BookmarkNode.swift index 86f24bdc28..4f6b58c10f 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkNode.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkNode.swift @@ -67,11 +67,24 @@ final class BookmarkNode: Hashable { return 0 } - init(representedObject: AnyObject, parent: BookmarkNode?) { + /// Creates an instance of a bookmark node. + /// - Parameters: + /// - representedObject: The represented object contained in the node. + /// - parent: An optional parent node. + /// - uniqueId: A unique identifier for the node. This should be used only in unit tests. + /// - Attention: Use this initializer only in tests. + init(representedObject: AnyObject, parent: BookmarkNode?, uniqueId: Int) { self.representedObject = representedObject self.parent = parent - self.uniqueID = BookmarkNode.incrementingID + self.uniqueID = uniqueId + } + /// Creates an instance of a bookmark node. + /// - Parameters: + /// - representedObject: The represented object contained in the node. + /// - parent: An optional parent node. + convenience init(representedObject: AnyObject, parent: BookmarkNode?) { + self.init(representedObject: representedObject, parent: parent, uniqueId: BookmarkNode.incrementingID) BookmarkNode.incrementingID += 1 } @@ -165,7 +178,7 @@ final class BookmarkNode: Hashable { // The Node class will most frequently represent Bookmark entities and PseudoFolders. Because of this, their unique properties are // used to derive the hash for the node so that equality can be handled based on the represented object. if let entity = self.representedObject as? BaseBookmarkEntity { - hasher.combine(entity.id) + hasher.combine(entity.hashValue) } else if let folder = self.representedObject as? PseudoFolder { hasher.combine(folder.name) } else { @@ -176,7 +189,7 @@ final class BookmarkNode: Hashable { // MARK: - Equatable class func == (lhs: BookmarkNode, rhs: BookmarkNode) -> Bool { - return lhs === rhs + return lhs.uniqueID == rhs.uniqueID && lhs.representedObjectEquals(rhs.representedObject) } } diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift index edfcfa6e2e..93a404fa95 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkOutlineViewDataSource.swift @@ -30,10 +30,12 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS @Published var selectedFolders: [BookmarkFolder] = [] let treeController: BookmarkTreeController - var expandedNodesIDs = Set() + private(set) var expandedNodesIDs = Set() private let contentMode: ContentMode private let bookmarkManager: BookmarkManager + private let showMenuButtonOnHover: Bool + private let onMenuRequestedAction: ((BookmarkOutlineCellView) -> Void)? private let presentFaviconsFetcherOnboarding: (() -> Void)? private var favoritesPseudoFolder = PseudoFolder.favorites @@ -43,11 +45,15 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS contentMode: ContentMode, bookmarkManager: BookmarkManager, treeController: BookmarkTreeController, + showMenuButtonOnHover: Bool = true, + onMenuRequestedAction: ((BookmarkOutlineCellView) -> Void)? = nil, presentFaviconsFetcherOnboarding: (() -> Void)? = nil ) { self.contentMode = contentMode self.bookmarkManager = bookmarkManager self.treeController = treeController + self.showMenuButtonOnHover = showMenuButtonOnHover + self.onMenuRequestedAction = onMenuRequestedAction self.presentFaviconsFetcherOnboarding = presentFaviconsFetcherOnboarding super.init() @@ -123,6 +129,8 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS } let cell = outlineView.makeView(withIdentifier: .init(BookmarkOutlineCellView.className()), owner: self) as? BookmarkOutlineCellView ?? BookmarkOutlineCellView(identifier: .init(BookmarkOutlineCellView.className())) + cell.shouldShowMenuButton = showMenuButtonOnHover + cell.delegate = self if let bookmark = node.representedObject as? Bookmark { cell.update(from: bookmark) @@ -233,7 +241,7 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS // Folders cannot be dragged onto any of their descendants: let containsDescendantOfDestination = draggedFolders.contains { draggedFolder in - let folder = BookmarkFolder(id: draggedFolder.id, title: draggedFolder.name) + let folder = BookmarkFolder(id: draggedFolder.id, title: draggedFolder.name, parentFolderUUID: draggedFolder.parentFolderUUID, children: draggedFolder.children) guard let draggedNode = treeController.node(representing: folder) else { return false @@ -329,3 +337,11 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS } } + +// MARK: - BookmarkOutlineCellViewDelegate + +extension BookmarkOutlineViewDataSource: BookmarkOutlineCellViewDelegate { + func outlineCellViewRequestedMenu(_ cell: BookmarkOutlineCellView) { + onMenuRequestedAction?(cell) + } +} diff --git a/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift b/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift index 06167f2356..b8549cad89 100644 --- a/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift +++ b/DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift @@ -33,19 +33,11 @@ final class BookmarkSidebarTreeController: BookmarkTreeControllerDataSource { // MARK: - Private private func childNodesForRootNode(_ node: BookmarkNode) -> [BookmarkNode] { - let favorites = PseudoFolder.favorites - let favoritesNode = BookmarkNode(representedObject: favorites, parent: node) - favoritesNode.canHaveChildNodes = false - - let blankSpacer = SpacerNode.blank - let spacerNode = BookmarkNode(representedObject: blankSpacer, parent: node) - spacerNode.canHaveChildNodes = false - let bookmarks = PseudoFolder.bookmarks let bookmarksNode = BookmarkNode(representedObject: bookmarks, parent: node) bookmarksNode.canHaveChildNodes = true - return [favoritesNode, spacerNode, bookmarksNode] + return [bookmarksNode] } private func childNodes(for parentNode: BookmarkNode) -> [BookmarkNode] { diff --git a/DuckDuckGo/Bookmarks/Model/PasteboardFolder.swift b/DuckDuckGo/Bookmarks/Model/PasteboardFolder.swift index ffc3eb9a41..f99f1e040d 100644 --- a/DuckDuckGo/Bookmarks/Model/PasteboardFolder.swift +++ b/DuckDuckGo/Bookmarks/Model/PasteboardFolder.swift @@ -26,12 +26,15 @@ struct PasteboardFolder: Hashable { static let name = "name" } - let id: String - let name: String + var id: String { folder.id } + var name: String { folder.title } + var parentFolderUUID: String? { folder.parentFolderUUID } + var children: [BaseBookmarkEntity] { folder.children } - init(id: String, name: String) { - self.id = id - self.name = name + private let folder: BookmarkFolder + + init(folder: BookmarkFolder) { + self.folder = folder } // MARK: - Pasteboard Restoration @@ -41,7 +44,7 @@ struct PasteboardFolder: Hashable { return nil } - self.init(id: id, name: name) + self.init(folder: .init(id: id, title: name)) } init?(pasteboardItem: NSPasteboardItem) { @@ -78,19 +81,17 @@ struct PasteboardFolder: Hashable { static let folderUTIInternalType = NSPasteboard.PasteboardType(rawValue: folderUTIInternal) var pasteboardFolder: PasteboardFolder { - return PasteboardFolder(id: folderID, name: folderName) + return PasteboardFolder(folder: folder) } var internalDictionary: PasteboardAttributes { return pasteboardFolder.internalDictionaryRepresentation } - private let folderID: String - private let folderName: String + private let folder: BookmarkFolder init(folder: BookmarkFolder) { - self.folderID = folder.id - self.folderName = folder.title + self.folder = folder } // MARK: - NSPasteboardWriting @@ -102,7 +103,7 @@ struct PasteboardFolder: Hashable { func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? { switch type { case .string: - return folderName + return folder.title case FolderPasteboardWriter.folderUTIInternalType: return internalDictionary default: diff --git a/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift b/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift index c3aed14be7..85cef1888a 100644 --- a/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift +++ b/DuckDuckGo/Bookmarks/Model/PseudoFolder.swift @@ -22,7 +22,7 @@ import Foundation final class PseudoFolder: Equatable { static let favorites = PseudoFolder(id: UUID().uuidString, name: UserText.favorites, icon: .favoriteFilledBorder) - static let bookmarks = PseudoFolder(id: UUID().uuidString, name: UserText.bookmarks, icon: .folder) + static let bookmarks = PseudoFolder(id: UUID().uuidString, name: UserText.bookmarks, icon: .bookmarksFolder) let id: String let name: String diff --git a/DuckDuckGo/Bookmarks/Services/BookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/BookmarkStore.swift index 6b7981c96a..3466d1a7fa 100644 --- a/DuckDuckGo/Bookmarks/Services/BookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/BookmarkStore.swift @@ -52,6 +52,7 @@ protocol BookmarkStore { func remove(objectsWithUUIDs: [String], completion: @escaping (Bool, Error?) -> Void) func update(bookmark: Bookmark) func update(folder: BookmarkFolder) + func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) func add(objectsWithUUIDs: [String], to parent: BookmarkFolder?, completion: @escaping (Error?) -> Void) func update(objectsWithUUIDs uuids: [String], update: @escaping (BaseBookmarkEntity) -> Void, completion: @escaping (Error?) -> Void) func canMoveObjectWithUUID(objectUUID uuid: String, to parent: BookmarkFolder) -> Bool diff --git a/DuckDuckGo/Bookmarks/Services/BookmarkStoreMock.swift b/DuckDuckGo/Bookmarks/Services/BookmarkStoreMock.swift index f505891891..2ab57158a9 100644 --- a/DuckDuckGo/Bookmarks/Services/BookmarkStoreMock.swift +++ b/DuckDuckGo/Bookmarks/Services/BookmarkStoreMock.swift @@ -46,6 +46,11 @@ public final class BookmarkStoreMock: BookmarkStore { self.updateFavoriteIndexCalled = updateFavoriteIndexCalled } + var capturedFolder: BookmarkFolder? + var capturedParentFolder: BookmarkFolder? + var capturedParentFolderType: ParentFolderType? + var capturedBookmark: Bookmark? + var loadAllCalled = false var bookmarks: [BaseBookmarkEntity]? var loadError: Error? @@ -60,6 +65,8 @@ public final class BookmarkStoreMock: BookmarkStore { func save(bookmark: Bookmark, parent: BookmarkFolder?, index: Int?, completion: @escaping (Bool, Error?) -> Void) { saveBookmarkCalled = true bookmarks?.append(bookmark) + capturedParentFolder = parent + capturedBookmark = bookmark completion(saveBookmarkSuccess, saveBookmarkError) } @@ -68,6 +75,8 @@ public final class BookmarkStoreMock: BookmarkStore { var saveFolderError: Error? func save(folder: BookmarkFolder, parent: BookmarkFolder?, completion: @escaping (Bool, Error?) -> Void) { saveFolderCalled = true + capturedFolder = folder + capturedParentFolder = parent completion(saveFolderSuccess, saveFolderError) } @@ -92,11 +101,20 @@ public final class BookmarkStoreMock: BookmarkStore { } updateBookmarkCalled = true + capturedBookmark = bookmark } var updateFolderCalled = false func update(folder: BookmarkFolder) { updateFolderCalled = true + capturedFolder = folder + } + + var updateFolderAndMoveToParentCalled = false + func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) { + updateFolderAndMoveToParentCalled = true + capturedFolder = folder + capturedParentFolderType = parent } var addChildCalled = false @@ -122,8 +140,11 @@ public final class BookmarkStoreMock: BookmarkStore { } var moveObjectUUIDCalled = false + var capturedObjectUUIDs: [String]? func move(objectUUIDs: [String], toIndex: Int?, withinParentFolder: ParentFolderType, completion: @escaping (Error?) -> Void) { moveObjectUUIDCalled = true + capturedObjectUUIDs = objectUUIDs + capturedParentFolderType = withinParentFolder } var updateFavoriteIndexCalled = false @@ -135,4 +156,17 @@ public final class BookmarkStoreMock: BookmarkStore { func handleFavoritesAfterDisablingSync() {} } +extension ParentFolderType: Equatable { + public static func == (lhs: Self, rhs: Self) -> Bool { + switch (lhs, rhs) { + case (.root, .root): + return true + case (.parent(let lhsValue), .parent(let rhsValue)): + return lhsValue == rhsValue + default: + return false + } + } +} + #endif diff --git a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift index 49f694be2b..f79e265d96 100644 --- a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift +++ b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift @@ -18,10 +18,19 @@ import AppKit -struct ContextualMenu { +enum ContextualMenu { + + static func menu(for objects: [Any]?) -> NSMenu? { + menu(for: objects, target: nil) + } + + /// Creates an instance of NSMenu for the specified Objects and target. + /// - Parameters: + /// - objects: The objects to create the menu for. + /// - target: The target to associate to the `NSMenuItem` + /// - Returns: An instance of NSMenu or nil if `objects` is not a `Bookmark` or a `Folder`. + static func menu(for objects: [Any]?, target: AnyObject?) -> NSMenu? { - // Not all contexts support an editing option for bookmarks. The option is displayed by default, but `includeBookmarkEditMenu` can disable it. - static func menu(for objects: [Any]?, includeBookmarkEditMenu: Bool = true) -> NSMenu? { guard let objects = objects, objects.count > 0 else { return menuForNoSelection() } @@ -31,140 +40,183 @@ struct ContextualMenu { } let node = objects.first as? BookmarkNode - let object = node?.representedObject ?? objects.first as? BaseBookmarkEntity + let object = node?.representedObject as? BaseBookmarkEntity ?? objects.first as? BaseBookmarkEntity + let parentFolder = node?.parent?.representedObject as? BookmarkFolder - if let bookmark = object as? Bookmark { - return menu(for: bookmark, includeBookmarkEditMenu: includeBookmarkEditMenu) - } else if let folder = object as? BookmarkFolder { - return menu(for: folder) - } else { - return nil - } - } + guard let object else { return nil } - // MARK: - Single Item Menu Creation + let menu = menu(for: object, parentFolder: parentFolder) - private static func menuForNoSelection() -> NSMenu { - let menu = NSMenu(title: "") - menu.addItem(newFolderMenuItem()) + menu?.items.forEach { item in + item.target = target + } return menu } - private static func menu(for bookmark: Bookmark, includeBookmarkEditMenu: Bool) -> NSMenu { - let menu = NSMenu(title: "") + /// Creates an instance of NSMenu for the specified `BaseBookmarkEntity`and parent `BookmarkFolder`. + /// + /// - Parameters: + /// - entity: The bookmark entity to create the menu for. + /// - parentFolder: An optional `BookmarkFolder`. + /// - Returns: An instance of NSMenu or nil if `entity` is not a `Bookmark` or a `Folder`. + static func menu(for entity: BaseBookmarkEntity, parentFolder: BookmarkFolder?) -> NSMenu? { + let menu: NSMenu? + if let bookmark = entity as? Bookmark { + menu = self.menu(for: bookmark, parent: parentFolder, isFavorite: bookmark.isFavorite) + } else if let folder = entity as? BookmarkFolder { + // When the user edits a folder we need to show the parent in the folder picker. Folders directly child of PseudoFolder `Bookmarks` have nil parent because their parent is not an instance of `BookmarkFolder` + menu = self.menu(for: folder, parent: parentFolder) + } else { + menu = nil + } - menu.addItem(openBookmarkInNewTabMenuItem(bookmark: bookmark)) - menu.addItem(openBookmarkInNewWindowMenuItem(bookmark: bookmark)) - menu.addItem(NSMenuItem.separator()) + return menu + } - menu.addItem(addBookmarkToFavoritesMenuItem(bookmark: bookmark)) + /// Returns an array of `NSMenuItem` to show for a bookmark. + /// + /// - Important: The `representedObject` for the `NSMenuItem` returned is `nil`. This function is meant to be used for scenarios where the model is not available at the time of creating the `NSMenu` such as from the BookmarkBarCollectionViewItem. + /// + /// - Parameter isFavorite: True if the menu item should contain a menu item to add to favorites. False to contain a menu item to remove from favorites. + /// - Returns: An array of `NSMenuItem` + static func bookmarkMenuItems(isFavorite: Bool) -> [NSMenuItem] { + menuItems(for: nil, parent: nil, isFavorite: isFavorite) + } - if includeBookmarkEditMenu { - menu.addItem(editBookmarkMenuItem(bookmark: bookmark)) - } + /// Returns an array of `NSMenuItem` to show for a bookmark folder. + /// + /// - Important: The `representedObject` for the `NSMenuItem` returned is `nil`. This function is meant to be used for scenarios where the model is not available at the time of creating the `NSMenu` such as from the BookmarkBarCollectionViewItem. + /// + /// - Returns: An array of `NSMenuItem` + static func folderMenuItems() -> [NSMenuItem] { + menuItems(for: nil, parent: nil) + } - menu.addItem(NSMenuItem.separator()) +} - menu.addItem(copyBookmarkMenuItem(bookmark: bookmark)) - menu.addItem(deleteBookmarkMenuItem(bookmark: bookmark)) - menu.addItem(NSMenuItem.separator()) +private extension ContextualMenu { - menu.addItem(newFolderMenuItem()) + static func menuForNoSelection() -> NSMenu { + NSMenu(items: [addFolderMenuItem(folder: nil)]) + } - return menu + static func menu(for bookmark: Bookmark?, parent: BookmarkFolder?, isFavorite: Bool) -> NSMenu { + NSMenu(items: menuItems(for: bookmark, parent: parent, isFavorite: isFavorite)) } - private static func menu(for folder: BookmarkFolder) -> NSMenu { - let menu = NSMenu(title: "") + static func menu(for folder: BookmarkFolder?, parent: BookmarkFolder?) -> NSMenu { + NSMenu(items: menuItems(for: folder, parent: parent)) + } - menu.addItem(renameFolderMenuItem(folder: folder)) - menu.addItem(deleteFolderMenuItem(folder: folder)) - menu.addItem(NSMenuItem.separator()) + static func menuItems(for bookmark: Bookmark?, parent: BookmarkFolder?, isFavorite: Bool) -> [NSMenuItem] { + [ + openBookmarkInNewTabMenuItem(bookmark: bookmark), + openBookmarkInNewWindowMenuItem(bookmark: bookmark), + NSMenuItem.separator(), + addBookmarkToFavoritesMenuItem(isFavorite: isFavorite, bookmark: bookmark), + NSMenuItem.separator(), + editBookmarkMenuItem(bookmark: bookmark), + copyBookmarkMenuItem(bookmark: bookmark), + deleteBookmarkMenuItem(bookmark: bookmark), + moveToEndMenuItem(entity: bookmark, parent: parent), + NSMenuItem.separator(), + addFolderMenuItem(folder: parent), + manageBookmarksMenuItem(), + ] + } - menu.addItem(openInNewTabsMenuItem(folder: folder)) + static func menuItems(for folder: BookmarkFolder?, parent: BookmarkFolder?) -> [NSMenuItem] { + [ + openInNewTabsMenuItem(folder: folder), + openAllInNewWindowMenuItem(folder: folder), + NSMenuItem.separator(), + editFolderMenuItem(folder: folder, parent: parent), + deleteFolderMenuItem(folder: folder), + moveToEndMenuItem(entity: folder, parent: parent), + NSMenuItem.separator(), + addFolderMenuItem(folder: folder), + manageBookmarksMenuItem(), + ] + } - return menu + static func menuItem(_ title: String, _ action: Selector, _ representedObject: Any? = nil) -> NSMenuItem { + let item = NSMenuItem(title: title, action: action, keyEquivalent: "") + item.representedObject = representedObject + return item } - // MARK: - Menu Items + // MARK: - Single Bookmark Menu Items - static func newFolderMenuItem() -> NSMenuItem { - return menuItem(UserText.newFolder, #selector(FolderMenuItemSelectors.newFolder(_:))) + static func openBookmarkInNewTabMenuItem(bookmark: Bookmark?) -> NSMenuItem { + menuItem(UserText.openInNewTab, #selector(BookmarkMenuItemSelectors.openBookmarkInNewTab(_:)), bookmark) } - static func renameFolderMenuItem(folder: BookmarkFolder) -> NSMenuItem { - return menuItem(UserText.renameFolder, #selector(FolderMenuItemSelectors.renameFolder(_:)), folder) + static func openBookmarkInNewWindowMenuItem(bookmark: Bookmark?) -> NSMenuItem { + menuItem(UserText.openInNewWindow, #selector(BookmarkMenuItemSelectors.openBookmarkInNewWindow(_:)), bookmark) } - static func deleteFolderMenuItem(folder: BookmarkFolder) -> NSMenuItem { - return menuItem(UserText.deleteFolder, #selector(FolderMenuItemSelectors.deleteFolder(_:)), folder) + static func manageBookmarksMenuItem() -> NSMenuItem { + menuItem(UserText.bookmarksManageBookmarks, #selector(BookmarkMenuItemSelectors.manageBookmarks(_:))) } - static func openInNewTabsMenuItem(folder: BookmarkFolder) -> NSMenuItem { - return menuItem(UserText.bookmarksOpenInNewTabs, #selector(FolderMenuItemSelectors.openInNewTabs(_:)), folder) + static func addBookmarkToFavoritesMenuItem(isFavorite: Bool, bookmark: Bookmark?) -> NSMenuItem { + let title = isFavorite ? UserText.removeFromFavorites : UserText.addToFavorites + return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmark) } - static func openBookmarksInNewTabsMenuItem(bookmarks: [Bookmark]) -> NSMenuItem { - return menuItem(UserText.bookmarksOpenInNewTabs, #selector(FolderMenuItemSelectors.openInNewTabs(_:)), bookmarks) + static func addBookmarksToFavoritesMenuItem(bookmarks: [Bookmark], allFavorites: Bool) -> NSMenuItem { + let title = allFavorites ? UserText.removeFromFavorites : UserText.addToFavorites + return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmarks) } - static func openBookmarkInNewTabMenuItem(bookmark: Bookmark) -> NSMenuItem { - return menuItem(UserText.openInNewTab, #selector(BookmarkMenuItemSelectors.openBookmarkInNewTab(_:)), bookmark) + static func editBookmarkMenuItem(bookmark: Bookmark?) -> NSMenuItem { + menuItem(UserText.editBookmark, #selector(BookmarkMenuItemSelectors.editBookmark(_:)), bookmark) } - static func openBookmarkInNewWindowMenuItem(bookmark: Bookmark) -> NSMenuItem { - return menuItem(UserText.openInNewWindow, #selector(BookmarkMenuItemSelectors.openBookmarkInNewWindow(_:)), bookmark) + static func copyBookmarkMenuItem(bookmark: Bookmark?) -> NSMenuItem { + menuItem(UserText.copy, #selector(BookmarkMenuItemSelectors.copyBookmark(_:)), bookmark) } - static func addBookmarkToFavoritesMenuItem(bookmark: Bookmark) -> NSMenuItem { - let title: String - - if bookmark.isFavorite { - title = UserText.removeFromFavorites - } else { - title = UserText.addToFavorites - } - - return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmark) + static func deleteBookmarkMenuItem(bookmark: Bookmark?) -> NSMenuItem { + menuItem(UserText.bookmarksBarContextMenuDelete, #selector(BookmarkMenuItemSelectors.deleteBookmark(_:)), bookmark) } - static func addBookmarksToFavoritesMenuItem(bookmarks: [Bookmark], allFavorites: Bool) -> NSMenuItem { - let title: String + static func moveToEndMenuItem(entity: BaseBookmarkEntity?, parent: BookmarkFolder?) -> NSMenuItem { + let bookmarkEntityInfo = entity.flatMap { BookmarkEntityInfo(entity: $0, parent: parent) } + return menuItem(UserText.bookmarksBarContextMenuMoveToEnd, #selector(BookmarkMenuItemSelectors.moveToEnd(_:)), bookmarkEntityInfo) + } - if allFavorites { - title = UserText.removeFromFavorites - } else { - title = UserText.addToFavorites - } + // MARK: - Bookmark Folder Menu Items - return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmarks) + static func openInNewTabsMenuItem(folder: BookmarkFolder?) -> NSMenuItem { + menuItem(UserText.openAllInNewTabs, #selector(FolderMenuItemSelectors.openInNewTabs(_:)), folder) } - static func editBookmarkMenuItem(bookmark: Bookmark) -> NSMenuItem { - let title = NSLocalizedString("Edit…", comment: "Command") - return menuItem(title, #selector(BookmarkMenuItemSelectors.editBookmark(_:)), bookmark) + static func openAllInNewWindowMenuItem(folder: BookmarkFolder?) -> NSMenuItem { + menuItem(UserText.openAllTabsInNewWindow, #selector(FolderMenuItemSelectors.openAllInNewWindow(_:)), folder) } - static func copyBookmarkMenuItem(bookmark: Bookmark) -> NSMenuItem { - let title = NSLocalizedString("Copy", comment: "Command") - return menuItem(title, #selector(BookmarkMenuItemSelectors.copyBookmark(_:)), bookmark) + static func addFolderMenuItem(folder: BookmarkFolder?) -> NSMenuItem { + menuItem(UserText.addFolder, #selector(FolderMenuItemSelectors.newFolder(_:)), folder) } - static func deleteBookmarkMenuItem(bookmark: Bookmark) -> NSMenuItem { - let title = NSLocalizedString("Delete", comment: "Command") - return menuItem(title, #selector(BookmarkMenuItemSelectors.deleteBookmark(_:)), bookmark) + static func editFolderMenuItem(folder: BookmarkFolder?, parent: BookmarkFolder?) -> NSMenuItem { + let folderEntityInfo = folder.flatMap { BookmarkEntityInfo(entity: $0, parent: parent) } + return menuItem(UserText.editBookmark, #selector(FolderMenuItemSelectors.editFolder(_:)), folderEntityInfo) } - static func menuItem(_ title: String, _ action: Selector, _ representedObject: Any? = nil) -> NSMenuItem { - let item = NSMenuItem(title: title, action: action, keyEquivalent: "") - item.representedObject = representedObject - return item + static func deleteFolderMenuItem(folder: BookmarkFolder?) -> NSMenuItem { + menuItem(UserText.bookmarksBarContextMenuDelete, #selector(FolderMenuItemSelectors.deleteFolder(_:)), folder) } // MARK: - Multi-Item Menu Creation - private static func menu(for entities: [BaseBookmarkEntity]) -> NSMenu { + static func openBookmarksInNewTabsMenuItem(bookmarks: [Bookmark]) -> NSMenuItem { + menuItem(UserText.bookmarksOpenInNewTabs, #selector(FolderMenuItemSelectors.openInNewTabs(_:)), bookmarks) + } + + static func menu(for entities: [BaseBookmarkEntity]) -> NSMenu { let menu = NSMenu(title: "") var menuItems: [NSMenuItem] = [] @@ -185,8 +237,7 @@ struct ContextualMenu { menuItems.append(NSMenuItem.separator()) } - let title = NSLocalizedString("Delete", comment: "Command") - let deleteItem = NSMenuItem(title: title, action: #selector(BookmarkMenuItemSelectors.deleteEntities(_:)), keyEquivalent: "") + let deleteItem = NSMenuItem(title: UserText.bookmarksBarContextMenuDelete, action: #selector(BookmarkMenuItemSelectors.deleteEntities(_:)), keyEquivalent: "") deleteItem.representedObject = entities menuItems.append(deleteItem) diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index 307d16cc5d..e2be507a44 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -391,18 +391,27 @@ final class LocalBookmarkStore: BookmarkStore { } func update(folder: BookmarkFolder) { - do { - _ = try applyChangesAndSave(changes: { context in - let folderFetchRequest = BaseBookmarkEntity.singleEntity(with: folder.id) - let folderFetchRequestResults = try? context.fetch(folderFetchRequest) - - guard let bookmarkFolderMO = folderFetchRequestResults?.first else { - assertionFailure("LocalBookmarkStore: Failed to get BookmarkEntity from the context") - throw BookmarkStoreError.missingEntity + _ = try applyChangesAndSave(changes: { [weak self] context in + guard let self = self else { + throw BookmarkStoreError.storeDeallocated } + try update(folder: folder, in: context) + }) + } catch { + let error = error as NSError + commonOnSaveErrorHandler(error) + } + } - bookmarkFolderMO.update(with: folder) + func update(folder: BookmarkFolder, andMoveToParent parent: ParentFolderType) { + do { + _ = try applyChangesAndSave(changes: { [weak self] context in + guard let self = self else { + throw BookmarkStoreError.storeDeallocated + } + let folderEntity = try update(folder: folder, in: context) + try move(entities: [folderEntity], toIndex: nil, withinParentFolderType: parent, in: context) }) } catch { let error = error as NSError @@ -566,10 +575,6 @@ final class LocalBookmarkStore: BookmarkStore { throw BookmarkStoreError.storeDeallocated } - guard let rootFolder = self.bookmarksRoot(in: context) else { - throw BookmarkStoreError.missingRoot - } - // Guarantee that bookmarks are fetched in the same order as the UUIDs. In the future, this should fetch all objects at once with a // batch fetch request and have them sorted in the correct order. let bookmarkManagedObjects: [BookmarkEntity] = objectUUIDs.compactMap { uuid in @@ -577,28 +582,8 @@ final class LocalBookmarkStore: BookmarkStore { return (try? context.fetch(entityFetchRequest))?.first } - let newParentFolder: BookmarkEntity - - switch type { - case .root: newParentFolder = rootFolder - case .parent(let newParentUUID): - let bookmarksFetchRequest = BaseBookmarkEntity.singleEntity(with: newParentUUID) + try move(entities: bookmarkManagedObjects, toIndex: index, withinParentFolderType: type, in: context) - if let fetchedParent = try context.fetch(bookmarksFetchRequest).first, fetchedParent.isFolder { - newParentFolder = fetchedParent - } else { - throw BookmarkStoreError.missingEntity - } - } - - if let index = index, index < newParentFolder.childrenArray.count { - self.move(entities: bookmarkManagedObjects, to: index, within: newParentFolder) - } else { - for bookmarkManagedObject in bookmarkManagedObjects { - bookmarkManagedObject.parent = nil - newParentFolder.addToChildren(bookmarkManagedObject) - } - } }, onError: { [weak self] error in self?.commonOnSaveErrorHandler(error) DispatchQueue.main.async { completion(error) } @@ -996,6 +981,53 @@ final class LocalBookmarkStore: BookmarkStore { } +private extension LocalBookmarkStore { + + @discardableResult + func update(folder: BookmarkFolder, in context: NSManagedObjectContext) throws -> BookmarkEntity { + let folderFetchRequest = BaseBookmarkEntity.singleEntity(with: folder.id) + let folderFetchRequestResults = try? context.fetch(folderFetchRequest) + + guard let bookmarkFolderMO = folderFetchRequestResults?.first else { + assertionFailure("LocalBookmarkStore: Failed to get BookmarkEntity from the context") + throw BookmarkStoreError.missingEntity + } + + bookmarkFolderMO.update(with: folder) + return bookmarkFolderMO + } + + func move(entities: [BookmarkEntity], toIndex index: Int?, withinParentFolderType type: ParentFolderType, in context: NSManagedObjectContext) throws { + guard let rootFolder = bookmarksRoot(in: context) else { + throw BookmarkStoreError.missingRoot + } + + let newParentFolder: BookmarkEntity + + switch type { + case .root: newParentFolder = rootFolder + case .parent(let newParentUUID): + let bookmarksFetchRequest = BaseBookmarkEntity.singleEntity(with: newParentUUID) + + if let fetchedParent = try context.fetch(bookmarksFetchRequest).first, fetchedParent.isFolder { + newParentFolder = fetchedParent + } else { + throw BookmarkStoreError.missingEntity + } + } + + if let index = index, index < newParentFolder.childrenArray.count { + self.move(entities: entities, to: index, within: newParentFolder) + } else { + for bookmarkManagedObject in entities { + bookmarkManagedObject.parent = nil + newParentFolder.addToChildren(bookmarkManagedObject) + } + } + } + +} + extension LocalBookmarkStore.BookmarkStoreError: CustomNSError { var errorCode: Int { diff --git a/DuckDuckGo/Bookmarks/Services/MenuItemSelectors.swift b/DuckDuckGo/Bookmarks/Services/MenuItemSelectors.swift index 44114fc9d8..393b22de8e 100644 --- a/DuckDuckGo/Bookmarks/Services/MenuItemSelectors.swift +++ b/DuckDuckGo/Bookmarks/Services/MenuItemSelectors.swift @@ -18,7 +18,13 @@ import AppKit -@objc protocol BookmarkMenuItemSelectors { +@objc protocol BookmarksMenuItemSelectors { + func newFolder(_ sender: NSMenuItem) + func moveToEnd(_ sender: NSMenuItem) + @objc optional func manageBookmarks(_ sender: NSMenuItem) +} + +@objc protocol BookmarkMenuItemSelectors: BookmarksMenuItemSelectors { func openBookmarkInNewTab(_ sender: NSMenuItem) func openBookmarkInNewWindow(_ sender: NSMenuItem) @@ -30,11 +36,11 @@ import AppKit } -@objc protocol FolderMenuItemSelectors { +@objc protocol FolderMenuItemSelectors: BookmarksMenuItemSelectors { - func newFolder(_ sender: NSMenuItem) - func renameFolder(_ sender: NSMenuItem) + func editFolder(_ sender: NSMenuItem) func deleteFolder(_ sender: NSMenuItem) func openInNewTabs(_ sender: NSMenuItem) + func openAllInNewWindow(_ sender: NSMenuItem) } diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift deleted file mode 100644 index 31cc06dc04..0000000000 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderModalView.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// AddBookmarkFolderModalView.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import SwiftUI - -struct AddBookmarkFolderModalView: ModalView { - - @State var model: AddBookmarkFolderModalViewModel = .init() - @Environment(\.dismiss) private var dismiss - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text(model.title) - .fontWeight(.semibold) - - HStack(spacing: 16) { - Text(UserText.newBookmarkDialogBookmarkNameTitle) - .frame(height: 22) - - TextField("", text: $model.folderName) - .accessibilityIdentifier("Title Text Field") - .textFieldStyle(.roundedBorder) - .disableAutocorrection(true) - } - .padding(.bottom, 4) - - HStack { - Spacer() - - Button(UserText.cancel) { - model.cancel(dismiss: dismiss.callAsFunction) - } - .keyboardShortcut(.cancelAction) - - Button(model.addButtonTitle) { - model.addFolder(dismiss: dismiss.callAsFunction) - } - .keyboardShortcut(.defaultAction) - .disabled(model.isAddButtonDisabled) - - } - } - .font(.system(size: 13)) - .padding() - .frame(width: 450, height: 131) - } - -} - -#Preview { - AddBookmarkFolderModalView() -} diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift index a0ab50c89a..f15465df36 100644 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkFolderPopoverView.swift @@ -23,59 +23,27 @@ struct AddBookmarkFolderPopoverView: ModalView { @ObservedObject var model: AddBookmarkFolderPopoverViewModel var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text(UserText.newFolder) - .bold() - - VStack(alignment: .leading, spacing: 7) { - Text("Location:", comment: "Add Folder popover: parent folder picker title") - - BookmarkFolderPicker(folders: model.folders, selectedFolder: $model.parent) - .accessibilityIdentifier("bookmark.folder.folder.dropdown") - .disabled(model.isDisabled) - } - - VStack(alignment: .leading, spacing: 7) { - Text(UserText.newFolderDialogFolderNameTitle) - - TextField("", text: $model.folderName) - .focusedOnAppear() - .accessibilityIdentifier("bookmark.folder.name.textfield") - .textFieldStyle(RoundedBorderTextFieldStyle()) - .disabled(model.isDisabled) - } - .padding(.bottom, 16) - - HStack { - Spacer() - - Button(action: { - model.cancel() - }) { - Text(UserText.cancel) - } - .accessibilityIdentifier("bookmark.add.cancel.button") - .disabled(model.isDisabled) - - Button(action: { - model.addFolder() - }) { - Text("Add Folder", comment: "Add Folder popover: Create folder button") - } - .keyboardShortcut(.defaultAction) - .accessibilityIdentifier("bookmark.add.add.folder.button") - .disabled(model.isAddFolderButtonDisabled || model.isDisabled) - } - } + AddEditBookmarkFolderView( + title: model.title, + buttonsState: .expanded, + folders: model.folders, + folderName: $model.folderName, + selectedFolder: $model.parent, + cancelActionTitle: model.cancelActionTitle, + isCancelActionDisabled: model.isCancelActionDisabled, + cancelAction: { _ in model.cancel() }, + defaultActionTitle: model.defaultActionTitle, + isDefaultActionDisabled: model.isDefaultActionButtonDisabled, + defaultAction: { _ in model.addFolder() } + ) + .padding(.vertical, 16.0) .font(.system(size: 13)) - .padding() - .frame(width: 300, height: 229) - .background(Color(.popoverBackground)) + .frame(width: 320) } } #if DEBUG -#Preview { +#Preview("Add Folder - Light") { let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ BookmarkFolder(id: "1", title: "Folder 1", children: [ BookmarkFolder(id: "2", title: "Nested Folder with a name that in theory won‘t fit into the picker", children: [ @@ -94,5 +62,17 @@ struct AddBookmarkFolderPopoverView: ModalView { return AddBookmarkFolderPopoverView(model: AddBookmarkFolderPopoverViewModel(bookmarkManager: bkman) { print("CompletionHandler:", $0?.title ?? "") }) + .preferredColorScheme(.light) +} + +#Preview("Add Folder - Dark") { + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [])) + bkman.loadBookmarks() + customAssertionFailure = { _, _, _ in } + + return AddBookmarkFolderPopoverView(model: AddBookmarkFolderPopoverViewModel(bookmarkManager: bkman) { + print("CompletionHandler:", $0?.title ?? "") + }) + .preferredColorScheme(.dark) } #endif diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkModalView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkModalView.swift deleted file mode 100644 index 56941737fd..0000000000 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkModalView.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// AddBookmarkModalView.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import AppKit -import SwiftUI - -struct AddBookmarkModalView: ModalView { - - @State private(set) var model: AddBookmarkModalViewModel - @Environment(\.dismiss) private var dismiss - - var body: some View { - VStack(alignment: .leading, spacing: 16) { - Text(model.title) - .fontWeight(.semibold) - - HStack(spacing: 16) { - VStack(alignment: .leading) { - Text("Title:", comment: "Add Bookmark dialog bookmark title field heading") - .frame(height: 22) - Text("Address:", comment: "Add Bookmark dialog bookmark url field heading") - .frame(height: 22) - } - - VStack { - TextField("", text: $model.bookmarkTitle) - .accessibilityIdentifier("Title Text Field") - .textFieldStyle(.roundedBorder) - TextField("", text: $model.bookmarkAddress) - .accessibilityIdentifier("URL Text Field") - .textFieldStyle(.roundedBorder) - .disableAutocorrection(true) - } - } - .padding(.bottom, 4) - - HStack { - Spacer() - - Button(UserText.cancel) { - model.cancel(dismiss: dismiss.callAsFunction) - } - .keyboardShortcut(.cancelAction) - - Button(model.addButtonTitle) { - model.addOrSave(dismiss: dismiss.callAsFunction) - } - .keyboardShortcut(.defaultAction) - .disabled(model.isAddButtonDisabled) - - } - } - .font(.system(size: 13)) - .padding() - .frame(width: 437, height: 164) - } - -} - -#Preview { - AddBookmarkModalView(model: AddBookmarkModalViewModel()) -} diff --git a/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift index c8d9cadcbd..fba84b44bc 100644 --- a/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift +++ b/DuckDuckGo/Bookmarks/View/AddBookmarkPopoverView.swift @@ -38,82 +38,32 @@ struct AddBookmarkPopoverView: View { @MainActor private var addBookmarkView: some View { - VStack(alignment: .leading, spacing: 19) { - Text("Bookmark Added", comment: "Bookmark Added popover title") - .fontWeight(.bold) - .padding(.bottom, 4) - - VStack(alignment: .leading, spacing: 10) { - TextField("", text: $model.bookmarkTitle) - .focusedOnAppear() - .accessibilityIdentifier("bookmark.add.name.textfield") - .textFieldStyle(RoundedBorderTextFieldStyle()) - .font(.system(size: 14)) - - HStack { - BookmarkFolderPicker(folders: model.folders, - selectedFolder: $model.selectedFolder) - .accessibilityIdentifier("bookmark.add.folder.dropdown") - - Button { - model.addFolderButtonAction() - } label: { - Image(.addFolder) - } - .accessibilityIdentifier("bookmark.add.new.folder.button") - .buttonStyle(StandardButtonStyle()) - } - } - - Divider() - - Button { - model.favoritesButtonAction() - } label: { - HStack(spacing: 8) { - if model.bookmark.isFavorite { - Image(.favoriteFilled) - Text(UserText.removeFromFavorites) - } else { - Image(.favorite) - Text(UserText.addToFavorites) - } - } - } - .accessibilityIdentifier("bookmark.add.add.to.favorites.button") - .buttonStyle(.borderless) - .foregroundColor(Color.button) - - HStack { - Spacer() - - Button { - model.removeButtonAction(dismiss: dismiss.callAsFunction) - } label: { - Text("Remove", comment: "Remove bookmark button title") - } - .accessibilityIdentifier("bookmark.add.remove.button") - - Button { - model.doneButtonAction(dismiss: dismiss.callAsFunction) - } label: { - Text(UserText.done) - } - .keyboardShortcut(.defaultAction) - .accessibilityIdentifier("bookmark.add.done.button") - } - - } + AddEditBookmarkView( + title: UserText.Bookmarks.Dialog.Title.addedBookmark, + buttonsState: .expanded, + bookmarkName: $model.bookmarkTitle, + bookmarkURLPath: nil, + isBookmarkFavorite: $model.isBookmarkFavorite, + folders: model.folders, + selectedFolder: $model.selectedFolder, + isURLFieldHidden: true, + addFolderAction: model.addFolderButtonAction, + otherActionTitle: UserText.remove, + isOtherActionDisabled: false, + otherAction: model.removeButtonAction, + defaultActionTitle: UserText.done, + isDefaultActionDisabled: model.isDefaultActionButtonDisabled, + defaultAction: model.doneButtonAction + ) + .padding(.vertical, 16.0) .font(.system(size: 13)) - .padding(EdgeInsets(top: 19, leading: 19, bottom: 19, trailing: 19)) - .frame(width: 300, height: 229) - .background(Color(.popoverBackground)) + .frame(width: 320) } } #if DEBUG -#Preview { { +#Preview("Bookmark Added - Light") { let bkm = Bookmark(id: "n", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "1") let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ BookmarkFolder(id: "1", title: "Folder with a name that shouldn‘t fit into the picker", children: [ @@ -133,5 +83,16 @@ struct AddBookmarkPopoverView: View { customAssertionFailure = { _, _, _ in } return AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bkm, bookmarkManager: bkman)) -}() } + .preferredColorScheme(.light) +} + +#Preview("Bookmark Added - Dark") { + let bkm = Bookmark(id: "n", url: URL.duckDuckGo.absoluteString, title: "DuckDuckGo", isFavorite: false, parentFolderUUID: "1") + let bkman = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [ + BookmarkFolder(id: "1", title: "Folder with a name that shouldn‘t fit into the picker", children: [])])) + bkman.loadBookmarks() + + return AddBookmarkPopoverView(model: AddBookmarkPopoverViewModel(bookmark: bkm, bookmarkManager: bkman)) + .preferredColorScheme(.dark) +} #endif diff --git a/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift index c0aeab9bec..e7de655e8c 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkFolderPicker.swift @@ -32,7 +32,7 @@ struct BookmarkFolderPicker: View { return popUpButton } content: { - PopupButtonItem(icon: .folder, title: UserText.bookmarks) + PopupButtonItem(icon: .bookmarksFolder, title: UserText.bookmarks) PopupButtonItem.separator() diff --git a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift index 541ad955b6..c405c23f61 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkListViewController.swift @@ -63,6 +63,9 @@ final class BookmarkListViewController: NSViewController { contentMode: .bookmarksAndFolders, bookmarkManager: bookmarkManager, treeController: treeController, + onMenuRequestedAction: { [weak self] cell in + self?.showContextMenu(for: cell) + }, presentFaviconsFetcherOnboarding: { [weak self] in guard let self, let window = self.view.window else { return @@ -334,23 +337,18 @@ final class BookmarkListViewController: NSViewController { } @objc func newBookmarkButtonClicked(_ sender: AnyObject) { - delegate?.popover(shouldPreventClosure: true) - AddBookmarkModalView(model: AddBookmarkModalViewModel(currentTabWebsite: currentTabWebsite) { [weak delegate] _ in - delegate?.popover(shouldPreventClosure: false) - }).show(in: parent?.view.window) + let view = BookmarksDialogViewFactory.makeAddBookmarkView(currentTab: currentTabWebsite) + showDialog(view: view) } @objc func newFolderButtonClicked(_ sender: AnyObject) { - delegate?.popover(shouldPreventClosure: true) - AddBookmarkFolderModalView() - .show(in: parent?.view.window) { [weak delegate] in - delegate?.popover(shouldPreventClosure: false) - } + let parentFolder = sender.representedObject as? BookmarkFolder + let view = BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: parentFolder) + showDialog(view: view) } @objc func openManagementInterface(_ sender: NSButton) { - WindowControllersManager.shared.showBookmarksTab() - delegate?.popoverShouldClose(self) + showManageBookmarks() } @objc func handleClick(_ sender: NSOutlineView) { @@ -425,6 +423,35 @@ final class BookmarkListViewController: NSViewController { outlineView.selectRowIndexes(indexes, byExtendingSelection: false) } + private func showContextMenu(for cell: BookmarkOutlineCellView) { + let row = outlineView.row(for: cell) + guard + let item = outlineView.item(atRow: row), + let contextMenu = ContextualMenu.menu(for: [item], target: self) + else { + return + } + + contextMenu.popUpAtMouseLocation(in: view) + } + +} + +private extension BookmarkListViewController { + + func showDialog(view: any ModalView) { + delegate?.popover(shouldPreventClosure: true) + + view.show(in: parent?.view.window) { [weak delegate] in + delegate?.popover(shouldPreventClosure: false) + } + } + + func showManageBookmarks() { + WindowControllersManager.shared.showBookmarksTab() + delegate?.popoverShouldClose(self) + } + } // MARK: - Menu Item Selectors @@ -439,11 +466,11 @@ extension BookmarkListViewController: NSMenuDelegate { } if outlineView.selectedRowIndexes.contains(row) { - return ContextualMenu.menu(for: outlineView.selectedItems, includeBookmarkEditMenu: false) + return ContextualMenu.menu(for: outlineView.selectedItems) } if let item = outlineView.item(atRow: row) { - return ContextualMenu.menu(for: [item], includeBookmarkEditMenu: false) + return ContextualMenu.menu(for: [item]) } else { return nil } @@ -498,7 +525,13 @@ extension BookmarkListViewController: BookmarkMenuItemSelectors { } func editBookmark(_ sender: NSMenuItem) { - // Unsupported in the list view for the initial release. + guard let bookmark = sender.representedObject as? Bookmark else { + assertionFailure("Failed to retrieve Bookmark from Edit Bookmark context menu item") + return + } + + let view = BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark) + showDialog(view: view) } func copyBookmark(_ sender: NSMenuItem) { @@ -527,6 +560,20 @@ extension BookmarkListViewController: BookmarkMenuItemSelectors { bookmarkManager.remove(objectsWithUUIDs: uuids) } + func manageBookmarks(_ sender: NSMenuItem) { + showManageBookmarks() + } + + func moveToEnd(_ sender: NSMenuItem) { + guard let bookmarkEntity = sender.representedObject as? BookmarksEntityIdentifiable else { + assertionFailure("Failed to cast menu item's represented object to BookmarkEntity") + return + } + + let parentFolderType: ParentFolderType = bookmarkEntity.parentId.flatMap { .parent(uuid: $0) } ?? .root + bookmarkManager.move(objectUUIDs: [bookmarkEntity.entityId], toIndex: nil, withinParentFolder: parentFolderType) { _ in } + } + } extension BookmarkListViewController: FolderMenuItemSelectors { @@ -535,18 +582,16 @@ extension BookmarkListViewController: FolderMenuItemSelectors { newFolderButtonClicked(sender) } - func renameFolder(_ sender: NSMenuItem) { - guard let folder = sender.representedObject as? BookmarkFolder else { - assertionFailure("Failed to retrieve Bookmark from Rename Folder context menu item") + func editFolder(_ sender: NSMenuItem) { + guard let bookmarkEntityInfo = sender.representedObject as? BookmarkEntityInfo, + let folder = bookmarkEntityInfo.entity as? BookmarkFolder + else { + assertionFailure("Failed to retrieve Bookmark from Edit Folder context menu item") return } - delegate?.popover(shouldPreventClosure: true) - - AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(folder: folder)) - .show(in: parent?.view.window) { [weak delegate] in - delegate?.popover(shouldPreventClosure: false) - } + let view = BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: folder, parentFolder: bookmarkEntityInfo.parent) + showDialog(view: view) } func deleteFolder(_ sender: NSMenuItem) { @@ -560,15 +605,28 @@ extension BookmarkListViewController: FolderMenuItemSelectors { func openInNewTabs(_ sender: NSMenuItem) { guard let tabCollection = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, - let children = (sender.representedObject as? BookmarkFolder)?.children else { - assertionFailure("Cannot open in new tabs") + let folder = sender.representedObject as? BookmarkFolder + else { + assertionFailure("Cannot open all in new tabs") return } - let tabs = children.compactMap { ($0 as? Bookmark)?.urlObject }.map { Tab(content: .url($0, source: .bookmark), shouldLoadInBackground: true, burnerMode: tabCollection.burnerMode) } + let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) tabCollection.append(tabs: tabs) } + func openAllInNewWindow(_ sender: NSMenuItem) { + guard let tabCollection = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, + let folder = sender.representedObject as? BookmarkFolder + else { + assertionFailure("Cannot open all in new window") + return + } + + let newTabCollection = TabCollection.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) + WindowsManager.openNewWindow(with: newTabCollection, isBurner: tabCollection.isBurner) + } + } // MARK: - BookmarkListPopover diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift index a9b03ede97..ecc643b33d 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementDetailViewController.swift @@ -32,12 +32,10 @@ private struct EditedBookmarkMetadata { final class BookmarkManagementDetailViewController: NSViewController, NSMenuItemValidation { - fileprivate enum Constants { - static let animationSpeed: TimeInterval = 0.3 - } - + private let toolbarButtonsStackView = NSStackView() private lazy var newBookmarkButton = MouseOverButton(title: " " + UserText.newBookmark, target: self, action: #selector(presentAddBookmarkModal)) private lazy var newFolderButton = MouseOverButton(title: " " + UserText.newFolder, target: self, action: #selector(presentAddFolderModal)) + private lazy var deleteItemsButton = MouseOverButton(title: " " + UserText.bookmarksBarContextMenuDelete, target: self, action: #selector(delete)) private lazy var separator = NSBox() private lazy var scrollView = NSScrollView() @@ -54,32 +52,10 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem private let bookmarkManager: BookmarkManager private var selectionState: BookmarkManagementSidebarViewController.SelectionState = .empty { didSet { - editingBookmarkIndex = nil reloadData() } } - private var isEditing: Bool { - return editingBookmarkIndex != nil - } - - private var editingBookmarkIndex: EditedBookmarkMetadata? { - didSet { - NSAnimationContext.runAnimationGroup { context in - context.allowsImplicitAnimation = true - context.duration = Constants.animationSpeed - - NSAppearance.withAppAppearance { - if editingBookmarkIndex != nil { - view.animator().layer?.backgroundColor = NSColor.backgroundSecondary.cgColor - } else { - view.animator().layer?.backgroundColor = NSColor.bookmarkPageBackground.cgColor - } - } - } - } - } - func update(selectionState: BookmarkManagementSidebarViewController.SelectionState) { self.selectionState = selectionState } @@ -101,34 +77,16 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem view.addSubview(separator) view.addSubview(scrollView) view.addSubview(emptyState) - view.addSubview(newBookmarkButton) - view.addSubview(newFolderButton) - - newBookmarkButton.bezelStyle = .shadowlessSquare - newBookmarkButton.cornerRadius = 4 - newBookmarkButton.normalTintColor = .button - newBookmarkButton.mouseDownColor = .buttonMouseDown - newBookmarkButton.mouseOverColor = .buttonMouseOver - newBookmarkButton.imageHugsTitle = true - newBookmarkButton.setContentHuggingPriority(.defaultHigh, for: .vertical) - newBookmarkButton.translatesAutoresizingMaskIntoConstraints = false - newBookmarkButton.alignment = .center - newBookmarkButton.font = .systemFont(ofSize: 13) - newBookmarkButton.image = .addBookmark - newBookmarkButton.imagePosition = .imageLeading - - newFolderButton.bezelStyle = .shadowlessSquare - newFolderButton.cornerRadius = 4 - newFolderButton.normalTintColor = .button - newFolderButton.mouseDownColor = .buttonMouseDown - newFolderButton.mouseOverColor = .buttonMouseOver - newFolderButton.imageHugsTitle = true - newFolderButton.setContentHuggingPriority(.defaultHigh, for: .vertical) - newFolderButton.translatesAutoresizingMaskIntoConstraints = false - newFolderButton.alignment = .center - newFolderButton.font = .systemFont(ofSize: 13) - newFolderButton.image = .addFolder - newFolderButton.imagePosition = .imageLeading + view.addSubview(toolbarButtonsStackView) + toolbarButtonsStackView.addArrangedSubview(newBookmarkButton) + toolbarButtonsStackView.addArrangedSubview(newFolderButton) + toolbarButtonsStackView.addArrangedSubview(deleteItemsButton) + toolbarButtonsStackView.translatesAutoresizingMaskIntoConstraints = false + toolbarButtonsStackView.distribution = .fill + + configureToolbar(button: newBookmarkButton, image: .addBookmark, isHidden: false) + configureToolbar(button: newFolderButton, image: .addFolder, isHidden: false) + configureToolbar(button: deleteItemsButton, image: .trash, isHidden: true) emptyState.addSubview(emptyStateImageView) emptyState.addSubview(emptyStateTitle) @@ -137,32 +95,27 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem emptyState.isHidden = true emptyState.translatesAutoresizingMaskIntoConstraints = false + importButton.translatesAutoresizingMaskIntoConstraints = false - emptyStateTitle.isEditable = false - emptyStateTitle.setContentHuggingPriority(.defaultHigh, for: .vertical) - emptyStateTitle.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) - emptyStateTitle.translatesAutoresizingMaskIntoConstraints = false - emptyStateTitle.alignment = .center - emptyStateTitle.drawsBackground = false - emptyStateTitle.isBordered = false - emptyStateTitle.font = .systemFont(ofSize: 15, weight: .semibold) - emptyStateTitle.textColor = .labelColor - emptyStateTitle.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateTitle, - lineHeight: 1.14, - kern: -0.23) - - emptyStateMessage.isEditable = false - emptyStateMessage.setContentHuggingPriority(.defaultHigh, for: .vertical) - emptyStateMessage.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) - emptyStateMessage.translatesAutoresizingMaskIntoConstraints = false - emptyStateMessage.alignment = .center - emptyStateMessage.drawsBackground = false - emptyStateMessage.isBordered = false - emptyStateMessage.font = .systemFont(ofSize: 13) - emptyStateMessage.textColor = .labelColor - emptyStateMessage.attributedStringValue = NSAttributedString.make(UserText.bookmarksEmptyStateMessage, - lineHeight: 1.05, - kern: -0.08) + configureEmptyState( + label: emptyStateTitle, + font: .systemFont(ofSize: 15, weight: .semibold), + attributedTitle: .make( + UserText.bookmarksEmptyStateTitle, + lineHeight: 1.14, + kern: -0.23 + ) + ) + + configureEmptyState( + label: emptyStateMessage, + font: .systemFont(ofSize: 13), + attributedTitle: .make( + UserText.bookmarksEmptyStateMessage, + lineHeight: 1.05, + kern: -0.08 + ) + ) emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) emptyStateImageView.setContentHuggingPriority(.init(rawValue: 251), for: .vertical) @@ -195,7 +148,6 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem tableView.selectionHighlightStyle = .none tableView.allowsMultipleSelection = true tableView.usesAutomaticRowHeights = true - tableView.action = #selector(handleClick) tableView.doubleAction = #selector(handleDoubleClick) tableView.delegate = self tableView.dataSource = self @@ -209,47 +161,47 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem } private func setupLayout() { - newBookmarkButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48).isActive = true - view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 48).isActive = true - separator.topAnchor.constraint(equalTo: newBookmarkButton.bottomAnchor, constant: 24).isActive = true - emptyState.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 20).isActive = true - scrollView.topAnchor.constraint(equalTo: separator.bottomAnchor).isActive = true - - view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true - view.trailingAnchor.constraint(greaterThanOrEqualTo: newFolderButton.trailingAnchor, constant: 20).isActive = true - view.trailingAnchor.constraint(equalTo: separator.trailingAnchor, constant: 58).isActive = true - newFolderButton.leadingAnchor.constraint(equalTo: newBookmarkButton.trailingAnchor, constant: 16).isActive = true - emptyState.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true - newFolderButton.centerYAnchor.constraint(equalTo: newBookmarkButton.centerYAnchor).isActive = true - separator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 58).isActive = true - newBookmarkButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 32).isActive = true - emptyState.topAnchor.constraint(greaterThanOrEqualTo: separator.bottomAnchor, constant: 8).isActive = true - scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48).isActive = true - emptyState.centerXAnchor.constraint(equalTo: separator.centerXAnchor).isActive = true - - newBookmarkButton.heightAnchor.constraint(equalToConstant: 24).isActive = true - - newFolderButton.heightAnchor.constraint(equalToConstant: 24).isActive = true - - emptyStateMessage.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true + NSLayoutConstraint.activate([ + toolbarButtonsStackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48), + view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 48), + separator.topAnchor.constraint(equalTo: toolbarButtonsStackView.bottomAnchor, constant: 24), + emptyState.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 20), + scrollView.topAnchor.constraint(equalTo: separator.bottomAnchor), - importButton.translatesAutoresizingMaskIntoConstraints = false - importButton.topAnchor.constraint(equalTo: emptyStateMessage.bottomAnchor, constant: 8).isActive = true - emptyState.heightAnchor.constraint(equalToConstant: 218).isActive = true - emptyStateMessage.topAnchor.constraint(equalTo: emptyStateTitle.bottomAnchor, constant: 8).isActive = true - importButton.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true - emptyStateImageView.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true - emptyState.widthAnchor.constraint(equalToConstant: 224).isActive = true - emptyStateImageView.topAnchor.constraint(equalTo: emptyState.topAnchor).isActive = true - emptyStateTitle.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor).isActive = true - emptyStateTitle.topAnchor.constraint(equalTo: emptyStateImageView.bottomAnchor, constant: 8).isActive = true + view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + view.trailingAnchor.constraint(greaterThanOrEqualTo: toolbarButtonsStackView.trailingAnchor, constant: 20), + view.trailingAnchor.constraint(equalTo: separator.trailingAnchor, constant: 58), + emptyState.centerXAnchor.constraint(equalTo: view.centerXAnchor), + separator.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 58), + toolbarButtonsStackView.topAnchor.constraint(equalTo: view.topAnchor, constant: 32), + emptyState.topAnchor.constraint(greaterThanOrEqualTo: separator.bottomAnchor, constant: 8), + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 48), + emptyState.centerXAnchor.constraint(equalTo: separator.centerXAnchor), + + newBookmarkButton.heightAnchor.constraint(equalToConstant: 24), + newFolderButton.heightAnchor.constraint(equalToConstant: 24), + deleteItemsButton.heightAnchor.constraint(equalToConstant: 24), - emptyStateMessage.widthAnchor.constraint(equalToConstant: 192).isActive = true + emptyStateMessage.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor), - emptyStateTitle.widthAnchor.constraint(equalToConstant: 192).isActive = true + importButton.topAnchor.constraint(equalTo: emptyStateMessage.bottomAnchor, constant: 8), + emptyState.heightAnchor.constraint(equalToConstant: 218), + emptyStateMessage.topAnchor.constraint(equalTo: emptyStateTitle.bottomAnchor, constant: 8), + importButton.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor), + emptyStateImageView.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor), + emptyState.widthAnchor.constraint(equalToConstant: 224), + emptyStateImageView.topAnchor.constraint(equalTo: emptyState.topAnchor), + emptyStateTitle.centerXAnchor.constraint(equalTo: emptyState.centerXAnchor), + emptyStateTitle.topAnchor.constraint(equalTo: emptyStateImageView.bottomAnchor, constant: 8), + + emptyStateMessage.widthAnchor.constraint(equalToConstant: 192), + + emptyStateTitle.widthAnchor.constraint(equalToConstant: 192), + + emptyStateImageView.widthAnchor.constraint(equalToConstant: 128), + emptyStateImageView.heightAnchor.constraint(equalToConstant: 96), + ]) - emptyStateImageView.widthAnchor.constraint(equalToConstant: 128).isActive = true - emptyStateImageView.heightAnchor.constraint(equalToConstant: 96).isActive = true } override func viewDidLoad() { @@ -264,15 +216,9 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem override func viewDidDisappear() { super.viewDidDisappear() - editingBookmarkIndex = nil reloadData() } - override func mouseUp(with event: NSEvent) { - // Clicking anywhere outside of the table view should end editing mode for a given cell. - updateEditingState(forRowAt: -1) - } - override func keyDown(with event: NSEvent) { if event.charactersIgnoringModifiers == String(UnicodeScalar(NSDeleteCharacter)!) { deleteSelectedItems() @@ -280,15 +226,13 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem } fileprivate func reloadData() { - guard editingBookmarkIndex == nil else { - // If the table view is editing, the reload will be deferred until after the cell animation has completed. - return - } emptyState.isHidden = !(bookmarkManager.list?.topLevelEntities.isEmpty ?? true) let scrollPosition = tableView.visibleRect.origin tableView.reloadData() tableView.scroll(scrollPosition) + + updateToolbarButtons() } @objc func onImportClicked(_ sender: NSButton) { @@ -306,7 +250,7 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem let index = sender.clickedRow - guard index != -1, editingBookmarkIndex?.index != index, let entity = fetchEntity(at: index) else { + guard index != -1, let entity = fetchEntity(at: index) else { return } @@ -324,21 +268,13 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem } } - @objc func handleClick(_ sender: NSTableView) { - let index = sender.clickedRow - - if index != editingBookmarkIndex?.index { - endEditing() - } - } - @objc func presentAddBookmarkModal(_ sender: Any) { - AddBookmarkModalView(model: AddBookmarkModalViewModel(parent: selectionState.folder)) + BookmarksDialogViewFactory.makeAddBookmarkView(parent: selectionState.folder) .show(in: view.window) } @objc func presentAddFolderModal(_ sender: Any) { - AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(parent: selectionState.folder)) + BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: selectionState.folder) .show(in: view.window) } @@ -354,53 +290,6 @@ final class BookmarkManagementDetailViewController: NSViewController, NSMenuItem return true } - private func endEditing() { - if let editingIndex = editingBookmarkIndex?.index { - self.editingBookmarkIndex = nil - animateEditingState(forRowAt: editingIndex, editing: false) - } - } - - private func updateEditingState(forRowAt index: Int) { - guard index != -1 else { - endEditing() - return - } - - if editingBookmarkIndex?.index == nil || editingBookmarkIndex?.index != index { - endEditing() - } - - if let entity = fetchEntity(at: index) { - editingBookmarkIndex = EditedBookmarkMetadata(uuid: entity.id, index: index) - animateEditingState(forRowAt: index, editing: true) - } else { - assertionFailure("\(#file): Failed to find entity when updating editing state") - } - } - - private func animateEditingState(forRowAt index: Int, editing: Bool, completion: (() -> Void)? = nil) { - if let cell = tableView.view(atColumn: 0, row: index, makeIfNecessary: false) as? BookmarkTableCellView, - let row = tableView.rowView(atRow: index, makeIfNecessary: false) as? BookmarkTableRowView { - - tableView.beginUpdates() - NSAnimationContext.runAnimationGroup { context in - context.allowsImplicitAnimation = true - context.duration = Constants.animationSpeed - context.completionHandler = completion - - cell.editing = editing - row.editing = editing - - row.layoutSubtreeIfNeeded() - cell.layoutSubtreeIfNeeded() - tableView.noteHeightOfRows(withIndexesChanged: IndexSet(arrayLiteral: 0, index)) - } - - tableView.endUpdates() - } - } - private func totalRows() -> Int { switch selectionState { case .empty: @@ -444,12 +333,6 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi let rowView = BookmarkTableRowView() rowView.onSelectionChanged = onSelectionChanged - let entity = fetchEntity(at: row) - - if let uuid = editingBookmarkIndex?.uuid, uuid == entity?.id { - rowView.editing = true - } - return rowView } @@ -463,14 +346,12 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi if let bookmark = entity as? Bookmark { cell.update(from: bookmark) - cell.editing = bookmark.id == editingBookmarkIndex?.uuid if bookmark.favicon(.small) == nil { faviconsFetcherOnboarding?.presentOnboardingIfNeeded() } } else if let folder = entity as? BookmarkFolder { cell.update(from: folder) - cell.editing = folder.id == editingBookmarkIndex?.uuid } else { assertionFailure("Failed to cast bookmark") } @@ -573,6 +454,17 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi } } + private func fetchEntityAndParent(at row: Int) -> (entity: BaseBookmarkEntity?, parentFolder: BookmarkFolder?) { + switch selectionState { + case .empty: + return (bookmarkManager.list?.topLevelEntities[safe: row], nil) + case .folder(let folder): + return (folder.children[safe: row], folder) + case .favorites: + return (bookmarkManager.list?.favoriteBookmarks[safe: row], nil) + } + } + private func index(for entity: Bookmark) -> Int? { switch selectionState { case .empty: @@ -610,11 +502,25 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi } func onSelectionChanged() { - resetSelections() - let indexes = tableView.selectedRowIndexes - indexes.forEach { - let cell = self.tableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? BookmarkTableCellView - cell?.isSelected = true + func updateCellSelections() { + resetSelections() + tableView.selectedRowIndexes.forEach { + let cell = self.tableView.view(atColumn: 0, row: $0, makeIfNecessary: false) as? BookmarkTableCellView + cell?.isSelected = true + } + } + + updateCellSelections() + updateToolbarButtons() + } + + private func updateToolbarButtons() { + let shouldShowDeleteButton = tableView.selectedRowIndexes.count > 1 + NSAnimationContext.runAnimationGroup { context in + context.duration = 0.25 + deleteItemsButton.animator().isHidden = !shouldShowDeleteButton + newBookmarkButton.animator().isHidden = shouldShowDeleteButton + newFolderButton.animator().isHidden = shouldShowDeleteButton } } @@ -633,13 +539,45 @@ extension BookmarkManagementDetailViewController: NSTableViewDelegate, NSTableVi } } +// MARK: - Private + +private extension BookmarkManagementDetailViewController { + + func configureToolbar(button: MouseOverButton, image: NSImage, isHidden: Bool) { + button.bezelStyle = .shadowlessSquare + button.cornerRadius = 4 + button.normalTintColor = .button + button.mouseDownColor = .buttonMouseDown + button.mouseOverColor = .buttonMouseOver + button.imageHugsTitle = true + button.setContentHuggingPriority(.defaultHigh, for: .vertical) + button.alignment = .center + button.font = .systemFont(ofSize: 13) + button.image = image + button.imagePosition = .imageLeading + button.isHidden = isHidden + } + + func configureEmptyState(label: NSTextField, font: NSFont, attributedTitle: NSAttributedString) { + label.isEditable = false + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + label.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + label.translatesAutoresizingMaskIntoConstraints = false + label.alignment = .center + label.drawsBackground = false + label.isBordered = false + label.font = font + label.textColor = .labelColor + label.attributedStringValue = attributedTitle + } + +} + // MARK: - BookmarkTableCellViewDelegate extension BookmarkManagementDetailViewController: BookmarkTableCellViewDelegate { func bookmarkTableCellViewRequestedMenu(_ sender: NSButton, cell: BookmarkTableCellView) { - guard !isEditing else { return } - let row = tableView.row(for: cell) guard let bookmark = fetchEntity(at: row) as? Bookmark else { @@ -647,45 +585,8 @@ extension BookmarkManagementDetailViewController: BookmarkTableCellViewDelegate return } - if let contextMenu = ContextualMenu.menu(for: [bookmark]), let cursorLocation = self.view.window?.mouseLocationOutsideOfEventStream { - let convertedLocation = self.view.convert(cursorLocation, from: nil) - contextMenu.items.forEach { item in - item.target = self - } - - contextMenu.popUp(positioning: nil, at: convertedLocation, in: self.view) - } - } - - func bookmarkTableCellViewToggledFavorite(cell: BookmarkTableCellView) { - let row = tableView.row(for: cell) - - guard let bookmark = fetchEntity(at: row) as? Bookmark else { - assertionFailure("BookmarkManagementDetailViewController: Tried to favorite object which is not bookmark") - return - } - - bookmark.isFavorite.toggle() - bookmarkManager.update(bookmark: bookmark) - } - - func bookmarkTableCellView(_ cell: BookmarkTableCellView, updatedBookmarkWithUUID uuid: String, newTitle: String, newUrl: String) { - let row = tableView.row(for: cell) - defer { - endEditing() - } - guard var bookmark = fetchEntity(at: row) as? Bookmark, bookmark.id == uuid else { - return - } - - if let url = newUrl.url, url.absoluteString != bookmark.url { - bookmark = bookmarkManager.updateUrl(of: bookmark, to: url) ?? bookmark - } - let bookmarkTitle = (newTitle.isEmpty ? bookmark.title : newTitle).trimmingWhitespace() - if bookmark.title != bookmarkTitle { - bookmark.title = bookmarkTitle - bookmarkManager.update(bookmark: bookmark) - } + guard let contextMenu = ContextualMenu.menu(for: [bookmark], target: self) else { return } + contextMenu.popUpAtMouseLocation(in: view) } } @@ -695,20 +596,21 @@ extension BookmarkManagementDetailViewController: BookmarkTableCellViewDelegate extension BookmarkManagementDetailViewController: NSMenuDelegate { func contextualMenuForClickedRows() -> NSMenu? { - guard !isEditing else { return nil } - let row = tableView.clickedRow guard row != -1 else { return ContextualMenu.menu(for: nil) } - if tableView.selectedRowIndexes.contains(row) { + // If only one item is selected try to get the item and its parent folder otherwise show the menu for multiple items. + if tableView.selectedRowIndexes.contains(row), tableView.selectedRowIndexes.count > 1 { return ContextualMenu.menu(for: self.selectedItems()) } - if let item = fetchEntity(at: row) { - return ContextualMenu.menu(for: [item]) + let (item, parent) = fetchEntityAndParent(at: row) + + if let item { + return ContextualMenu.menu(for: item, parentFolder: parent) } else { return nil } @@ -738,13 +640,15 @@ extension BookmarkManagementDetailViewController: FolderMenuItemSelectors { presentAddFolderModal(sender) } - func renameFolder(_ sender: NSMenuItem) { - guard let folder = sender.representedObject as? BookmarkFolder else { + func editFolder(_ sender: NSMenuItem) { + guard let bookmarkEntityInfo = sender.representedObject as? BookmarkEntityInfo, + let folder = bookmarkEntityInfo.entity as? BookmarkFolder + else { assertionFailure("Failed to cast menu represented object to BookmarkFolder") return } - AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(folder: folder)) + BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: folder, parentFolder: bookmarkEntityInfo.parent) .show(in: view.window) } @@ -757,6 +661,16 @@ extension BookmarkManagementDetailViewController: FolderMenuItemSelectors { bookmarkManager.remove(folder: folder) } + func moveToEnd(_ sender: NSMenuItem) { + guard let bookmarkEntity = sender.representedObject as? BookmarksEntityIdentifiable else { + assertionFailure("Failed to cast menu item's represented object to BookmarkEntity") + return + } + + let parentFolderType: ParentFolderType = bookmarkEntity.parentId.flatMap { .parent(uuid: $0) } ?? .root + bookmarkManager.move(objectUUIDs: [bookmarkEntity.entityId], toIndex: nil, withinParentFolder: parentFolderType) { _ in } + } + func openInNewTabs(_ sender: NSMenuItem) { if let children = (sender.representedObject as? BookmarkFolder)?.children { let bookmarks = children.compactMap { $0 as? Bookmark } @@ -768,6 +682,18 @@ extension BookmarkManagementDetailViewController: FolderMenuItemSelectors { } } + func openAllInNewWindow(_ sender: NSMenuItem) { + guard let tabCollection = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, + let folder = sender.representedObject as? BookmarkFolder + else { + assertionFailure("Cannot open all in new window") + return + } + + let newTabCollection = TabCollection.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) + WindowsManager.openNewWindow(with: newTabCollection, isBurner: tabCollection.isBurner) + } + } extension BookmarkManagementDetailViewController: BookmarkMenuItemSelectors { @@ -811,8 +737,10 @@ extension BookmarkManagementDetailViewController: BookmarkMenuItemSelectors { } func editBookmark(_ sender: NSMenuItem) { - guard let bookmark = sender.representedObject as? Bookmark, let bookmarkIndex = index(for: bookmark) else { return } - updateEditingState(forRowAt: bookmarkIndex) + guard let bookmark = sender.representedObject as? Bookmark else { return } + + BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark) + .show(in: view.window) } func copyBookmark(_ sender: NSMenuItem) { diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift index 6d0fdd6860..53e502383b 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift @@ -51,7 +51,7 @@ final class BookmarkManagementSidebarViewController: NSViewController { private lazy var outlineView = BookmarksOutlineView(frame: scrollView.frame) private lazy var treeController = BookmarkTreeController(dataSource: treeControllerDataSource) - private lazy var dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController) + private lazy var dataSource = BookmarkOutlineViewDataSource(contentMode: .foldersOnly, bookmarkManager: bookmarkManager, treeController: treeController, showMenuButtonOnHover: false) private var cancellables = Set() @@ -211,6 +211,13 @@ final class BookmarkManagementSidebarViewController: NSViewController { // MARK: NSOutlineView Configuration private func expandAndRestore(selectedNodes: [BookmarkNode]) { + // OutlineView doesn't allow multiple selections so there should be only one selected node at time. + let selectedNode = selectedNodes.first + // As the data source reloaded we need to refresh the previously selected nodes. + // Lets consider the scenario where we add a folder to a subfolder. + // When the folder is added we need to "refresh" the node because the previously selected node folder has changed (it has a child folder now). + var refreshedSelectedNodes: [BookmarkNode] = [] + treeController.visitNodes { node in if let objectID = (node.representedObject as? BaseBookmarkEntity)?.id { if dataSource.expandedNodesIDs.contains(objectID) { @@ -218,6 +225,11 @@ final class BookmarkManagementSidebarViewController: NSViewController { } else { outlineView.collapseItem(node) } + + // Add the node if it contains previously selected folder + if let folder = selectedNode?.representedObject as? BookmarkFolder, folder.id == objectID { + refreshedSelectedNodes.append(node) + } } // Expand the Bookmarks pseudo folder automatically. @@ -226,7 +238,7 @@ final class BookmarkManagementSidebarViewController: NSViewController { } } - restoreSelection(to: selectedNodes) + restoreSelection(to: refreshedSelectedNodes) } private func restoreSelection(to nodes: [BookmarkNode]) { @@ -292,16 +304,20 @@ extension BookmarkManagementSidebarViewController: NSMenuDelegate { extension BookmarkManagementSidebarViewController: FolderMenuItemSelectors { func newFolder(_ sender: NSMenuItem) { - AddBookmarkFolderModalView().show(in: view.window) + let parent = sender.representedObject as? BookmarkFolder + BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: parent) + .show(in: view.window) } - func renameFolder(_ sender: NSMenuItem) { - guard let folder = sender.representedObject as? BookmarkFolder else { - assertionFailure("Failed to retrieve Bookmark from Rename Folder context menu item") + func editFolder(_ sender: NSMenuItem) { + guard let bookmarkEntityInfo = sender.representedObject as? BookmarkEntityInfo, + let folder = bookmarkEntityInfo.entity as? BookmarkFolder + else { + assertionFailure("Failed to cast menu represented object to BookmarkFolder") return } - AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(folder: folder)) + BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: folder, parentFolder: bookmarkEntityInfo.parent) .show(in: view.window) } @@ -314,17 +330,40 @@ extension BookmarkManagementSidebarViewController: FolderMenuItemSelectors { bookmarkManager.remove(folder: folder) } + func moveToEnd(_ sender: NSMenuItem) { + guard let bookmarkEntity = sender.representedObject as? BookmarksEntityIdentifiable else { + assertionFailure("Failed to cast menu item's represented object to BookmarkEntity") + return + } + + let parentFolderType: ParentFolderType = bookmarkEntity.parentId.flatMap { .parent(uuid: $0) } ?? .root + bookmarkManager.move(objectUUIDs: [bookmarkEntity.entityId], toIndex: nil, withinParentFolder: parentFolderType) { _ in } + } + func openInNewTabs(_ sender: NSMenuItem) { guard let tabCollection = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, - let children = (sender.representedObject as? BookmarkFolder)?.children else { - assertionFailure("Cannot open in new tabs") + let folder = sender.representedObject as? BookmarkFolder + else { + assertionFailure("Cannot open all in new tabs") return } - let tabs = children.compactMap { ($0 as? Bookmark)?.urlObject }.map { Tab(content: .url($0, source: .bookmark), shouldLoadInBackground: true, burnerMode: tabCollection.burnerMode) } + let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) tabCollection.append(tabs: tabs) } + func openAllInNewWindow(_ sender: NSMenuItem) { + guard let tabCollection = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel, + let folder = sender.representedObject as? BookmarkFolder + else { + assertionFailure("Cannot open all in new window") + return + } + + let newTabCollection = TabCollection.withContentOfBookmark(folder: folder, burnerMode: tabCollection.burnerMode) + WindowsManager.openNewWindow(with: newTabCollection, isBurner: tabCollection.isBurner) + } + } #if DEBUG diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift index 08e6c56953..603849bbbf 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -19,11 +19,24 @@ import AppKit import Foundation +protocol BookmarkOutlineCellViewDelegate: AnyObject { + func outlineCellViewRequestedMenu(_ cell: BookmarkOutlineCellView) +} + final class BookmarkOutlineCellView: NSTableCellView { private lazy var faviconImageView = NSImageView() private lazy var titleLabel = NSTextField(string: "Bookmark/Folder") private lazy var countLabel = NSTextField(string: "42") + private lazy var menuButton = NSButton(title: "", image: .settings, target: self, action: #selector(cellMenuButtonClicked)) + private lazy var favoriteImageView = NSImageView() + private lazy var trackingArea: NSTrackingArea = { + NSTrackingArea(rect: .zero, options: [.inVisibleRect, .activeAlways, .mouseEnteredAndExited], owner: self, userInfo: nil) + }() + + var shouldShowMenuButton = false + + weak var delegate: BookmarkOutlineCellViewDelegate? init(identifier: NSUserInterfaceItemIdentifier) { super.init(frame: .zero) @@ -34,10 +47,35 @@ final class BookmarkOutlineCellView: NSTableCellView { fatalError("\(type(of: self)): Bad initializer") } + override func updateTrackingAreas() { + super.updateTrackingAreas() + + guard !trackingAreas.contains(trackingArea), shouldShowMenuButton else { return } + addTrackingArea(trackingArea) + } + + override func mouseEntered(with event: NSEvent) { + guard shouldShowMenuButton else { return } + countLabel.isHidden = true + favoriteImageView.isHidden = true + menuButton.isHidden = false + } + + override func mouseExited(with event: NSEvent) { + guard shouldShowMenuButton else { return } + menuButton.isHidden = true + countLabel.isHidden = false + favoriteImageView.isHidden = false + } + + // MARK: - Private + private func setupUI() { addSubview(faviconImageView) addSubview(titleLabel) addSubview(countLabel) + addSubview(menuButton) + addSubview(favoriteImageView) faviconImageView.translatesAutoresizingMaskIntoConstraints = false faviconImageView.image = .bookmarkDefaultFavicon @@ -64,40 +102,74 @@ final class BookmarkOutlineCellView: NSTableCellView { countLabel.textColor = .blackWhite60 countLabel.lineBreakMode = .byClipping + menuButton.translatesAutoresizingMaskIntoConstraints = false + menuButton.contentTintColor = .button + menuButton.imagePosition = .imageTrailing + menuButton.isBordered = false + menuButton.isHidden = true + + favoriteImageView.translatesAutoresizingMaskIntoConstraints = false + favoriteImageView.imageScaling = .scaleProportionallyDown setupLayout() } private func setupLayout() { - faviconImageView.heightAnchor.constraint(equalToConstant: 16).isActive = true - faviconImageView.widthAnchor.constraint(equalToConstant: 16).isActive = true - faviconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true - faviconImageView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + NSLayoutConstraint.activate([ + faviconImageView.heightAnchor.constraint(equalToConstant: 16), + faviconImageView.widthAnchor.constraint(equalToConstant: 16), + faviconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5), + faviconImageView.centerYAnchor.constraint(equalTo: centerYAnchor), + + titleLabel.leadingAnchor.constraint(equalTo: faviconImageView.trailingAnchor, constant: 10), + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6), + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 6), + + countLabel.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + countLabel.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 5), + trailingAnchor.constraint(equalTo: countLabel.trailingAnchor), + + menuButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + menuButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 5), + menuButton.trailingAnchor.constraint(equalTo: trailingAnchor), + menuButton.topAnchor.constraint(equalTo: topAnchor), + menuButton.bottomAnchor.constraint(equalTo: bottomAnchor), + menuButton.widthAnchor.constraint(equalToConstant: 28), + + favoriteImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + favoriteImageView.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 5), + favoriteImageView.trailingAnchor.constraint(equalTo: menuButton.trailingAnchor), + favoriteImageView.heightAnchor.constraint(equalToConstant: 15), + favoriteImageView.widthAnchor.constraint(equalToConstant: 15), + ]) + faviconImageView.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal) faviconImageView.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .vertical) - titleLabel.leadingAnchor.constraint(equalTo: faviconImageView.trailingAnchor, constant: 10).isActive = true - bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 6).isActive = true - titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 6).isActive = true titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) titleLabel.setContentHuggingPriority(.init(rawValue: 200), for: .horizontal) - countLabel.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true - countLabel.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 5).isActive = true - trailingAnchor.constraint(equalTo: countLabel.trailingAnchor).isActive = true countLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) countLabel.setContentHuggingPriority(.required, for: .horizontal) } + @objc private func cellMenuButtonClicked() { + delegate?.outlineCellViewRequestedMenu(self) + } + + // MARK: - Public + func update(from bookmark: Bookmark) { faviconImageView.image = bookmark.favicon(.small) ?? .bookmarkDefaultFavicon titleLabel.stringValue = bookmark.title countLabel.stringValue = "" + favoriteImageView.image = bookmark.isFavorite ? .favoriteFilledBorder : nil } func update(from folder: BookmarkFolder) { faviconImageView.image = .folder titleLabel.stringValue = folder.title + favoriteImageView.image = nil let totalChildBookmarks = folder.totalChildBookmarks if totalChildBookmarks > 0 { @@ -111,10 +183,13 @@ final class BookmarkOutlineCellView: NSTableCellView { faviconImageView.image = pseudoFolder.icon titleLabel.stringValue = pseudoFolder.name countLabel.stringValue = pseudoFolder.count > 0 ? String(pseudoFolder.count) : "" + favoriteImageView.image = nil } } +// MARK: - Preview + #if DEBUG @available(macOS 14.0, *) #Preview { diff --git a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift index 195ad48845..8bfdc3c4fb 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift @@ -22,8 +22,6 @@ import Foundation @objc protocol BookmarkTableCellViewDelegate: AnyObject { func bookmarkTableCellViewRequestedMenu(_ sender: NSButton, cell: BookmarkTableCellView) - func bookmarkTableCellViewToggledFavorite(cell: BookmarkTableCellView) - func bookmarkTableCellView(_ cellView: BookmarkTableCellView, updatedBookmarkWithUUID uuid: String, newTitle: String, newUrl: String) } @@ -33,53 +31,18 @@ final class BookmarkTableCellView: NSTableCellView { private lazy var titleLabel = NSTextField(string: "Bookmark") private lazy var bookmarkURLLabel = NSTextField(string: "URL") - private lazy var favoriteButton = NSButton(title: "", image: .favoriteFilledBorder, target: self, action: #selector(favoriteButtonClicked)) private lazy var accessoryImageView = NSImageView(image: .forward) - private var favoriteButtonBottomConstraint: NSLayoutConstraint! - private var favoriteButtonTrailingConstraint: NSLayoutConstraint! - private lazy var containerView = NSView() - private lazy var shadowView = NSBox() private lazy var menuButton = NSButton(title: "", image: .settings, target: self, action: #selector(cellMenuButtonClicked)) - // Shadow view constraints: - - private var shadowViewTopConstraint: NSLayoutConstraint! - private var shadowViewBottomConstraint: NSLayoutConstraint! - - // Container view constraints: - - private var titleLabelTopConstraint: NSLayoutConstraint! - private var titleLabelBottomConstraint: NSLayoutConstraint! - @objc func cellMenuButtonClicked(_ sender: NSButton) { delegate?.bookmarkTableCellViewRequestedMenu(sender, cell: self) } - @objc func favoriteButtonClicked(_ sender: NSButton) { - guard entity is Bookmark else { - assertionFailure("\(#file): Tried to favorite non-Bookmark object") - return - } - - delegate?.bookmarkTableCellViewToggledFavorite(cell: self) - } - weak var delegate: BookmarkTableCellViewDelegate? - var editing: Bool = false { - didSet { - if editing { - enterEditingMode() - } else { - exitEditingMode() - } - updateColors() - } - } - var isSelected = false { didSet { updateColors() @@ -96,16 +59,14 @@ final class BookmarkTableCellView: NSTableCellView { return } - accessoryImageView.isHidden = mouseInside || editing - menuButton.isHidden = !mouseInside || editing + accessoryImageView.isHidden = mouseInside + menuButton.isHidden = !mouseInside - if !mouseInside && !editing { + if !mouseInside { resetAppearanceFromBookmark() } - if !editing { - updateTitleLabelValue() - } + updateTitleLabelValue() } } @@ -130,36 +91,16 @@ final class BookmarkTableCellView: NSTableCellView { fatalError("\(type(of: self)): Bad initializer") } - // swiftlint:disable:next function_body_length private func setupUI() { autoresizingMask = [.width, .height] - addSubview(shadowView) addSubview(containerView) - shadowView.boxType = .custom - shadowView.borderColor = .clear - shadowView.borderWidth = 1 - shadowView.cornerRadius = 4 - shadowView.fillColor = .tableCellEditing - shadowView.translatesAutoresizingMaskIntoConstraints = false - shadowView.wantsLayer = true - shadowView.layer?.backgroundColor = NSColor.tableCellEditing.cgColor - shadowView.layer?.cornerRadius = 6 - - let shadow = NSShadow() - shadow.shadowOffset = NSSize(width: 0, height: -1) - shadow.shadowColor = NSColor.black.withAlphaComponent(0.2) - shadow.shadowBlurRadius = 2.0 - shadowView.shadow = shadow - containerView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(faviconImageView) containerView.addSubview(titleLabel) containerView.addSubview(menuButton) containerView.addSubview(accessoryImageView) - containerView.addSubview(bookmarkURLLabel) - containerView.addSubview(favoriteButton) faviconImageView.contentTintColor = .suggestionIcon faviconImageView.wantsLayer = true @@ -176,92 +117,50 @@ final class BookmarkTableCellView: NSTableCellView { titleLabel.font = .systemFont(ofSize: 13) titleLabel.textColor = .labelColor titleLabel.lineBreakMode = .byTruncatingTail - titleLabel.cell?.sendsActionOnEndEditing = true titleLabel.cell?.usesSingleLineMode = true titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) titleLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) - titleLabel.delegate = self - - bookmarkURLLabel.focusRingType = .none - bookmarkURLLabel.isEditable = false - bookmarkURLLabel.isSelectable = false - bookmarkURLLabel.isBordered = false - bookmarkURLLabel.drawsBackground = false - bookmarkURLLabel.font = .systemFont(ofSize: 13) - bookmarkURLLabel.textColor = .secondaryLabelColor - bookmarkURLLabel.lineBreakMode = .byClipping - bookmarkURLLabel.translatesAutoresizingMaskIntoConstraints = false - bookmarkURLLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - bookmarkURLLabel.setContentHuggingPriority(.required, for: .vertical) - bookmarkURLLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) - bookmarkURLLabel.delegate = self accessoryImageView.translatesAutoresizingMaskIntoConstraints = false - accessoryImageView.widthAnchor.constraint(equalToConstant: 22).isActive = true - accessoryImageView.heightAnchor.constraint(equalToConstant: 32).isActive = true menuButton.contentTintColor = .button menuButton.translatesAutoresizingMaskIntoConstraints = false menuButton.isBordered = false menuButton.isHidden = true - - favoriteButton.translatesAutoresizingMaskIntoConstraints = false - favoriteButton.isBordered = false } private func setupLayout() { + NSLayoutConstraint.activate([ + trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 3), + containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 3), + bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 3), + containerView.topAnchor.constraint(equalTo: topAnchor, constant: 3), - trailingAnchor.constraint(equalTo: shadowView.trailingAnchor, constant: 3).isActive = true - shadowView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 3).isActive = true - containerView.leadingAnchor.constraint(equalTo: shadowView.leadingAnchor).isActive = true - containerView.bottomAnchor.constraint(equalTo: shadowView.bottomAnchor).isActive = true - containerView.topAnchor.constraint(equalTo: shadowView.topAnchor).isActive = true - containerView.trailingAnchor.constraint(equalTo: shadowView.trailingAnchor).isActive = true + menuButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8), + faviconImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 6), - bookmarkURLLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10).isActive = true - favoriteButtonTrailingConstraint = trailingAnchor.constraint(equalTo: favoriteButton.trailingAnchor, constant: 3) - favoriteButtonTrailingConstraint.isActive = true + accessoryImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + titleLabel.leadingAnchor.constraint(equalTo: faviconImageView.trailingAnchor, constant: 8), + trailingAnchor.constraint(equalTo: accessoryImageView.trailingAnchor, constant: 3), + faviconImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + trailingAnchor.constraint(equalTo: menuButton.trailingAnchor, constant: 2), - menuButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8).isActive = true - faviconImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 6).isActive = true - favoriteButton.topAnchor.constraint(equalTo: bookmarkURLLabel.bottomAnchor).isActive = true + menuButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), - accessoryImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true - titleLabel.leadingAnchor.constraint(equalTo: faviconImageView.trailingAnchor, constant: 8).isActive = true - trailingAnchor.constraint(equalTo: accessoryImageView.trailingAnchor, constant: 3).isActive = true - faviconImageView.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true - trailingAnchor.constraint(equalTo: menuButton.trailingAnchor, constant: 2).isActive = true - bookmarkURLLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor).isActive = true - trailingAnchor.constraint(equalTo: bookmarkURLLabel.trailingAnchor, constant: 16).isActive = true - menuButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor).isActive = true + menuButton.heightAnchor.constraint(equalToConstant: 32), + menuButton.widthAnchor.constraint(equalToConstant: 28), - favoriteButton.widthAnchor.constraint(equalToConstant: 24).isActive = true - favoriteButton.heightAnchor.constraint(equalToConstant: 24).isActive = true + faviconImageView.heightAnchor.constraint(equalToConstant: 16), + faviconImageView.widthAnchor.constraint(equalToConstant: 16), - menuButton.heightAnchor.constraint(equalToConstant: 32).isActive = true - menuButton.widthAnchor.constraint(equalToConstant: 28).isActive = true + bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8), + titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5), - faviconImageView.heightAnchor.constraint(equalToConstant: 16).isActive = true - faviconImageView.widthAnchor.constraint(equalToConstant: 16).isActive = true - - shadowViewTopConstraint = shadowView.topAnchor.constraint(equalTo: topAnchor, constant: 3) - shadowViewTopConstraint.isActive = true - - shadowViewBottomConstraint = bottomAnchor.constraint(equalTo: shadowView.bottomAnchor, constant: 3) - shadowViewBottomConstraint.isActive = true - - titleLabelBottomConstraint = bottomAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8) - titleLabelBottomConstraint.priority = .init(rawValue: 250) - titleLabelBottomConstraint.isActive = true - - favoriteButtonBottomConstraint = bottomAnchor.constraint(equalTo: favoriteButton.bottomAnchor, constant: 8) - favoriteButtonBottomConstraint.priority = .init(rawValue: 750) - favoriteButtonBottomConstraint.isActive = true - - titleLabelTopConstraint = titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5) - titleLabelTopConstraint.isActive = true + accessoryImageView.widthAnchor.constraint(equalToConstant: 22), + accessoryImageView.heightAnchor.constraint(equalToConstant: 32), + ]) } override var backgroundStyle: NSView.BackgroundStyle { @@ -314,84 +213,28 @@ final class BookmarkTableCellView: NSTableCellView { accessoryImageView.isHidden = false } - accessoryImageView.image = bookmark.isFavorite ? .favorite : nil - favoriteButton.image = bookmark.isFavorite ? .favoriteFilledBorder : .favorite + accessoryImageView.image = bookmark.isFavorite ? .favoriteFilledBorder : nil titleLabel.stringValue = bookmark.title primaryTitleLabelValue = bookmark.title tertiaryTitleLabelValue = bookmark.url - bookmarkURLLabel.stringValue = bookmark.url } func update(from folder: BookmarkFolder) { self.entity = folder faviconImageView.image = .folder - accessoryImageView.image = .chevronNext16 + accessoryImageView.image = .chevronMediumRight16 primaryTitleLabelValue = folder.title tertiaryTitleLabelValue = nil } private func resetCellState() { self.entity = nil - editing = false mouseInside = false - bookmarkURLLabel.isHidden = true - favoriteButton.isHidden = true - titleLabelBottomConstraint.priority = .required - } - - private func enterEditingMode() { - titleLabel.isEditable = true - bookmarkURLLabel.isEditable = true - - shadowViewTopConstraint.constant = 10 - shadowViewBottomConstraint.constant = 10 - titleLabelTopConstraint.constant = 12 - favoriteButtonTrailingConstraint.constant = 11 - favoriteButtonBottomConstraint.constant = 18 - shadowView.isHidden = false - faviconImageView.isHidden = true - - bookmarkURLLabel.isHidden = false - favoriteButton.isHidden = false - titleLabelBottomConstraint.priority = .defaultLow - - hideTertiaryValueInTitleLabel() - - // Reluctantly use GCD as a workaround for a rare label layout issue, in which the text field shows no text upon becoming first responder. - DispatchQueue.main.async { - self.titleLabel.becomeFirstResponder() - } - } - - private func exitEditingMode() { - window?.makeFirstResponder(nil) - - titleLabel.isEditable = false - bookmarkURLLabel.isEditable = false - - titleLabelTopConstraint.constant = 5 - shadowViewTopConstraint.constant = 3 - shadowViewBottomConstraint.constant = 3 - favoriteButtonTrailingConstraint.constant = 3 - favoriteButtonBottomConstraint.constant = 8 - shadowView.isHidden = true - faviconImageView.isHidden = false - - bookmarkURLLabel.isHidden = true - favoriteButton.isHidden = true - titleLabelBottomConstraint.priority = .required - - if let editedBookmark = self.entity as? Bookmark { - delegate?.bookmarkTableCellView(self, - updatedBookmarkWithUUID: editedBookmark.id, - newTitle: titleLabel.stringValue, - newUrl: bookmarkURLLabel.stringValue) - } } private func updateColors() { - titleLabel.textColor = isSelected && !editing ? .white : .controlTextColor + titleLabel.textColor = isSelected ? .white : .controlTextColor menuButton.contentTintColor = isSelected ? .white : .button faviconImageView.contentTintColor = isSelected ? .white : .suggestionIcon accessoryImageView.contentTintColor = isSelected ? .white : .suggestionIcon @@ -428,11 +271,7 @@ final class BookmarkTableCellView: NSTableCellView { } private func updateTitleLabelValue() { - guard !editing else { - return - } - - if let tertiaryValue = tertiaryTitleLabelValue, mouseInside, !editing { + if let tertiaryValue = tertiaryTitleLabelValue, mouseInside { showTertiaryValueInTitleLabel(tertiaryValue) } else { hideTertiaryValueInTitleLabel() @@ -467,26 +306,6 @@ final class BookmarkTableCellView: NSTableCellView { } -extension BookmarkTableCellView: NSTextFieldDelegate { - - func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool { - switch commandSelector { - case #selector(cancelOperation) where self.editing: - self.resetAppearanceFromBookmark() - self.editing = false - return true - - case #selector(insertNewline) where self.editing: - self.editing = false - return true - - default: break - } - return false - } - -} - #if DEBUG @available(macOS 14.0, *) #Preview { @@ -517,19 +336,10 @@ extension BookmarkTableCellView { fatalError("init(coder:) has not been implemented") } - func bookmarkTableCellViewRequestedMenu(_ sender: NSButton, cell: BookmarkTableCellView) { - cell.editing.toggle() - } + func bookmarkTableCellViewRequestedMenu(_ sender: NSButton, cell: BookmarkTableCellView) {} func bookmarkTableCellViewToggledFavorite(cell: BookmarkTableCellView) { (cell.entity as? Bookmark)?.isFavorite.toggle() - cell.editing = false - } - - func bookmarkTableCellView(_ cellView: BookmarkTableCellView, updatedBookmarkWithUUID uuid: String, newTitle: String, newUrl: String) { - if cell.editing { - cell.editing = false - } } } } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkTableRowView.swift b/DuckDuckGo/Bookmarks/View/BookmarkTableRowView.swift index ebd3a9f318..0f06f84788 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkTableRowView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkTableRowView.swift @@ -16,14 +16,12 @@ // limitations under the License. // -import Foundation +import AppKit final class BookmarkTableRowView: NSTableRowView { var onSelectionChanged: (() -> Void)? - var editing = false - var hasPrevious = false { didSet { needsDisplay = true @@ -56,7 +54,7 @@ final class BookmarkTableRowView: NSTableRowView { backgroundColor.setFill() bounds.fill() - if mouseInside && !editing { + if mouseInside { let path = NSBezierPath(roundedRect: bounds, xRadius: 6, yRadius: 6) NSColor.rowHover.setFill() path.fill() @@ -68,8 +66,6 @@ final class BookmarkTableRowView: NSTableRowView { } override func drawSelection(in dirtyRect: NSRect) { - guard !editing else { return } - var roundedCorners = [NSBezierPath.Corners]() if !hasPrevious { diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift new file mode 100644 index 0000000000..2c0256bba4 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkDialogView.swift @@ -0,0 +1,123 @@ +// +// AddEditBookmarkDialogView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct AddEditBookmarkDialogView: ModalView { + @ObservedObject private var viewModel: AddEditBookmarkDialogCoordinatorViewModel + + init(viewModel: AddEditBookmarkDialogCoordinatorViewModel) { + self.viewModel = viewModel + } + + var body: some View { + Group { + switch viewModel.viewState { + case .bookmark: + addEditBookmarkView + case .folder: + addFolderView + } + } + .font(.system(size: 13)) + } + + private var addEditBookmarkView: some View { + AddEditBookmarkView( + title: viewModel.bookmarkModel.title, + buttonsState: .compressed, + bookmarkName: $viewModel.bookmarkModel.bookmarkName, + bookmarkURLPath: $viewModel.bookmarkModel.bookmarkURLPath, + isBookmarkFavorite: $viewModel.bookmarkModel.isBookmarkFavorite, + folders: viewModel.bookmarkModel.folders, + selectedFolder: $viewModel.bookmarkModel.selectedFolder, + isURLFieldHidden: false, + addFolderAction: viewModel.addFolderAction, + otherActionTitle: viewModel.bookmarkModel.cancelActionTitle, + isOtherActionDisabled: viewModel.bookmarkModel.isOtherActionDisabled, + otherAction: viewModel.bookmarkModel.cancel, + defaultActionTitle: viewModel.bookmarkModel.defaultActionTitle, + isDefaultActionDisabled: viewModel.bookmarkModel.isDefaultActionDisabled, + defaultAction: viewModel.bookmarkModel.addOrSave + ) + .frame(width: 448, height: 288) + } + + private var addFolderView: some View { + AddEditBookmarkFolderView( + title: viewModel.folderModel.title, + buttonsState: .compressed, + folders: viewModel.folderModel.folders, + folderName: $viewModel.folderModel.folderName, + selectedFolder: $viewModel.folderModel.selectedFolder, + cancelActionTitle: viewModel.folderModel.cancelActionTitle, + isCancelActionDisabled: viewModel.folderModel.isOtherActionDisabled, + cancelAction: { _ in + viewModel.dismissAction() + }, + defaultActionTitle: viewModel.folderModel.defaultActionTitle, + isDefaultActionDisabled: viewModel.folderModel.isDefaultActionDisabled, + defaultAction: { _ in + viewModel.folderModel.addOrSave { + viewModel.dismissAction() + } + } + ) + .frame(width: 448, height: 210) + } +} + +// MARK: - Previews + +#if DEBUG +#Preview("Add Bookmark - Light Mode") { + let bookmarkManager = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [])) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkView(parent: nil, bookmarkManager: bookmarkManager) + .preferredColorScheme(.light) +} + +#Preview("Add Bookmark - Dark Mode") { + let bookmarkManager = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [])) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkView(parent: nil, bookmarkManager: bookmarkManager) + .preferredColorScheme(.dark) +} + +#Preview("Edit Bookmark - Light Mode") { + let parentFolder = BookmarkFolder(id: "7", title: "DuckDuckGo") + let bookmark = Bookmark(id: "1", url: "www.duckduckgo.com", title: "DuckDuckGo", isFavorite: true, parentFolderUUID: "7") + let bookmarkManager = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [bookmark, parentFolder])) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark, bookmarkManager: bookmarkManager) + .preferredColorScheme(.light) +} + +#Preview("Edit Bookmark - Dark Mode") { + let parentFolder = BookmarkFolder(id: "7", title: "DuckDuckGo") + let bookmark = Bookmark(id: "1", url: "www.duckduckgo.com", title: "DuckDuckGo", isFavorite: true, parentFolderUUID: "7") + let bookmarkManager = LocalBookmarkManager(bookmarkStore: BookmarkStoreMock(bookmarks: [bookmark, parentFolder])) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark, bookmarkManager: bookmarkManager) + .preferredColorScheme(.dark) +} +#endif diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderDialogView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderDialogView.swift new file mode 100644 index 0000000000..f859311335 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderDialogView.swift @@ -0,0 +1,107 @@ +// +// AddEditBookmarkFolderDialogView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct AddEditBookmarkFolderDialogView: ModalView { + @ObservedObject private var viewModel: AddEditBookmarkFolderDialogViewModel + + init(viewModel: AddEditBookmarkFolderDialogViewModel) { + self.viewModel = viewModel + } + + var body: some View { + AddEditBookmarkFolderView( + title: viewModel.title, + buttonsState: .compressed, + folders: viewModel.folders, + folderName: $viewModel.folderName, + selectedFolder: $viewModel.selectedFolder, + cancelActionTitle: viewModel.cancelActionTitle, + isCancelActionDisabled: viewModel.isOtherActionDisabled, + cancelAction: viewModel.cancel, + defaultActionTitle: viewModel.defaultActionTitle, + isDefaultActionDisabled: viewModel.isDefaultActionDisabled, + defaultAction: viewModel.addOrSave + ) + .font(.system(size: 13)) + .frame(width: 448, height: 210) + } +} + +// MARK: - Previews +#if DEBUG +#Preview("Add Folder To Bookmarks - Light") { + let bookmarkFolder = BookmarkFolder(id: "1", title: "DuckDuckGo", children: []) + let store = BookmarkStoreMock(bookmarks: [bookmarkFolder]) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: nil, bookmarkManager: bookmarkManager) + .preferredColorScheme(.light) +} + +#Preview("Add Folder To Bookmarks Subfolder - Light") { + let bookmarkFolder = BookmarkFolder(id: "1", title: "DuckDuckGo", children: []) + let store = BookmarkStoreMock(bookmarks: [bookmarkFolder]) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: bookmarkFolder, bookmarkManager: bookmarkManager) + .preferredColorScheme(.light) +} + +#Preview("Edit Folder - Light") { + let bookmarkFolder = BookmarkFolder(id: "1", title: "DuckDuckGo", children: []) + let store = BookmarkStoreMock(bookmarks: [bookmarkFolder]) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: bookmarkFolder, parentFolder: nil, bookmarkManager: bookmarkManager) + .preferredColorScheme(.light) +} + +#Preview("Add Folder To Bookmarks - Dark") { + let store = BookmarkStoreMock(bookmarks: []) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: nil, bookmarkManager: bookmarkManager) + .preferredColorScheme(.dark) +} + +#Preview("Add Folder To Bookmarks Subfolder - Dark") { + let bookmarkFolder = BookmarkFolder(id: "1", title: "DuckDuckGo", children: []) + let store = BookmarkStoreMock(bookmarks: [bookmarkFolder]) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: bookmarkFolder, bookmarkManager: bookmarkManager) + .preferredColorScheme(.dark) +} + +#Preview("Edit Folder in Subfolder - Dark") { + let bookmarkFolder = BookmarkFolder(id: "1", title: "DuckDuckGo", children: []) + let store = BookmarkStoreMock(bookmarks: [bookmarkFolder]) + let bookmarkManager = LocalBookmarkManager(bookmarkStore: store) + bookmarkManager.loadBookmarks() + + return BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: bookmarkFolder, parentFolder: bookmarkFolder, bookmarkManager: bookmarkManager) + .preferredColorScheme(.dark) +} +#endif diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderView.swift new file mode 100644 index 0000000000..d89cfc7c93 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkFolderView.swift @@ -0,0 +1,132 @@ +// +// AddEditBookmarkFolderView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct AddEditBookmarkFolderView: View { + enum ButtonsState { + case compressed + case expanded + } + + let title: String + let buttonsState: ButtonsState + let folders: [FolderViewModel] + @Binding var folderName: String + @Binding var selectedFolder: BookmarkFolder? + + let cancelActionTitle: String + let isCancelActionDisabled: Bool + let cancelAction: @MainActor (_ dismiss: () -> Void) -> Void + + let defaultActionTitle: String + let isDefaultActionDisabled: Bool + let defaultAction: @MainActor (_ dismiss: () -> Void) -> Void + + var body: some View { + BookmarkDialogContainerView( + title: title, + middleSection: { + BookmarkDialogStackedContentView( + .init( + title: UserText.Bookmarks.Dialog.Field.name, + content: TextField("", text: $folderName) + .focusedOnAppear() + .accessibilityIdentifier("bookmark.add.name.textfield") + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)) + ), + .init( + title: UserText.Bookmarks.Dialog.Field.location, + content: BookmarkFolderPicker( + folders: folders, + selectedFolder: $selectedFolder + ) + .accessibilityIdentifier("bookmark.folder.folder.dropdown") + ) + ) + }, + bottomSection: { + BookmarkDialogButtonsView( + viewState: .init(buttonsState), + otherButtonAction: .init( + title: cancelActionTitle, + keyboardShortCut: .cancelAction, + isDisabled: isCancelActionDisabled, + action: cancelAction + ), defaultButtonAction: .init( + title: defaultActionTitle, + keyboardShortCut: .defaultAction, + isDisabled: isDefaultActionDisabled, + action: defaultAction + ) + ) + } + ) + } +} + +private extension BookmarkDialogButtonsView.ViewState { + + init(_ state: AddEditBookmarkFolderView.ButtonsState) { + switch state { + case .compressed: + self = .compressed + case .expanded: + self = .expanded + } + } +} + +#Preview("Compressed") { + @State var folderName = "" + @State var selectedFolder: BookmarkFolder? + + return AddEditBookmarkFolderView( + title: "Test Title", + buttonsState: .compressed, + folders: [], + folderName: $folderName, + selectedFolder: $selectedFolder, + cancelActionTitle: UserText.cancel, + isCancelActionDisabled: false, + cancelAction: { _ in }, + defaultActionTitle: UserText.save, + isDefaultActionDisabled: false, + defaultAction: { _ in } + ) +} + +#Preview("Expanded") { + @State var folderName = "" + @State var selectedFolder: BookmarkFolder? + + return AddEditBookmarkFolderView( + title: "Test Title", + buttonsState: .expanded, + folders: [], + folderName: $folderName, + selectedFolder: $selectedFolder, + cancelActionTitle: UserText.cancel, + isCancelActionDisabled: false, + cancelAction: { _ in }, + defaultActionTitle: UserText.save, + isDefaultActionDisabled: false, + defaultAction: { _ in } + ) +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift new file mode 100644 index 0000000000..8d34889432 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/AddEditBookmarkView.swift @@ -0,0 +1,113 @@ +// +// AddEditBookmarkView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct AddEditBookmarkView: View { + let title: String + let buttonsState: BookmarksDialogButtonsState + + @Binding var bookmarkName: String + var bookmarkURLPath: Binding? + @Binding var isBookmarkFavorite: Bool + + let folders: [FolderViewModel] + @Binding var selectedFolder: BookmarkFolder? + + let isURLFieldHidden: Bool + + let addFolderAction: () -> Void + + let otherActionTitle: String + let isOtherActionDisabled: Bool + let otherAction: @MainActor (_ dismiss: () -> Void) -> Void + + let defaultActionTitle: String + let isDefaultActionDisabled: Bool + let defaultAction: @MainActor (_ dismiss: () -> Void) -> Void + + var body: some View { + BookmarkDialogContainerView( + title: title, + middleSection: { + BookmarkDialogStackedContentView( + .init( + title: UserText.Bookmarks.Dialog.Field.name, + content: TextField("", text: $bookmarkName) + .focusedOnAppear() + .accessibilityIdentifier("bookmark.add.name.textfield") + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)) + ), + .init( + title: UserText.Bookmarks.Dialog.Field.url, + content: TextField("", text: bookmarkURLPath ?? .constant("")) + .accessibilityIdentifier("bookmark.add.url.textfield") + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)), + isContentViewHidden: isURLFieldHidden + ), + .init( + title: UserText.Bookmarks.Dialog.Field.location, + content: BookmarkDialogFolderManagementView( + folders: folders, + selectedFolder: $selectedFolder, + onActionButton: addFolderAction + ) + ) + ) + BookmarkFavoriteView(isFavorite: $isBookmarkFavorite) + }, + bottomSection: { + BookmarkDialogButtonsView( + viewState: .init(buttonsState), + otherButtonAction: .init( + title: otherActionTitle, + isDisabled: isOtherActionDisabled, + action: otherAction + ), + defaultButtonAction: .init( + title: defaultActionTitle, + keyboardShortCut: .defaultAction, + isDisabled: isDefaultActionDisabled, + action: defaultAction + ) + ) + } + ) + } + +} + +// MARK: - BookmarksDialogButtonsState + +enum BookmarksDialogButtonsState { + case compressed + case expanded +} + +extension BookmarkDialogButtonsView.ViewState { + init(_ state: BookmarksDialogButtonsState) { + switch state { + case .compressed: + self = .compressed + case .expanded: + self = .expanded + } + } +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogButtonsView.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogButtonsView.swift new file mode 100644 index 0000000000..d55f6dd34f --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogButtonsView.swift @@ -0,0 +1,186 @@ +// +// BookmarkDialogButtonsView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +struct BookmarkDialogButtonsView: View { + private let viewState: ViewState + private let otherButtonAction: Action + private let defaultButtonAction: Action + @Environment(\.dismiss) private var dismiss + + init( + viewState: ViewState, + otherButtonAction: Action, + defaultButtonAction: Action + ) { + self.viewState = viewState + self.otherButtonAction = otherButtonAction + self.defaultButtonAction = defaultButtonAction + } + + var body: some View { + HStack { + if viewState == .compressed { + Spacer() + } + + actionButton(action: otherButtonAction, viewState: viewState) + + actionButton(action: defaultButtonAction, viewState: viewState).accessibilityIdentifier("BookmarkDialogButtonsView.defaultButton") + } + } + + @MainActor + private func actionButton(action: Action, viewState: ViewState) -> some View { + Button { + action.action(dismiss.callAsFunction) + } label: { + Text(action.title) + .frame(height: viewState.height) + .frame(maxWidth: viewState.maxWidth) + } + .keyboardShortcut(action.keyboardShortCut) + .disabled(action.isDisabled) + .ifLet(action.accessibilityIdentifier) { view, value in + view.accessibilityIdentifier(value) + } + } +} + +// MARK: - BookmarkDialogButtonsView + Types + +extension BookmarkDialogButtonsView { + + enum ViewState: Equatable { + case compressed + case expanded + } + + struct Action { + let title: String + let keyboardShortCut: KeyboardShortcut? + let accessibilityIdentifier: String? + let isDisabled: Bool + let action: @MainActor (_ dismiss: () -> Void) -> Void + + init( + title: String, + accessibilityIdentifier: String? = nil, + keyboardShortCut: KeyboardShortcut? = nil, + isDisabled: Bool = false, + action: @MainActor @escaping (_ dismiss: () -> Void) -> Void + ) { + self.title = title + self.keyboardShortCut = keyboardShortCut + self.accessibilityIdentifier = accessibilityIdentifier + self.isDisabled = isDisabled + self.action = action + } + } +} + +// MARK: - BookmarkDialogButtonsView.ViewState + +private extension BookmarkDialogButtonsView.ViewState { + + var maxWidth: CGFloat? { + switch self { + case .compressed: + return nil + case .expanded: + return .infinity + } + } + + var height: CGFloat? { + switch self { + case .compressed: + return nil + case .expanded: + return 28.0 + } + } + +} + +// MARK: - Preview + +#Preview("Compressed - Disable Default Button") { + BookmarkDialogButtonsView( + viewState: .compressed, + otherButtonAction: .init( + title: "Left", + action: { _ in } + ), + defaultButtonAction: .init( + title: "Right", + isDisabled: true, + action: {_ in } + ) + ) + .frame(width: 320, height: 50) +} + +#Preview("Compressed - Enabled Default Button") { + BookmarkDialogButtonsView( + viewState: .compressed, + otherButtonAction: .init( + title: "Left", + action: { _ in } + ), + defaultButtonAction: .init( + title: "Right", + isDisabled: false, + action: {_ in } + ) + ) + .frame(width: 320, height: 50) +} + +#Preview("Expanded - Disable Default Button") { + BookmarkDialogButtonsView( + viewState: .expanded, + otherButtonAction: .init( + title: "Left", + action: { _ in } + ), + defaultButtonAction: .init( + title: "Right", + isDisabled: true, + action: {_ in } + ) + ) + .frame(width: 320, height: 50) +} + +#Preview("Expanded - Enable Default Button") { + BookmarkDialogButtonsView( + viewState: .expanded, + otherButtonAction: .init( + title: "Left", + action: { _ in } + ), + defaultButtonAction: .init( + title: "Right", + isDisabled: false, + action: {_ in } + ) + ) + .frame(width: 320, height: 50) +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogContainerView.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogContainerView.swift new file mode 100644 index 0000000000..ea49712abb --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogContainerView.swift @@ -0,0 +1,50 @@ +// +// BookmarkDialogContainerView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import SwiftUIExtensions + +struct BookmarkDialogContainerView: View { + private let title: String + @ViewBuilder private let middleSection: () -> Content + @ViewBuilder private let bottomSection: () -> Buttons + + init( + title: String, + @ViewBuilder middleSection: @escaping () -> Content, + @ViewBuilder bottomSection: @escaping () -> Buttons + ) { + self.title = title + self.middleSection = middleSection + self.bottomSection = bottomSection + } + + var body: some View { + TieredDialogView( + verticalSpacing: 16.0, + horizontalPadding: 20.0, + top: { + Text(title) + .foregroundColor(.primary) + .fontWeight(.semibold) + }, + center: middleSection, + bottom: bottomSection + ) + } +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogFolderManagementView.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogFolderManagementView.swift new file mode 100644 index 0000000000..8081abc14d --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogFolderManagementView.swift @@ -0,0 +1,76 @@ +// +// BookmarkDialogFolderManagementView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import SwiftUIExtensions + +struct BookmarkDialogFolderManagementView: View { + private let folders: [FolderViewModel] + private var selectedFolder: Binding + private let onActionButton: @MainActor () -> Void + + init( + folders: [FolderViewModel], + selectedFolder: Binding, + onActionButton: @escaping @MainActor () -> Void + ) { + self.folders = folders + self.selectedFolder = selectedFolder + self.onActionButton = onActionButton + } + + var body: some View { + HStack { + BookmarkFolderPicker( + folders: folders, + selectedFolder: selectedFolder + ) + .accessibilityIdentifier("bookmark.add.folder.dropdown") + + Button { + onActionButton() + } label: { + Image(.addFolder) + } + .accessibilityIdentifier("bookmark.add.new.folder.button") + .buttonStyle(StandardButtonStyle()) + } + } +} + +#Preview { + @State var selectedFolder: BookmarkFolder? = BookmarkFolder(id: "1", title: "Nested Folder", children: []) + let folderViewModels: [FolderViewModel] = [ + .init( + entity: .init( + id: "1", + title: "Nested Folder", + parentFolderUUID: nil, + children: [] + ), + level: 1 + ) + ] + + return BookmarkDialogFolderManagementView( + folders: folderViewModels, + selectedFolder: $selectedFolder, + onActionButton: {} + ) + .frame(width: 400) +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogStackedContentView.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogStackedContentView.swift new file mode 100644 index 0000000000..864b0cdb13 --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkDialogStackedContentView.swift @@ -0,0 +1,111 @@ +// +// BookmarkDialogStackedContentView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import SwiftUIExtensions + +struct BookmarkDialogStackedContentView: View { + private let items: [Item] + + init(_ items: Item...) { + self.items = items + } + + init(_ items: [Item]) { + self.items = items + } + + var body: some View { + TwoColumnsListView( + horizontalSpacing: 16.0, + verticalSpacing: 20.0, + rowHeight: 22.0, + leftColumn: { + ForEach(items, id: \.title) { item in + if !item.isContentViewHidden { + Text(item.title) + .foregroundColor(.primary) + .fontWeight(.medium) + } + } + }, + rightColumn: { + ForEach(items, id: \.title) { item in + if !item.isContentViewHidden { + item.content + } + } + } + ) + } +} + +// MARK: - BookmarkModalStackedContentView + Item + +extension BookmarkDialogStackedContentView { + struct Item { + fileprivate let title: String + fileprivate let content: AnyView + fileprivate let isContentViewHidden: Bool + + init(title: String, content: any View, isContentViewHidden: Bool = false) { + self.title = title + self.content = AnyView(content) + self.isContentViewHidden = isContentViewHidden + } + } +} + +// MARK: - Preview + +#Preview { + @State var name: String = "DuckDuckGo" + @State var url: String = "https://www.duckduckgo.com" + @State var selectedFolder: BookmarkFolder? + + return BookmarkDialogStackedContentView( + .init( + title: "Name", + content: + TextField("", text: $name) + .textFieldStyle(.roundedBorder) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)) + + ), + .init( + title: "URL", + content: + TextField("", text: $url) + .textFieldStyle(.roundedBorder) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .font(.system(size: 14)) + ), + .init( + title: "Location", + content: + BookmarkDialogFolderManagementView( + folders: [], + selectedFolder: $selectedFolder, + onActionButton: { } + ) + ) + ) + .padding([.horizontal, .vertical]) + .frame(width: 400) +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarkFavoriteView.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkFavoriteView.swift new file mode 100644 index 0000000000..9778ab0d5c --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarkFavoriteView.swift @@ -0,0 +1,47 @@ +// +// BookmarkFavoriteView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import SwiftUIExtensions +import PreferencesViews + +struct BookmarkFavoriteView: View { + @Binding var isFavorite: Bool + + var body: some View { + Toggle(isOn: $isFavorite) { + HStack(spacing: 6) { + Image(.favoriteFilledBorder) + Text(UserText.addToFavorites) + .foregroundColor(.primary) + } + } + .toggleStyle(.checkbox) + .accessibilityIdentifier("bookmark.add.add.to.favorites.button") + } +} + +#Preview("Favorite") { + BookmarkFavoriteView(isFavorite: .constant(true)) + .frame(width: 300) +} + +#Preview("Not Favorite") { + BookmarkFavoriteView(isFavorite: .constant(false)) + .frame(width: 300) +} diff --git a/DuckDuckGo/Bookmarks/View/Dialog/BookmarksDialogViewFactory.swift b/DuckDuckGo/Bookmarks/View/Dialog/BookmarksDialogViewFactory.swift new file mode 100644 index 0000000000..b29b50bbbb --- /dev/null +++ b/DuckDuckGo/Bookmarks/View/Dialog/BookmarksDialogViewFactory.swift @@ -0,0 +1,93 @@ +// +// BookmarksDialogViewFactory.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +@MainActor +enum BookmarksDialogViewFactory { + + /// Creates an instance of AddEditBookmarkFolderDialogView for adding a Bookmark Folder. + /// - Parameters: + /// - parentFolder: An optional `BookmarkFolder`. When adding a folder to the root bookmark folder pass `nil`. For any other folder pass the `BookmarkFolder` the new folder should be within. + /// - bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkFolderDialogView. + static func makeAddBookmarkFolderView(parentFolder: BookmarkFolder?, bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkFolderDialogView { + let viewModel = AddEditBookmarkFolderDialogViewModel(mode: .add(parentFolder: parentFolder), bookmarkManager: bookmarkManager) + return AddEditBookmarkFolderDialogView(viewModel: viewModel) + } + + /// Creates an instance of AddEditBookmarkFolderDialogView for editing a Bookmark Folder. + /// - Parameters: + /// - folder: The `BookmarkFolder` to edit. + /// - parentFolder: An optional `BookmarkFolder`. When editing a folder within the root bookmark folder pass `nil`. For any other folder pass the `BookmarkFolder` the folder belongs to. + /// - bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkFolderDialogView. + static func makeEditBookmarkFolderView(folder: BookmarkFolder, parentFolder: BookmarkFolder?, bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkFolderDialogView { + let viewModel = AddEditBookmarkFolderDialogViewModel(mode: .edit(folder: folder, parentFolder: parentFolder), bookmarkManager: bookmarkManager) + return AddEditBookmarkFolderDialogView(viewModel: viewModel) + } + + /// Creates an instance of AddEditBookmarkDialogView for adding a Bookmark with the specified web page. + /// - Parameters: + /// - currentTab: An optional `WebsiteInfo`. When adding a bookmark from the bookmark shortcut panel, if the `Tab` has loaded a web page pass the information via the `currentTab`. If the `Tab` has not loaded a tab pass `nil`. If adding a `Bookmark` from the `Manage Bookmark` settings page, pass `nil`. + /// - bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkDialogView. + static func makeAddBookmarkView(currentTab: WebsiteInfo?, bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkDialogView { + let viewModel = AddEditBookmarkDialogViewModel(mode: .add(tabWebsite: currentTab), bookmarkManager: bookmarkManager) + return makeAddEditBookmarkDialogView(viewModel: viewModel, bookmarkManager: bookmarkManager) + } + + /// Creates an instance of AddEditBookmarkDialogView for adding a Bookmark with the specified parent folder. + /// - Parameters: + /// - parentFolder: An optional `BookmarkFolder`. When adding a bookmark from the bookmark management view, if the user select a parent folder pass this value won't be `nil`. Otherwise, if no folder is selected this value will be `nil`. + /// - bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkDialogView. + static func makeAddBookmarkView(parent: BookmarkFolder?, bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkDialogView { + let viewModel = AddEditBookmarkDialogViewModel(mode: .add(parentFolder: parent), bookmarkManager: bookmarkManager) + return makeAddEditBookmarkDialogView(viewModel: viewModel, bookmarkManager: bookmarkManager) + } + + /// Creates an instance of AddEditBookmarkDialogView for adding a Bookmark from the Favorites view in the empty Tab. + /// - Parameter bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkDialogView, + static func makeAddFavoriteView(bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkDialogView { + let viewModel = AddEditBookmarkDialogViewModel(mode: .add(shouldPresetFavorite: true), bookmarkManager: bookmarkManager) + return makeAddEditBookmarkDialogView(viewModel: viewModel, bookmarkManager: bookmarkManager) + } + + /// Creates an instance of AddEditBookmarkDialogView for editing a Bookmark. + /// - Parameters: + /// - bookmark: The `Bookmark` to edit. + /// - bookmarkManager: An instance of `BookmarkManager`. This should be used for `#previews` only. + /// - Returns: An instance of AddEditBookmarkDialogView. + static func makeEditBookmarkView(bookmark: Bookmark, bookmarkManager: LocalBookmarkManager = .shared) -> AddEditBookmarkDialogView { + let viewModel = AddEditBookmarkDialogViewModel(mode: .edit(bookmark: bookmark), bookmarkManager: bookmarkManager) + return makeAddEditBookmarkDialogView(viewModel: viewModel, bookmarkManager: bookmarkManager) + } + +} + +private extension BookmarksDialogViewFactory { + + private static func makeAddEditBookmarkDialogView(viewModel: AddEditBookmarkDialogViewModel, bookmarkManager: BookmarkManager) -> AddEditBookmarkDialogView { + let addFolderViewModel = AddEditBookmarkFolderDialogViewModel(mode: .add(parentFolder: nil), bookmarkManager: bookmarkManager) + let viewModel = AddEditBookmarkDialogCoordinatorViewModel(bookmarkModel: viewModel, folderModel: addFolderViewModel) + return AddEditBookmarkDialogView(viewModel: viewModel) + } + +} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderModalViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderModalViewModel.swift deleted file mode 100644 index ac2701e3ca..0000000000 --- a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderModalViewModel.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// AddBookmarkFolderModalViewModel.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -struct AddBookmarkFolderModalViewModel { - - let bookmarkManager: BookmarkManager - - let title: String - let addButtonTitle: String - let originalFolder: BookmarkFolder? - let parent: BookmarkFolder? - - var folderName: String = "" - - var isAddButtonDisabled: Bool { - folderName.trimmingWhitespace().isEmpty - } - - init(folder: BookmarkFolder, - bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, - completionHandler: @escaping (BookmarkFolder?) -> Void = { _ in }) { - self.bookmarkManager = bookmarkManager - self.folderName = folder.title - self.originalFolder = folder - self.parent = nil - self.title = UserText.renameFolder - self.addButtonTitle = UserText.save - } - - init(parent: BookmarkFolder? = nil, - bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, - completionHandler: @escaping (BookmarkFolder?) -> Void = { _ in }) { - self.bookmarkManager = bookmarkManager - self.originalFolder = nil - self.parent = parent - self.title = UserText.newFolder - self.addButtonTitle = UserText.newFolderDialogAdd - } - - func cancel(dismiss: () -> Void) { - dismiss() - } - - func addFolder(dismiss: () -> Void) { - guard !folderName.isEmpty else { - assertionFailure("folderName is empty, button should be disabled") - return - } - - let folderName = folderName.trimmingWhitespace() - if let folder = originalFolder { - folder.title = folderName - bookmarkManager.update(folder: folder) - - } else { - bookmarkManager.makeFolder(for: folderName, parent: parent, completion: { _ in }) - } - - dismiss() - } - -} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift index c16625a362..a1359fa61b 100644 --- a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift +++ b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkFolderPopoverViewModel.swift @@ -22,15 +22,30 @@ import Foundation final class AddBookmarkFolderPopoverViewModel: ObservableObject { private let bookmarkManager: BookmarkManager - let folders: [FolderViewModel] - @Published var parent: BookmarkFolder? + private let completionHandler: (BookmarkFolder?) -> Void + @Published var parent: BookmarkFolder? @Published var folderName: String = "" - @Published private(set) var isDisabled = false - private let completionHandler: (BookmarkFolder?) -> Void + var title: String { + UserText.Bookmarks.Dialog.Title.addFolder + } + + var cancelActionTitle: String { + UserText.cancel + } + + var defaultActionTitle: String { + UserText.Bookmarks.Dialog.Action.addFolder + } + + let isCancelActionDisabled = false + + var isDefaultActionButtonDisabled: Bool { + folderName.trimmingWhitespace().isEmpty || isDisabled + } init(bookmark: Bookmark? = nil, folderName: String = "", diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkModalViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkModalViewModel.swift deleted file mode 100644 index 15554afcbc..0000000000 --- a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkModalViewModel.swift +++ /dev/null @@ -1,127 +0,0 @@ -// -// AddBookmarkModalViewModel.swift -// -// Copyright © 2024 DuckDuckGo. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation - -struct AddBookmarkModalViewModel { - - let bookmarkManager: BookmarkManager - - let title: String - let addButtonTitle: String - var isFavorite: Bool - - private let originalBookmark: Bookmark? - private let parent: BookmarkFolder? - - private let completionHandler: (Bookmark?) -> Void - - var bookmarkTitle: String = "" - var bookmarkAddress: String = "" - - private var hasValidInput: Bool { - guard let url = bookmarkAddress.url else { return false } - - return !bookmarkTitle.trimmingWhitespace().isEmpty && url.isValid - } - - var isAddButtonDisabled: Bool { !hasValidInput } - - func cancel(dismiss: () -> Void) { - completionHandler(nil) - dismiss() - } - - func addOrSave(dismiss: () -> Void) { - guard let url = bookmarkAddress.url else { - assertionFailure("invalid URL, button should be disabled") - return - } - - var result: Bookmark? - let bookmarkTitle = bookmarkTitle.trimmingWhitespace() - if var bookmark = originalBookmark ?? bookmarkManager.getBookmark(for: url) { - - if url.absoluteString != bookmark.url { - bookmark = bookmarkManager.updateUrl(of: bookmark, to: url) ?? bookmark - } - if bookmark.title != bookmarkTitle || bookmark.isFavorite != isFavorite { - bookmark.title = bookmarkTitle - bookmark.isFavorite = isFavorite - bookmarkManager.update(bookmark: bookmark) - } - - result = bookmark - - } else if !bookmarkManager.isUrlBookmarked(url: url) { - result = bookmarkManager.makeBookmark(for: url, title: bookmarkTitle, isFavorite: isFavorite, index: nil, parent: parent) - } - - completionHandler(result) - dismiss() - } - - init(isFavorite: Bool = false, - bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, - currentTabWebsite website: WebsiteInfo? = nil, - parent: BookmarkFolder? = nil, - completionHandler: @escaping (Bookmark?) -> Void = { _ in }) { - - self.bookmarkManager = bookmarkManager - - self.isFavorite = isFavorite - self.title = isFavorite ? UserText.addFavorite : UserText.newBookmark - self.addButtonTitle = UserText.bookmarkDialogAdd - - if let website, - !LocalBookmarkManager.shared.isUrlBookmarked(url: website.url) { - bookmarkTitle = website.title ?? "" - bookmarkAddress = website.url.absoluteString - } - self.parent = parent - self.originalBookmark = nil - - self.completionHandler = completionHandler - } - - init(bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, - originalBookmark: Bookmark?, - isFavorite: Bool = false, - completionHandler: @escaping (Bookmark?) -> Void = { _ in }) { - - self.bookmarkManager = bookmarkManager - - self.isFavorite = isFavorite - if originalBookmark != nil { - self.title = isFavorite ? UserText.editFavorite : UserText.updateBookmark - self.addButtonTitle = UserText.save - } else { - self.title = isFavorite ? UserText.addFavorite : UserText.newBookmark - self.addButtonTitle = UserText.bookmarkDialogAdd - } - - self.parent = nil - self.originalBookmark = originalBookmark - - bookmarkTitle = originalBookmark?.title ?? "" - bookmarkAddress = originalBookmark?.url ?? "" - - self.completionHandler = completionHandler - } - -} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift index b474e148b3..88168901ed 100644 --- a/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift +++ b/DuckDuckGo/Bookmarks/ViewModel/AddBookmarkPopoverViewModel.swift @@ -38,8 +38,24 @@ final class AddBookmarkPopoverViewModel: ObservableObject { } } + @Published var isBookmarkFavorite: Bool { + didSet { + bookmark.isFavorite = isBookmarkFavorite + bookmarkManager.update(bookmark: bookmark) + } + } + + @Published var bookmarkTitle: String { + didSet { + bookmark.title = bookmarkTitle.trimmingWhitespace() + bookmarkManager.update(bookmark: bookmark) + } + } + @Published var addFolderViewModel: AddBookmarkFolderPopoverViewModel? + let isDefaultActionButtonDisabled: Bool = false + private var bookmarkListCancellable: AnyCancellable? init(bookmark: Bookmark, @@ -47,6 +63,7 @@ final class AddBookmarkPopoverViewModel: ObservableObject { self.bookmarkManager = bookmarkManager self.bookmark = bookmark self.bookmarkTitle = bookmark.title + self.isBookmarkFavorite = bookmark.isFavorite bookmarkListCancellable = bookmarkManager.listPublisher .receive(on: DispatchQueue.main) @@ -74,12 +91,6 @@ final class AddBookmarkPopoverViewModel: ObservableObject { dismiss() } - func favoritesButtonAction() { - bookmark.isFavorite.toggle() - - bookmarkManager.update(bookmark: bookmark) - } - func addFolderButtonAction() { addFolderViewModel = .init(bookmark: bookmark, bookmarkManager: bookmarkManager) { [bookmark, bookmarkManager, weak self] newFolder in if let newFolder { @@ -98,14 +109,6 @@ final class AddBookmarkPopoverViewModel: ObservableObject { addFolderViewModel = nil } - @Published var bookmarkTitle: String { - didSet { - bookmark.title = bookmarkTitle.trimmingWhitespace() - - bookmarkManager.update(bookmark: bookmark) - } - } - } struct FolderViewModel: Identifiable, Equatable { diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogCoordinatorViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogCoordinatorViewModel.swift new file mode 100644 index 0000000000..27fc0c64e5 --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogCoordinatorViewModel.swift @@ -0,0 +1,74 @@ +// +// AddEditBookmarkDialogCoordinatorViewModel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import Combine + +final class AddEditBookmarkDialogCoordinatorViewModel: ObservableObject { + @ObservedObject var bookmarkModel: BookmarkViewModel + @ObservedObject var folderModel: AddFolderViewModel + @Published var viewState: ViewState + + private var cancellables: Set = [] + + init(bookmarkModel: BookmarkViewModel, folderModel: AddFolderViewModel) { + self.bookmarkModel = bookmarkModel + self.folderModel = folderModel + viewState = .bookmark + bind() + } + + func dismissAction() { + viewState = .bookmark + } + + func addFolderAction() { + folderModel.selectedFolder = bookmarkModel.selectedFolder + viewState = .folder + } + + private func bind() { + bookmarkModel.objectWillChange + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.objectWillChange.send() + } + .store(in: &cancellables) + + folderModel.objectWillChange + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.objectWillChange.send() + } + .store(in: &cancellables) + + folderModel.addFolderPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] bookmarkFolder in + self?.bookmarkModel.selectedFolder = bookmarkFolder + } + .store(in: &cancellables) + } +} + +extension AddEditBookmarkDialogCoordinatorViewModel { + enum ViewState { + case bookmark + case folder + } +} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogViewModel.swift new file mode 100644 index 0000000000..0aa03eade1 --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkDialogViewModel.swift @@ -0,0 +1,216 @@ +// +// AddEditBookmarkDialogViewModel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine + +@MainActor +protocol BookmarkDialogEditing: BookmarksDialogViewModel { + var bookmarkName: String { get set } + var bookmarkURLPath: String { get set } + var isBookmarkFavorite: Bool { get set } + + var isURLFieldHidden: Bool { get } +} + +@MainActor +final class AddEditBookmarkDialogViewModel: BookmarkDialogEditing { + + /// The type of operation to perform on a bookmark. + enum Mode { + /// Add a new bookmark. Bookmarks can have a parent folder but not necessarily. + /// If the users add a bookmark to the root `Bookmarks` folder, then the parent folder is `nil`. + /// If the users add a bookmark to a different folder then the parent folder is not `nil`. + /// If the users add a bookmark from the bookmark shortcut and `Tab` has a page loaded, then the `tabWebsite` is not `nil`. + /// When adding a bookmark from favorite screen the `shouldPresetFavorite` flag should be set to `true`. + case add(tabWebsite: WebsiteInfo? = nil, parentFolder: BookmarkFolder? = nil, shouldPresetFavorite: Bool = false) + /// Edit an existing bookmark. + case edit(bookmark: Bookmark) + } + + @Published var bookmarkName: String + @Published var bookmarkURLPath: String + @Published var isBookmarkFavorite: Bool + + @Published private(set) var folders: [FolderViewModel] + @Published var selectedFolder: BookmarkFolder? + + private var folderCancellable: AnyCancellable? + + var title: String { + mode.title + } + + let isURLFieldHidden: Bool = false + + var cancelActionTitle: String { + mode.cancelActionTitle + } + + var defaultActionTitle: String { + mode.defaultActionTitle + } + + private var hasValidInput: Bool { + guard let url = bookmarkURLPath.url else { return false } + return !bookmarkName.trimmingWhitespace().isEmpty && url.isValid + } + + let isOtherActionDisabled: Bool = false + + var isDefaultActionDisabled: Bool { !hasValidInput } + + private let mode: Mode + private let bookmarkManager: BookmarkManager + + init(mode: Mode, bookmarkManager: LocalBookmarkManager = .shared) { + let isFavorite = mode.bookmarkURL.flatMap(bookmarkManager.isUrlFavorited) ?? false + self.mode = mode + self.bookmarkManager = bookmarkManager + folders = .init(bookmarkManager.list) + switch mode { + case let .add(websiteInfo, parentFolder, shouldPresetFavorite): + // When adding a new bookmark with website info we need to show the bookmark name and URL only if the bookmark is not bookmarked already. + // Scenario we click on the "Add Bookmark" button from Bookmarks shortcut Panel. If Tab has a Bookmark loaded we present the dialog with prepopulated name and URL from the tab. + // If we save and click again on the "Add Bookmark" button we don't want to try re-add the same bookmark. Hence we present a dialog that is not pre-populated. + let isAlreadyBookmarked = websiteInfo.flatMap { bookmarkManager.isUrlBookmarked(url: $0.url) } ?? false + let websiteName = isAlreadyBookmarked ? "" : websiteInfo?.title ?? "" + let websiteURLPath = isAlreadyBookmarked ? "" : websiteInfo?.url.absoluteString ?? "" + bookmarkName = websiteName + bookmarkURLPath = websiteURLPath + isBookmarkFavorite = shouldPresetFavorite ? true : isFavorite + selectedFolder = parentFolder + case let .edit(bookmark): + bookmarkName = bookmark.title + bookmarkURLPath = bookmark.urlObject?.absoluteString ?? "" + isBookmarkFavorite = isFavorite + selectedFolder = folders.first(where: { $0.id == bookmark.parentFolderUUID })?.entity + } + + bind() + } + + func cancel(dismiss: () -> Void) { + dismiss() + } + + func addOrSave(dismiss: () -> Void) { + guard let url = bookmarkURLPath.url else { + assertionFailure("Invalid URL, default action button should be disabled.") + return + } + + let trimmedBookmarkName = bookmarkName.trimmingWhitespace() + + switch mode { + case .add: + addBookmark(withURL: url, name: trimmedBookmarkName, isFavorite: isBookmarkFavorite, to: selectedFolder) + case let .edit(bookmark): + updateBookmark(bookmark, url: url, name: trimmedBookmarkName, isFavorite: isBookmarkFavorite, location: selectedFolder) + } + dismiss() + } +} + +private extension AddEditBookmarkDialogViewModel { + + func bind() { + folderCancellable = bookmarkManager.listPublisher + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] bookmarkList in + self?.folders = .init(bookmarkList) + }) + } + + func updateBookmark(_ bookmark: Bookmark, url: URL, name: String, isFavorite: Bool, location: BookmarkFolder?) { + // If the URL or Title or Favorite is changed update bookmark + if bookmark.url != url.absoluteString || bookmark.title != name || bookmark.isFavorite != isBookmarkFavorite { + bookmarkManager.update(bookmark: bookmark, withURL: url, title: name, isFavorite: isFavorite) + } + + // If the bookmark changed parent location, move it. + if shouldMove(bookmark: bookmark) { + let parentFolder: ParentFolderType = selectedFolder.flatMap { .parent(uuid: $0.id) } ?? .root + bookmarkManager.move(objectUUIDs: [bookmark.id], toIndex: nil, withinParentFolder: parentFolder, completion: { _ in }) + } + } + + func addBookmark(withURL url: URL, name: String, isFavorite: Bool, to parent: BookmarkFolder?) { + // If a bookmark already exist with the new URL, update it + if let existingBookmark = bookmarkManager.getBookmark(for: url) { + updateBookmark(existingBookmark, url: url, name: name, isFavorite: isFavorite, location: parent) + } else { + bookmarkManager.makeBookmark(for: url, title: name, isFavorite: isFavorite, index: nil, parent: parent) + } + } + + func shouldMove(bookmark: Bookmark) -> Bool { + // There's a discrepancy in representing the root folder. A bookmark belonging to the root folder has `parentFolderUUID` equal to `bookmarks_root`. + // There's no `BookmarkFolder` to represent the root folder, so the root folder is represented by a nil selectedFolder. + // Move Bookmarks if its parent folder is != from the selected folder but ONLY if: + // - The selected folder is not nil. This ensure we're comparing a subfolder with any bookmark parent folder. + // - The selected folder is nil and the bookmark parent folder is not the root folder. This ensure we're not unnecessarily moving the items within the same root folder. + bookmark.parentFolderUUID != selectedFolder?.id && (selectedFolder != nil || selectedFolder == nil && !bookmark.isParentFolderRoot) + } +} + +private extension AddEditBookmarkDialogViewModel.Mode { + + var title: String { + switch self { + case .add: + return UserText.Bookmarks.Dialog.Title.addBookmark + case .edit: + return UserText.Bookmarks.Dialog.Title.editBookmark + } + } + + var cancelActionTitle: String { + switch self { + case .add, .edit: + return UserText.cancel + } + } + + var defaultActionTitle: String { + switch self { + case .add: + return UserText.Bookmarks.Dialog.Action.addBookmark + case .edit: + return UserText.save + } + } + + var bookmarkURL: URL? { + switch self { + case let .add(tabInfo, _, _): + return tabInfo?.url + case let .edit(bookmark): + return bookmark.urlObject + } + } + +} + +private extension Bookmark { + + var isParentFolderRoot: Bool { + parentFolderUUID == "bookmarks_root" + } + +} diff --git a/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkFolderDialogViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkFolderDialogViewModel.swift new file mode 100644 index 0000000000..62c1e0356c --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/AddEditBookmarkFolderDialogViewModel.swift @@ -0,0 +1,181 @@ +// +// AddEditBookmarkFolderDialogViewModel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine + +@MainActor +protocol BookmarkFolderDialogEditing: BookmarksDialogViewModel { + var addFolderPublisher: AnyPublisher { get } + var folderName: String { get set } +} + +@MainActor +final class AddEditBookmarkFolderDialogViewModel: BookmarkFolderDialogEditing { + + /// The type of operation to perform on a folder + enum Mode { + /// Add a new folder. Folders can have a parent folder but not necessarily. + /// If the users add a folder to a folder whose parent is the root `Bookmarks` folder, then the parent folder is `nil`. + /// If the users add a folder to a folder whose parent is not the root `Bookmarks` folder, then the parent folder is not `nil`. + case add(parentFolder: BookmarkFolder? = nil) + /// Edit an existing folder. Existing folder can have a parent folder but not necessarily. + /// If the users edit a folder whose parent is the root `Bookmarks` folder, then the parent folder is `nil` + /// If the users edit a folder whose parent is not the root `Bookmarks` folder, then the parent folder is not `nil`. + case edit(folder: BookmarkFolder, parentFolder: BookmarkFolder?) + } + + @Published var folderName: String + @Published var selectedFolder: BookmarkFolder? + + let folders: [FolderViewModel] + + var title: String { + mode.title + } + + var cancelActionTitle: String { + mode.cancelActionTitle + } + + var defaultActionTitle: String { + mode.defaultActionTitle + } + + let isOtherActionDisabled = false + + var isDefaultActionDisabled: Bool { + folderName.trimmingWhitespace().isEmpty + } + + var addFolderPublisher: AnyPublisher { + addFolderSubject.eraseToAnyPublisher() + } + + private let mode: Mode + private let bookmarkManager: BookmarkManager + private let addFolderSubject: PassthroughSubject = .init() + + init(mode: Mode, bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + self.mode = mode + self.bookmarkManager = bookmarkManager + folderName = mode.folderName + folders = .init(bookmarkManager.list) + selectedFolder = mode.parentFolder + } + + func cancel(dismiss: () -> Void) { + dismiss() + } + + func addOrSave(dismiss: () -> Void) { + defer { dismiss() } + + guard !folderName.isEmpty else { + assertionFailure("folderName is empty, button should be disabled") + return + } + + let folderName = folderName.trimmingWhitespace() + + switch mode { + case let .edit(folder, originalParent): + // If there are no pending changes dismiss + guard folder.title != folderName || selectedFolder?.id != originalParent?.id else { return } + // Otherwise update Folder. + update(folder: folder, originalParent: originalParent, newParent: selectedFolder) + case .add: + add(folderWithName: folderName, to: selectedFolder) + } + } + +} + +// MARK: - Private + +private extension AddEditBookmarkFolderDialogViewModel { + + func update(folder: BookmarkFolder, originalParent: BookmarkFolder?, newParent: BookmarkFolder?) { + // If the original location of the folder changed move it to the new folder. + if selectedFolder?.id != originalParent?.id { + // Update the title anyway. + folder.title = folderName + let parentFolderType: ParentFolderType = newParent.flatMap { ParentFolderType.parent(uuid: $0.id) } ?? .root + bookmarkManager.update(folder: folder, andMoveToParent: parentFolderType) + } else if folder.title != folderName { // If only title changed just update the folder title without updating its parent. + folder.title = folderName + bookmarkManager.update(folder: folder) + } + } + + func add(folderWithName name: String, to parent: BookmarkFolder?) { + bookmarkManager.makeFolder(for: name, parent: parent) { [weak self] bookmarkFolder in + self?.addFolderSubject.send(bookmarkFolder) + } + } + +} + +// MARK: - AddEditBookmarkFolderDialogViewModel.Mode + +private extension AddEditBookmarkFolderDialogViewModel.Mode { + + var title: String { + switch self { + case .add: + return UserText.Bookmarks.Dialog.Title.addFolder + case .edit: + return UserText.Bookmarks.Dialog.Title.editFolder + } + } + + var cancelActionTitle: String { + switch self { + case .add, .edit: + return UserText.cancel + } + } + + var defaultActionTitle: String { + switch self { + case .add: + return UserText.Bookmarks.Dialog.Action.addFolder + case .edit: + return UserText.save + } + } + + var folderName: String { + switch self { + case .add: + return "" + case let .edit(folder, _): + return folder.title + } + } + + var parentFolder: BookmarkFolder? { + switch self { + case let .add(parentFolder): + return parentFolder + case let .edit(_, parentFolder): + return parentFolder + } + } + +} diff --git a/DuckDuckGo/Bookmarks/ViewModel/BookmarksDialogViewModel.swift b/DuckDuckGo/Bookmarks/ViewModel/BookmarksDialogViewModel.swift new file mode 100644 index 0000000000..08ef2cc47d --- /dev/null +++ b/DuckDuckGo/Bookmarks/ViewModel/BookmarksDialogViewModel.swift @@ -0,0 +1,35 @@ +// +// BookmarksDialogViewModel.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@MainActor +protocol BookmarksDialogViewModel: ObservableObject { + var title: String { get } + + var folders: [FolderViewModel] { get } + var selectedFolder: BookmarkFolder? { get set } + + var cancelActionTitle: String { get } + var isOtherActionDisabled: Bool { get } + var defaultActionTitle: String { get } + var isDefaultActionDisabled: Bool { get } + + func cancel(dismiss: () -> Void) + func addOrSave(dismiss: () -> Void) +} diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarCollectionViewItem.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarCollectionViewItem.swift index 61c0ec7630..dd9d2e617f 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarCollectionViewItem.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarCollectionViewItem.swift @@ -24,11 +24,13 @@ protocol BookmarksBarCollectionViewItemDelegate: AnyObject { func bookmarksBarCollectionViewItemOpenInNewTabAction(_ item: BookmarksBarCollectionViewItem) func bookmarksBarCollectionViewItemOpenInNewWindowAction(_ item: BookmarksBarCollectionViewItem) - func bookmarksBarCollectionViewItemAddToFavoritesAction(_ item: BookmarksBarCollectionViewItem) + func bookmarksBarCollectionViewItemToggleFavoritesAction(_ item: BookmarksBarCollectionViewItem) func bookmarksBarCollectionViewEditAction(_ item: BookmarksBarCollectionViewItem) func bookmarksBarCollectionViewItemMoveToEndAction(_ item: BookmarksBarCollectionViewItem) func bookmarksBarCollectionViewItemCopyBookmarkURLAction(_ item: BookmarksBarCollectionViewItem) func bookmarksBarCollectionViewItemDeleteEntityAction(_ item: BookmarksBarCollectionViewItem) + func bookmarksBarCollectionViewItemAddEntityAction(_ item: BookmarksBarCollectionViewItem) + func bookmarksBarCollectionViewItemManageBookmarksAction(_ item: BookmarksBarCollectionViewItem) } @@ -128,114 +130,72 @@ extension BookmarksBarCollectionViewItem: NSMenuDelegate { switch entityType { case .bookmark(_, _, _, let isFavorite): - menu.items = createBookmarkMenuItems(isFavorite: isFavorite) + menu.items = ContextualMenu.bookmarkMenuItems(isFavorite: isFavorite) case .folder: - menu.items = createFolderMenuItems() + menu.items = ContextualMenu.folderMenuItems() } } } -extension BookmarksBarCollectionViewItem { +extension BookmarksBarCollectionViewItem: BookmarkMenuItemSelectors { - // MARK: Bookmark Menu Items - - func createBookmarkMenuItems(isFavorite: Bool) -> [NSMenuItem] { - let items = [ - openBookmarkInNewTabMenuItem(), - openBookmarkInNewWindowMenuItem(), - NSMenuItem.separator(), - addToFavoritesMenuItem(isFavorite: isFavorite), - editItem(), - moveToEndMenuItem(), - NSMenuItem.separator(), - copyBookmarkURLMenuItem(), - deleteEntityMenuItem() - ].compactMap { $0 } - - return items - } - - func openBookmarkInNewTabMenuItem() -> NSMenuItem { - return menuItem(UserText.openInNewTab, #selector(openBookmarkInNewTabMenuItemSelected(_:))) - } - - @objc - func openBookmarkInNewTabMenuItemSelected(_ sender: NSMenuItem) { + func openBookmarkInNewTab(_ sender: NSMenuItem) { delegate?.bookmarksBarCollectionViewItemOpenInNewTabAction(self) } - func openBookmarkInNewWindowMenuItem() -> NSMenuItem { - return menuItem(UserText.openInNewWindow, #selector(openBookmarkInNewWindowMenuItemSelected(_:))) - } - - @objc - func openBookmarkInNewWindowMenuItemSelected(_ sender: NSMenuItem) { + func openBookmarkInNewWindow(_ sender: NSMenuItem) { delegate?.bookmarksBarCollectionViewItemOpenInNewWindowAction(self) } - func addToFavoritesMenuItem(isFavorite: Bool) -> NSMenuItem? { - guard !isFavorite else { - return nil - } - - return menuItem(UserText.addToFavorites, #selector(addToFavoritesMenuItemSelected(_:))) - } - - @objc - func addToFavoritesMenuItemSelected(_ sender: NSMenuItem) { - delegate?.bookmarksBarCollectionViewItemAddToFavoritesAction(self) + func toggleBookmarkAsFavorite(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemToggleFavoritesAction(self) } - func editItem() -> NSMenuItem { - return menuItem("Edit…", #selector(editItemSelected(_:))) + func editBookmark(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewEditAction(self) } - @objc - func editItemSelected(_ sender: NSMenuItem) { - delegate?.bookmarksBarCollectionViewEditAction(self) + func copyBookmark(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemCopyBookmarkURLAction(self) } - func moveToEndMenuItem() -> NSMenuItem { - return menuItem(UserText.bookmarksBarContextMenuMoveToEnd, #selector(moveToEndMenuItemSelected(_:))) + func deleteBookmark(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemDeleteEntityAction(self) } - @objc - func moveToEndMenuItemSelected(_ sender: NSMenuItem) { + func moveToEnd(_ sender: NSMenuItem) { delegate?.bookmarksBarCollectionViewItemMoveToEndAction(self) } - func copyBookmarkURLMenuItem() -> NSMenuItem { - return menuItem(UserText.bookmarksBarContextMenuCopy, #selector(copyBookmarkURLMenuItemSelected(_:))) + func deleteEntities(_ sender: NSMenuItem) {} + + func manageBookmarks(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemManageBookmarksAction(self) } - @objc - func copyBookmarkURLMenuItemSelected(_ sender: NSMenuItem) { - delegate?.bookmarksBarCollectionViewItemCopyBookmarkURLAction(self) +} + +extension BookmarksBarCollectionViewItem: FolderMenuItemSelectors { + + func newFolder(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemAddEntityAction(self) } - func deleteEntityMenuItem() -> NSMenuItem { - return menuItem(UserText.bookmarksBarContextMenuDelete, #selector(deleteMenuItemSelected(_:))) + func editFolder(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewEditAction(self) } - @objc - func deleteMenuItemSelected(_ sender: NSMenuItem) { + func deleteFolder(_ sender: NSMenuItem) { delegate?.bookmarksBarCollectionViewItemDeleteEntityAction(self) } - // MARK: Folder Menu Items - - func createFolderMenuItems() -> [NSMenuItem] { - return [ - editItem(), - moveToEndMenuItem(), - NSMenuItem.separator(), - deleteEntityMenuItem() - ] + func openInNewTabs(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemOpenInNewTabAction(self) } - func menuItem(_ title: String, _ action: Selector) -> NSMenuItem { - return NSMenuItem(title: title, action: action, keyEquivalent: "") + func openAllInNewWindow(_ sender: NSMenuItem) { + delegate?.bookmarksBarCollectionViewItemOpenInNewWindowAction(self) } } diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift index 56eff5f57a..c8d9f6c016 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarMenuFactory.swift @@ -34,6 +34,13 @@ struct BookmarksBarMenuFactory { menu.addItem(makeMenuItem(prefs)) } + static func addToMenuWithManageBookmarksSection(_ menu: NSMenu, target: AnyObject, addFolderSelector: Selector, manageBookmarksSelector: Selector, prefs: AppearancePreferences = .shared) { + addToMenu(menu, prefs) + menu.addItem(.separator()) + menu.addItem(NSMenuItem(title: UserText.addFolder, action: addFolderSelector, target: target)) + menu.addItem(NSMenuItem(title: UserText.bookmarksManageBookmarks, action: manageBookmarksSelector, target: target)) + } + private static func makeMenuItem( _ prefs: AppearancePreferences) -> NSMenuItem { let item = NSMenuItem(title: UserText.showBookmarksBar, action: nil, keyEquivalent: "B") item.submenu = NSMenu(items: [ diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift index ea9b61e496..e298a6335e 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewController.swift @@ -223,59 +223,113 @@ extension BookmarksBarViewController: BookmarksBarViewModelDelegate { bookmarksBarCollectionView.reloadData() } - private func handle(_ action: BookmarksBarViewModel.BookmarksBarItemAction, for bookmark: Bookmark) { +} + +// MARK: - Private + +private extension BookmarksBarViewController { + + func handle(_ action: BookmarksBarViewModel.BookmarksBarItemAction, for bookmark: Bookmark) { switch action { case .openInNewTab: - guard let url = bookmark.urlObject else { return } - tabCollectionViewModel.appendNewTab(with: .url(url, source: .bookmark), selected: true) + openInNewTab(bookmark: bookmark) case .openInNewWindow: - guard let url = bookmark.urlObject else { return } - WindowsManager.openNewWindow(with: url, source: .bookmark, isBurner: false) + openInNewWindow(bookmark: bookmark) case .clickItem: WindowControllersManager.shared.open(bookmark: bookmark) - case .addToFavorites: - bookmark.isFavorite = true + case .toggleFavorites: + bookmark.isFavorite.toggle() bookmarkManager.update(bookmark: bookmark) case .edit: - AddBookmarkModalView(model: AddBookmarkModalViewModel(originalBookmark: bookmark)) - .show(in: view.window) + showDialog(view: BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark)) case .moveToEnd: bookmarkManager.move(objectUUIDs: [bookmark.id], toIndex: nil, withinParentFolder: .root) { _ in } case .copyURL: bookmark.copyUrlToPasteboard() case .deleteEntity: bookmarkManager.remove(bookmark: bookmark) + case .addFolder: + addFolder(inParent: nil) + case .manageBookmarks: + manageBookmarks() } } - private func handle(_ action: BookmarksBarViewModel.BookmarksBarItemAction, for folder: BookmarkFolder, item: BookmarksBarCollectionViewItem) { + func handle(_ action: BookmarksBarViewModel.BookmarksBarItemAction, for folder: BookmarkFolder, item: BookmarksBarCollectionViewItem) { switch action { case .clickItem: - let childEntities = folder.children - let viewModels = childEntities.map { BookmarkViewModel(entity: $0) } - let menuItems = viewModel.bookmarksTreeMenuItems(from: viewModels, topLevel: true) - let menu = bookmarkFolderMenu(items: menuItems) - - menu.popUp(positioning: nil, at: CGPoint(x: 0, y: item.view.frame.minY - 7), in: item.view) + showSubmenuFor(folder: folder, fromView: item.view) case .edit: - AddBookmarkFolderModalView(model: AddBookmarkFolderModalViewModel(folder: folder)) - .show(in: view.window) + showDialog(view: BookmarksDialogViewFactory.makeEditBookmarkFolderView(folder: folder, parentFolder: nil)) case .moveToEnd: bookmarkManager.move(objectUUIDs: [folder.id], toIndex: nil, withinParentFolder: .root) { _ in } case .deleteEntity: bookmarkManager.remove(folder: folder) + case .addFolder: + addFolder(inParent: folder) + case .openInNewTab: + openAllInNewTabs(folder: folder) + case .openInNewWindow: + openAllInNewWindow(folder: folder) + case .manageBookmarks: + manageBookmarks() default: assertionFailure("Received unexpected action for bookmark folder") } } - private func bookmarkFolderMenu(items: [NSMenuItem]) -> NSMenu { + func bookmarkFolderMenu(items: [NSMenuItem]) -> NSMenu { let menu = NSMenu() menu.items = items.isEmpty ? [NSMenuItem.empty] : items menu.autoenablesItems = false return menu } + func openInNewTab(bookmark: Bookmark) { + guard let url = bookmark.urlObject else { return } + tabCollectionViewModel.appendNewTab(with: .url(url, source: .bookmark), selected: true) + } + + func openInNewWindow(bookmark: Bookmark) { + guard let url = bookmark.urlObject else { return } + WindowsManager.openNewWindow(with: url, source: .bookmark, isBurner: false) + } + + func openAllInNewTabs(folder: BookmarkFolder) { + let tabs = Tab.withContentOfBookmark(folder: folder, burnerMode: tabCollectionViewModel.burnerMode) + tabCollectionViewModel.append(tabs: tabs) + } + + func openAllInNewWindow(folder: BookmarkFolder) { + let tabCollection = TabCollection.withContentOfBookmark(folder: folder, burnerMode: tabCollectionViewModel.burnerMode) + WindowsManager.openNewWindow(with: tabCollection, isBurner: tabCollectionViewModel.isBurner) + } + + func showSubmenuFor(folder: BookmarkFolder, fromView view: NSView) { + let childEntities = folder.children + let viewModels = childEntities.map { BookmarkViewModel(entity: $0) } + let menuItems = viewModel.bookmarksTreeMenuItems(from: viewModels, topLevel: true) + let menu = bookmarkFolderMenu(items: menuItems) + + menu.popUp(positioning: nil, at: CGPoint(x: 0, y: view.frame.minY - 7), in: view) + } + + func addFolder(inParent parent: BookmarkFolder?) { + showDialog(view: BookmarksDialogViewFactory.makeAddBookmarkFolderView(parentFolder: parent)) + } + + func showDialog(view: any ModalView) { + view.show(in: self.view.window) + } + + @objc func manageBookmarks() { + WindowControllersManager.shared.showBookmarksTab() + } + + @objc func addFolder(sender: NSMenuItem) { + addFolder(inParent: nil) + } + } // MARK: - Menu @@ -284,7 +338,12 @@ extension BookmarksBarViewController: NSMenuDelegate { public func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() - BookmarksBarMenuFactory.addToMenu(menu) + BookmarksBarMenuFactory.addToMenuWithManageBookmarksSection( + menu, + target: self, + addFolderSelector: #selector(addFolder(sender:)), + manageBookmarksSelector: #selector(manageBookmarks) + ) } } diff --git a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewModel.swift b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewModel.swift index 2dc81a1596..1c82b4c576 100644 --- a/DuckDuckGo/BookmarksBar/View/BookmarksBarViewModel.swift +++ b/DuckDuckGo/BookmarksBar/View/BookmarksBarViewModel.swift @@ -47,11 +47,13 @@ final class BookmarksBarViewModel: NSObject { case clickItem case openInNewTab case openInNewWindow - case addToFavorites + case toggleFavorites case edit case moveToEnd case copyURL case deleteEntity + case addFolder + case manageBookmarks } struct BookmarksBarItem { @@ -482,8 +484,8 @@ extension BookmarksBarViewModel: BookmarksBarCollectionViewItemDelegate { delegate?.bookmarksBarViewModelReceived(action: .openInNewWindow, for: item) } - func bookmarksBarCollectionViewItemAddToFavoritesAction(_ item: BookmarksBarCollectionViewItem) { - delegate?.bookmarksBarViewModelReceived(action: .addToFavorites, for: item) + func bookmarksBarCollectionViewItemToggleFavoritesAction(_ item: BookmarksBarCollectionViewItem) { + delegate?.bookmarksBarViewModelReceived(action: .toggleFavorites, for: item) } func bookmarksBarCollectionViewEditAction(_ item: BookmarksBarCollectionViewItem) { @@ -502,4 +504,12 @@ extension BookmarksBarViewModel: BookmarksBarCollectionViewItemDelegate { delegate?.bookmarksBarViewModelReceived(action: .deleteEntity, for: item) } + func bookmarksBarCollectionViewItemAddEntityAction(_ item: BookmarksBarCollectionViewItem) { + delegate?.bookmarksBarViewModelReceived(action: .addFolder, for: item) + } + + func bookmarksBarCollectionViewItemManageBookmarksAction(_ item: BookmarksBarCollectionViewItem) { + delegate?.bookmarksBarViewModelReceived(action: .manageBookmarks, for: item) + } + } diff --git a/DuckDuckGo/Common/Extensions/BundleExtension.swift b/DuckDuckGo/Common/Extensions/BundleExtension.swift index 0a9fbafcf6..b3c7a629f5 100644 --- a/DuckDuckGo/Common/Extensions/BundleExtension.swift +++ b/DuckDuckGo/Common/Extensions/BundleExtension.swift @@ -95,17 +95,7 @@ extension Bundle { #endif func appGroup(bundle: BundleGroup) -> String { - var appGroupName: String - - switch bundle { - case .dbp: - appGroupName = "DBP_APP_GROUP" - case .netP: - appGroupName = "NETP_APP_GROUP" - case .subs: - appGroupName = "SUBSCRIPTION_APP_GROUP" - } - + let appGroupName = bundle.appGroupKey guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { fatalError("Info.plist is missing \(appGroupName)") } @@ -131,4 +121,15 @@ enum BundleGroup { case netP case dbp case subs + + var appGroupKey: String { + switch self { + case .dbp: + return "DBP_APP_GROUP" + case .netP: + return "NETP_APP_GROUP" + case .subs: + return "SUBSCRIPTION_APP_GROUP" + } + } } diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 8aa901b548..554ce896e5 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -38,8 +38,10 @@ extension NSAlert { alert.informativeText = UserText.clearAllDataDescription alert.alertStyle = .warning alert.icon = .burnAlert - alert.addButton(withTitle: UserText.clear) - alert.addButton(withTitle: UserText.cancel) + let clearButton = alert.addButton(withTitle: UserText.clear) + let cancelButton = alert.addButton(withTitle: UserText.cancel) + clearButton.setAccessibilityIdentifier("ClearAllHistoryAndDataAlert.clearButton") + cancelButton.setAccessibilityIdentifier("ClearAllHistoryAndDataAlert.cancelButton") return alert } @@ -88,11 +90,11 @@ extension NSAlert { static func resetNetworkProtectionAlert() -> NSAlert { let alert = NSAlert() - alert.messageText = "Reset Network Protection?" + alert.messageText = "Reset VPN?" alert.informativeText = """ This will remove your stored network configuration (including private key) and disable the VPN. - You can re-enable the VPN from the Network Protection view. + You can re-enable the VPN from the status view. """ alert.alertStyle = .warning alert.addButton(withTitle: "Reset") @@ -108,7 +110,7 @@ extension NSAlert { let sysExText = "" #endif alert.messageText = "Uninstall \(sysExText)Login Items?" - alert.informativeText = "This will remove the Network Protection \(sysExText)Status Menu icon and disable the VPN." + alert.informativeText = "This will remove the VPN \(sysExText)Status Menu icon and disable the VPN." alert.alertStyle = .warning alert.addButton(withTitle: "Uninstall") alert.addButton(withTitle: UserText.cancel) diff --git a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift index 69043e9a3b..6790d5c4f7 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuExtension.swift @@ -58,4 +58,14 @@ extension NSMenu { insertItem(newItem, at: index) } + /// Pops up the menu at the current mouse location. + /// + /// - Parameter view: The view to display the menu item over. + /// - Attention: If the view is not currently installed in a window, this function does not show any pop up menu. + func popUpAtMouseLocation(in view: NSView) { + guard let cursorLocation = view.window?.mouseLocationOutsideOfEventStream else { return } + let convertedLocation = view.convert(cursorLocation, from: nil) + popUp(positioning: nil, at: convertedLocation, in: view) + } + } diff --git a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift index bfa2e24e09..b3f0870252 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift @@ -104,6 +104,12 @@ extension NSMenuItem { return self } + @discardableResult + func withAccessibilityIdentifier(_ accessibilityIdentifier: String) -> NSMenuItem { + self.setAccessibilityIdentifier(accessibilityIdentifier) + return self + } + @discardableResult func withImage(_ image: NSImage?) -> NSMenuItem { self.image = image diff --git a/DuckDuckGo/Common/Extensions/URLExtension.swift b/DuckDuckGo/Common/Extensions/URLExtension.swift index 1e367e4e6c..3f5b12e8f7 100644 --- a/DuckDuckGo/Common/Extensions/URLExtension.swift +++ b/DuckDuckGo/Common/Extensions/URLExtension.swift @@ -340,13 +340,21 @@ extension URL { } static var cookieConsentPopUpManagement: URL { - return URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#cookie-consent-pop-up-management")! + return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#cookie-pop-up-management")! } static var gpcLearnMore: URL { return URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/gpc/")! } + static var privateSearchLearnMore: URL { + return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/search-privacy/")! + } + + static var searchSettings: URL { + return URL(string: "https://duckduckgo.com/settings/")! + } + static var ddgLearnMore: URL { return URL(string: "https://duckduckgo.com/duckduckgo-help-pages/get-duckduckgo/get-duckduckgo-browser-on-mac/")! } @@ -362,6 +370,7 @@ extension URL { static var duckDuckGoEmail = URL(string: "https://duckduckgo.com/email-protection")! static var duckDuckGoEmailLogin = URL(string: "https://duckduckgo.com/email")! + static var duckDuckGoEmailInfo = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/email-protection/what-is-duckduckgo-email-protection/")! static var duckDuckGoMorePrivacyInfo = URL(string: "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/atb/")! var isDuckDuckGo: Bool { diff --git a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift index 8653bba9e2..69d36bb228 100644 --- a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift +++ b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift @@ -21,37 +21,39 @@ import Foundation extension UserText { // "network.protection.tunnel.name" - The name of the NetP VPN that will be visible in the system to the user - static let networkProtectionTunnelName = "DuckDuckGo Network Protection" - // "network.protection" - Menu item for opening Network Protection - static let networkProtection = "Network Protection" + static let networkProtectionTunnelName = "DuckDuckGo VPN" + // "network.protection" - Menu item for opening the VPN + static let networkProtection = "VPN" // MARK: - Navigation Bar // "network.protection.status.button.tooltip" - The tooltip for NetP's nav bar button - static let networkProtectionButtonTooltip = "Network Protection" + static let networkProtectionButtonTooltip = "VPN" // MARK: - Invite Code - // "network.protection.invite.dialog.title" - Title for the network protection invite dialog + // "network.protection.invite.dialog.title" - Title for the VPN invite dialog static let networkProtectionInviteDialogTitle = "Enter your invite code" - // "network.protection.invite.dialog.message" - Message for the network protection invite dialog + // "network.protection.invite.dialog.message" - Message for the VPN invite dialog static let networkProtectionInviteDialogMessage = "Enter your invite code to get started." - // "network.protection.invite.field.prompt" - Prompt for the network protection invite code text field + // "network.protection.invite.field.prompt" - Prompt for the VPN invite code text field static let networkProtectionInviteFieldPrompt = "Code" - // "network.protection.invite.success.title" - Title for the network protection invite success view + // "network.protection.invite.success.title" - Title for the VPN invite success view static let networkProtectionInviteSuccessTitle = "Success! You’re in." - // "network.protection.invite.success.title" - Message for the network protection invite success view + // "network.protection.invite.success.title" - Message for the VPN invite success view static let networkProtectionInviteSuccessMessage = "DuckDuckGo's VPN secures all of your device's Internet traffic anytime, anywhere." // MARK: - Navigation Bar Status View - // "network.protection.navbar.status.view.share.feedback" - Menu item for 'Send VPN Feedback' in the Network Protection status view that's shown in the navigation bar + // "network.protection.navbar.status.view.share.feedback" - Menu item for 'Send VPN Feedback' in the VPN status view that's shown in the navigation bar static let networkProtectionNavBarStatusViewShareFeedback = "Send VPN Feedback…" // "network.protection.status.menu.vpn.settings" - The status menu 'VPN Settings' menu item static let networkProtectionNavBarStatusMenuVPNSettings = "VPN Settings…" + // "network.protection.status.menu.faq" - The status menu 'FAQ' menu item + static let networkProtectionNavBarStatusMenuFAQ = "Frequently Asked Questions…" // MARK: - System Extension Installation Messages - // "network.protection.configuration.system-settings.legacy" - Text for a label in the Network Protection popover, displayed after attempting to enable Network Protection for the first time while using macOS 12 and below - private static let networkProtectionSystemSettingsLegacy = "Go to Security & Privacy in System Preferences to allow Network Protection to activate" - // "network.protection.configuration.system-settings.modern" - Text for a label in the Network Protection popover, displayed after attempting to enable Network Protection for the first time while using macOS 13 and above - private static let networkProtectionSystemSettingsModern = "Go to Privacy & Security in System Settings to allow Network Protection to activate" + // "network.protection.configuration.system-settings.legacy" - Text for a label in the VPN popover, displayed after attempting to enable the VPN for the first time while using macOS 12 and below + private static let networkProtectionSystemSettingsLegacy = "Go to Security & Privacy in System Preferences to allow DuckDuckGo VPN to activate" + // "network.protection.configuration.system-settings.modern" - Text for a label in the VPN popover, displayed after attempting to enable the VPN for the first time while using macOS 13 and above + private static let networkProtectionSystemSettingsModern = "Go to Privacy & Security in System Settings to allow DuckDuckGo VPN to activate" // Dynamically selected based on macOS version, not directly convertible to static string static var networkProtectionSystemSettings: String { @@ -65,161 +67,161 @@ extension UserText { // "network.protection.system.extension.unknown.activation.error" - Message shown to users when they try to enable NetP and there is an unexpected activation error. static let networkProtectionUnknownActivationError = "There as an unexpected error. Please try again." // "network.protection.system.extension.please.reboot" - Message shown to users when they try to enable NetP and they need to reboot the computer to complete the installation - static let networkProtectionPleaseReboot = "Please reboot to activate Network Protection" + static let networkProtectionPleaseReboot = "Please reboot to activate the VPN" } -// MARK: - Network Protection Waitlist +// MARK: - VPN Waitlist extension UserText { - // "network-protection.waitlist.notification.title" - Title for Network Protection waitlist notification - static let networkProtectionWaitlistNotificationTitle = "Network Protection beta is ready!" - // "network-protection.waitlist.notification.text" - Title for Network Protection waitlist notification + // "network-protection.waitlist.notification.title" - Title for VPN waitlist notification + static let networkProtectionWaitlistNotificationTitle = "DuckDuckGo VPN beta is ready!" + // "network-protection.waitlist.notification.text" - Title for VPN waitlist notification static let networkProtectionWaitlistNotificationText = "Open your invite" - // "network-protection.waitlist.join.title" - Title for Network Protection join waitlist screen - static let networkProtectionWaitlistJoinTitle = "Network Protection Beta" - // "network-protection.waitlist.join.subtitle.1" - First subtitle for Network Protection join waitlist screen - static let networkProtectionWaitlistJoinSubtitle1 = "Secure your connection anytime, anywhere with Network Protection, the VPN from DuckDuckGo." - // "network-protection.waitlist.join.subtitle.2" - Second subtitle for Network Protection join waitlist screen + // "network-protection.waitlist.join.title" - Title for VPN join waitlist screen + static let networkProtectionWaitlistJoinTitle = "DuckDuckGo VPN Beta" + // "network-protection.waitlist.join.subtitle.1" - First subtitle for VPN join waitlist screen + static let networkProtectionWaitlistJoinSubtitle1 = "Secure your connection anytime, anywhere with DuckDuckGo VPN." + // "network-protection.waitlist.join.subtitle.2" - Second subtitle for VPN join waitlist screen static let networkProtectionWaitlistJoinSubtitle2 = "Join the waitlist, and we’ll notify you when it’s your turn." - // "network-protection.waitlist.joined.title" - Title for Network Protection joined waitlist screen + // "network-protection.waitlist.joined.title" - Title for VPN joined waitlist screen static let networkProtectionWaitlistJoinedTitle = "You’re on the list!" - // "network-protection.waitlist.joined.with-notifications.subtitle.1" - Subtitle 1 for Network Protection joined waitlist screen when notifications are enabled + // "network-protection.waitlist.joined.with-notifications.subtitle.1" - Subtitle 1 for VPN joined waitlist screen when notifications are enabled static let networkProtectionWaitlistJoinedWithNotificationsSubtitle1 = "New invites are sent every few days, on a first come, first served basis." - // "network-protection.waitlist.joined.with-notifications.subtitle.2" - Subtitle 2 for Network Protection joined waitlist screen when notifications are enabled + // "network-protection.waitlist.joined.with-notifications.subtitle.2" - Subtitle 2 for VPN joined waitlist screen when notifications are enabled static let networkProtectionWaitlistJoinedWithNotificationsSubtitle2 = "We’ll notify you when your invite is ready." - // "network-protection.waitlist.enable-notifications" - Enable notifications prompt for Network Protection joined waitlist screen - static let networkProtectionWaitlistEnableNotifications = "Want to get a notification when your Network Protection invite is ready?" + // "network-protection.waitlist.enable-notifications" - Enable notifications prompt for VPN joined waitlist screen + static let networkProtectionWaitlistEnableNotifications = "Want to get a notification when your VPN invite is ready?" - // "network-protection.waitlist.invited.title" - Title for Network Protection invited screen - static let networkProtectionWaitlistInvitedTitle = "You’re invited to try\nNetwork Protection beta!" - // "network-protection.waitlist.invited.subtitle" - Subtitle for Network Protection invited screen + // "network-protection.waitlist.invited.title" - Title for VPN invited screen + static let networkProtectionWaitlistInvitedTitle = "You’re invited to try\nDuckDuckGo VPN beta!" + // "network-protection.waitlist.invited.subtitle" - Subtitle for VPN invited screen static let networkProtectionWaitlistInvitedSubtitle = "Get an extra layer of protection online with the VPN built for speed and simplicity. Encrypt your internet connection across your entire device and hide your location and IP address from sites you visit." - // "network-protection.waitlist.invited.section-1.title" - Title for section 1 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-1.title" - Title for section 1 of the VPN invited screen static let networkProtectionWaitlistInvitedSection1Title = "Full-device coverage" - // "network-protection.waitlist.invited.section-1.subtitle" - Subtitle for section 1 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-1.subtitle" - Subtitle for section 1 of the VPN invited screen static let networkProtectionWaitlistInvitedSection1Subtitle = "Encrypt online traffic across your browsers and apps." - // "network-protection.waitlist.invited.section-2.title" - Title for section 2 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-2.title" - Title for section 2 of the VPN invited screen static let networkProtectionWaitlistInvitedSection2Title = "Fast, reliable, and easy to use" - // "network-protection.waitlist.invited.section-2.subtitle" - Subtitle for section 2 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-2.subtitle" - Subtitle for section 2 of the VPN invited screen static let networkProtectionWaitlistInvitedSection2Subtitle = "No need for a separate app. Connect in one click and see your connection status at a glance." - // "network-protection.waitlist.invited.section-3.title" - Title for section 3 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-3.title" - Title for section 3 of the VPN invited screen static let networkProtectionWaitlistInvitedSection3Title = "Strict no-logging policy" - // "network-protection.waitlist.invited.section-3.subtitle" - Subtitle for section 3 of the Network Protection invited screen + // "network-protection.waitlist.invited.section-3.subtitle" - Subtitle for section 3 of the VPN invited screen static let networkProtectionWaitlistInvitedSection3Subtitle = "We do not log or save any data that can connect you to your online activity." - // "network-protection.waitlist.enable.title" - Title for Network Protection enable screen - static let networkProtectionWaitlistEnableTitle = "Ready to enable Network Protection?" - // "network-protection.waitlist.enable.subtitle" - Subtitle for Network Protection enable screen - static let networkProtectionWaitlistEnableSubtitle = "Look for the globe icon in the browser toolbar or in the Mac menu bar.\n\nYou'll be asked to Allow a VPN connection once when setting up Network Protection the first time." + // "network-protection.waitlist.enable.title" - Title for VPN enable screen + static let networkProtectionWaitlistEnableTitle = "Ready to enable DuckDuckGo VPN?" + // "network-protection.waitlist.enable.subtitle" - Subtitle for VPN enable screen + static let networkProtectionWaitlistEnableSubtitle = "Look for the globe icon in the browser toolbar or in the Mac menu bar.\n\nYou'll be asked to Allow a VPN connection once when setting up DuckDuckGo VPN the first time." - // "network-protection.waitlist.availability-disclaimer" - Availability disclaimer for Network Protection join waitlist screen - static let networkProtectionWaitlistAvailabilityDisclaimer = "Network Protection is free to use during the beta." + // "network-protection.waitlist.availability-disclaimer" - Availability disclaimer for VPN join waitlist screen + static let networkProtectionWaitlistAvailabilityDisclaimer = "DuckDuckGo VPN is free to use during the beta." - // "network-protection.waitlist.button.close" - Close button for Network Protection join waitlist screen + // "network-protection.waitlist.button.close" - Close button for VPN join waitlist screen static let networkProtectionWaitlistButtonClose = "Close" - // "network-protection.waitlist.button.done" - Close button for Network Protection joined waitlist screen + // "network-protection.waitlist.button.done" - Close button for VPN joined waitlist screen static let networkProtectionWaitlistButtonDone = "Done" - // "network-protection.waitlist.button.dismiss" - Dismiss button for Network Protection join waitlist screen + // "network-protection.waitlist.button.dismiss" - Dismiss button for VPN join waitlist screen static let networkProtectionWaitlistButtonDismiss = "Dismiss" - // "network-protection.waitlist.button.cancel" - Cancel button for Network Protection join waitlist screen + // "network-protection.waitlist.button.cancel" - Cancel button for VPN join waitlist screen static let networkProtectionWaitlistButtonCancel = "Cancel" - // "network-protection.waitlist.button.no-thanks" - No Thanks button for Network Protection joined waitlist screen + // "network-protection.waitlist.button.no-thanks" - No Thanks button for VPN joined waitlist screen static let networkProtectionWaitlistButtonNoThanks = "No Thanks" - // "network-protection.waitlist.button.get-started" - Get Started button for Network Protection joined waitlist screen + // "network-protection.waitlist.button.get-started" - Get Started button for VPN joined waitlist screen static let networkProtectionWaitlistButtonGetStarted = "Get Started" - // "network-protection.waitlist.button.got-it" - Got It button for Network Protection joined waitlist screen + // "network-protection.waitlist.button.got-it" - Got It button for VPN joined waitlist screen static let networkProtectionWaitlistButtonGotIt = "Got It" - // "network-protection.waitlist.button.enable-notifications" - Enable Notifications button for Network Protection joined waitlist screen + // "network-protection.waitlist.button.enable-notifications" - Enable Notifications button for VPN joined waitlist screen static let networkProtectionWaitlistButtonEnableNotifications = "Enable Notifications" - // "network-protection.waitlist.button.join-waitlist" - Join Waitlist button for Network Protection join waitlist screen + // "network-protection.waitlist.button.join-waitlist" - Join Waitlist button for VPN join waitlist screen static let networkProtectionWaitlistButtonJoinWaitlist = "Join the Waitlist" - // "network-protection.waitlist.button.agree-and-continue" - Agree and Continue button for Network Protection join waitlist screen + // "network-protection.waitlist.button.agree-and-continue" - Agree and Continue button for VPN join waitlist screen static let networkProtectionWaitlistButtonAgreeAndContinue = "Agree and Continue" } -// MARK: - Network Protection Terms of Service +// MARK: - VPN Terms of Service extension UserText { - // "network-protection.privacy-policy.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicyTitle = "Privacy Policy" - // "network-protection.privacy-policy.section.1.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.section.1.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicySection1Title = "We don’t ask for any personal information from you in order to use this beta service." - // "network-protection.privacy-policy.section.1.list" - Privacy Policy list for Network Protection (Markdown version) + // "network-protection.privacy-policy.section.1.list" - Privacy Policy list for VPN (Markdown version) static let networkProtectionPrivacyPolicySection1ListMarkdown = "This Privacy Policy is for our limited waitlist beta VPN product.\n\nOur main [Privacy Policy](https://duckduckgo.com/privacy) also applies here." - // "network-protection.privacy-policy.section.1.list" - Privacy Policy list for Network Protection (Non-Markdown version) + // "network-protection.privacy-policy.section.1.list" - Privacy Policy list for VPN (Non-Markdown version) static let networkProtectionPrivacyPolicySection1ListNonMarkdown = "This Privacy Policy is for our limited waitlist beta VPN product.\n\nOur main Privacy Policy also applies here." - // "network-protection.privacy-policy.section.2.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.section.2.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicySection2Title = "We don’t keep any logs of your online activity." - // "network-protection.privacy-policy.section.2.list" - Privacy Policy list for Network Protection + // "network-protection.privacy-policy.section.2.list" - Privacy Policy list for VPN static let networkProtectionPrivacyPolicySection2List = "That means we have no way to tie what you do online to you as an individual and we don’t have any record of things like:\n • Website visits\n • DNS requests\n • Connections made\n • IP addresses used\n • Session lengths" - // "network-protection.privacy-policy.section.3.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.section.3.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicySection3Title = "We only keep anonymous performance metrics that we cannot connect to your online activity." - // "network-protection.privacy-policy.section.3.list" - Privacy Policy list for Network Protection + // "network-protection.privacy-policy.section.3.list" - Privacy Policy list for VPN static let networkProtectionPrivacyPolicySection3List = "Our servers store generic usage (for example, CPU load) and diagnostic data (for example, errors), but none of that data is connected to any individual’s activity.\n\nWe use this non-identifying information to monitor and ensure the performance and quality of the service, for example to make sure servers aren’t overloaded." - // "network-protection.privacy-policy.section.4.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.section.4.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicySection4Title = "We use dedicated servers for all VPN traffic." - // "network-protection.privacy-policy.section.4.list" - Privacy Policy list for Network Protection + // "network-protection.privacy-policy.section.4.list" - Privacy Policy list for VPN static let networkProtectionPrivacyPolicySection4List = "Dedicated servers means they are not shared with anyone else.\n\nWe rent our servers from providers we carefully selected because they meet our privacy requirements.\n\nWe have strict access controls in place so that only limited DuckDuckGo team members have access to our servers." - // "network-protection.privacy-policy.section.5.title" - Privacy Policy title for Network Protection + // "network-protection.privacy-policy.section.5.title" - Privacy Policy title for VPN static let networkProtectionPrivacyPolicySection5Title = "We protect and limit use of your data when you communicate directly with DuckDuckGo." - // "network-protection.privacy-policy.section.5.list" - Privacy Policy list for Network Protection + // "network-protection.privacy-policy.section.5.list" - Privacy Policy list for VPN static let networkProtectionPrivacyPolicySection5List = "If you reach out to us for support by submitting a bug report or through email and agree to be contacted to troubleshoot the issue, we’ll contact you using the information you provide.\n\nIf you participate in a voluntary product survey or questionnaire and agree to provide further feedback, we may contact you using the information you provide.\n\nWe will permanently delete all personal information you provided to us (email, contact information), within 30 days after closing a support case or, in the case of follow up feedback, within 60 days after ending this beta service." - // "network-protection.terms-of-service.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceTitle = "Terms of Service" - // "network-protection.terms-of-service.section.1.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.1.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection1Title = "The service is for limited and personal use only." - // "network-protection.terms-of-service.section.1.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.1.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection1List = "This service is provided for your personal use only.\n\nYou are responsible for all activity in the service that occurs on or through your device.\n\nThis service may only be used through the DuckDuckGo app on the device on which you are given access. If you delete the DuckDuckGo app, you will lose access to the service.\n\nYou may not use this service through a third-party client." - // "network-protection.terms-of-service.section.2.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.2.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection2Title = "You agree to comply with all applicable laws, rules, and regulations." - // "network-protection.terms-of-service.section.2.list" - Terms of Service list for Network Protection (Markdown version) + // "network-protection.terms-of-service.section.2.list" - Terms of Service list for VPN (Markdown version) static let networkProtectionTermsOfServiceSection2ListMarkdown = "You agree that you will not use the service for any unlawful, illicit, criminal, or fraudulent purpose, or in any manner that could give rise to civil or criminal liability under applicable law.\n\nYou agree to comply with our [DuckDuckGo Terms of Service](https://duckduckgo.com/terms), which are incorporated by reference." - // "network-protection.terms-of-service.section.2.list" - Terms of Service list for Network Protection (Non-Markdown version) + // "network-protection.terms-of-service.section.2.list" - Terms of Service list for VPN (Non-Markdown version) static let networkProtectionTermsOfServiceSection2ListNonMarkdown = "You agree that you will not use the service for any unlawful, illicit, criminal, or fraudulent purpose, or in any manner that could give rise to civil or criminal liability under applicable law.\n\nYou agree to comply with our DuckDuckGo Terms of Service, which are incorporated by reference." - // "network-protection.terms-of-service.section.3.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.3.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection3Title = "You must be eligible to use this service." - // "network-protection.terms-of-service.section.3.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.3.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection3List = "Access to this beta is randomly awarded. You are responsible for ensuring eligibility.\n\nYou must be at least 18 years old and live in a location where use of a VPN is legal in order to be eligible to use this service." - // "network-protection.terms-of-service.section.4.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.4.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection4Title = "We provide this beta service as-is and without warranty." - // "network-protection.terms-of-service.section.4.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.4.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection4List = "This service is provided as-is and without warranties or guarantees of any kind.\n\nTo the extent possible under applicable law, DuckDuckGo will not be liable for any damage or loss arising from your use of the service. In any event, the total aggregate liability of DuckDuckGo shall not exceed $25 or the equivalent in your local currency.\n\nWe may in the future transfer responsibility for the service to a subsidiary of DuckDuckGo. If that happens, you agree that references to “DuckDuckGo” will refer to our subsidiary, which will then become responsible for providing the service and for any liabilities relating to it." - // "network-protection.terms-of-service.section.5.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.5.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection5Title = "We may terminate access at any time." - // "network-protection.terms-of-service.section.5.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.5.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection5List = "We reserve the right to revoke access to the service at any time in our sole discretion.\n\nWe may also terminate access for violation of these terms, including for repeated infringement of the intellectual property rights of others." - // "network-protection.terms-of-service.section.6.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.6.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection6Title = "The service is free during the beta period." - // "network-protection.terms-of-service.section.6.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.6.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection6List = "Access to this service is currently free of charge, but that is limited to this beta period.\n\nYou understand and agree that this service is provided on a temporary, testing basis only." - // "network-protection.terms-of-service.section.7.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.7.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection7Title = "We are continually updating the service." - // "network-protection.terms-of-service.section.7.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.7.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection7List = "The service is in beta, and we are regularly changing it.\n\nService coverage, speed, server locations, and quality may vary without warning." - // "network-protection.terms-of-service.section.8.title" - Terms of Service title for Network Protection + // "network-protection.terms-of-service.section.8.title" - Terms of Service title for VPN static let networkProtectionTermsOfServiceSection8Title = "We need your feedback." - // "network-protection.terms-of-service.section.8.list" - Terms of Service list for Network Protection + // "network-protection.terms-of-service.section.8.list" - Terms of Service list for VPN static let networkProtectionTermsOfServiceSection8List = "You may be asked during the beta period to provide feedback about your experience. Doing so is optional and your feedback may be used to improve the service.\n\nIf you have enabled notifications for the DuckDuckGo app, we may use notifications to ask about your experience. You can disable notifications if you do not want to receive them." // MARK: - Feedback Form @@ -383,3 +385,23 @@ extension UserText { static let dataBrokerProtectionWaitlistButtonAgreeAndContinue = "Agree and Continue" } #endif + +// MARK: - Thank You Modals + +extension UserText { + static let dbpThankYouTitle = "Personal Information Removal early access is over" + static let dbpThankYouSubtitle = "Thank you for being a tester!" + static let dbpThankYouBody1 = "To continue using Personal Information Removal, subscribe to DuckDuckGo Privacy Pro and get 40% off with promo code THANKYOU" + + static let vpnThankYouTitle = "DuckDuckGo VPN early access is over" + static let vpnThankYouSubtitle = "Thank you for being a tester!" + static let vpnThankYouBody1 = "To continue using the VPN, subscribe to DuckDuckGo Privacy Pro and get 40% off with promo code THANKYOU" + +#if APPSTORE + static let dbpThankYouBody2 = "Offer redeemable for a limited time only in the desktop version of the DuckDuckGo browser by U.S. testers when you download from duckduckgo.com/app" + static let vpnThankYouBody2 = "Offer redeemable for a limited time only in the desktop version of the DuckDuckGo browser by U.S. testers when you download from duckduckgo.com/app" +#else + static let dbpThankYouBody2 = "Offer redeemable for a limited time in the desktop version of the DuckDuckGo browser by U.S. beta testers only." + static let vpnThankYouBody2 = "Offer redeemable for a limited time in the desktop version of the DuckDuckGo browser by U.S. beta testers only." +#endif +} diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 8fad1dff04..66e81e8608 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -329,10 +329,12 @@ struct UserText { value: "Fireproofing this site will keep you signed in after using the Fire Button.", comment: "Fireproof confirmation message") static let webTrackingProtectionSettingsTitle = NSLocalizedString("web.tracking.protection.title", value: "Web Tracking Protection", comment: "Web tracking protection settings section title") - static let webTrackingProtectionExplenation = NSLocalizedString("web.tracking.protection.explenation", value: "DuckDuckGo automatically blocks hidden trackers as you browse the web.", comment: "feature explanation in settings") - static let autoconsentSettingsTitle = NSLocalizedString("autoconsent.title", value: "Cookie Pop-ups", comment: "Autoconsent settings section title") + static let webTrackingProtectionExplenation = NSLocalizedString("web.tracking.protection.explenation", value: "DuckDuckGo automatically blocks hidden trackers as you browse the web.", comment: "Privacy feature explanation in the browser settings") static let autoconsentCheckboxTitle = NSLocalizedString("autoconsent.checkbox.title", value: "Automatically handle cookie pop-ups", comment: "Autoconsent settings checkbox title") static let autoconsentExplanation = NSLocalizedString("autoconsent.explanation", value: "DuckDuckGo will try to select the most private settings available and hide these pop-ups for you.", comment: "Autoconsent feature explanation in settings") + static let privateSearchExplanation = NSLocalizedString("private.search.explenation", value: "DuckDuckGo Private Search is your default search engine, so you can search the web without being tracked.", comment: "feature explanation in settings") + static let webTrackingProtectionExplanation = NSLocalizedString("web.tracking.protection.explanation", value: "DuckDuckGo automatically blocks hidden trackers as you browse the web.", comment: "feature explanation in settings") + static let emailProtectionExplanation = NSLocalizedString("email.protection.explanation", value: "Block email trackers and hide your address without switching your email provider.", comment: "Email protection feature explanation in settings. The feature blocks email trackers and hides original email address.") // Misc @@ -342,7 +344,6 @@ struct UserText { static let duckPlayerOff = NSLocalizedString("duck-player.off", value: "Never use Duck Player", comment: "Private YouTube Player option") static let duckPlayerExplanation = NSLocalizedString("duck-player.explanation", value: "Duck Player provides a clean viewing experience without personalized ads and prevents viewing activity from influencing your YouTube recommendations.", comment: "Private YouTube Player explanation in settings") - static let gpcSettingsTitle = NSLocalizedString("gpc.title", value: "Global Privacy Control (GPC)", comment: "GPC settings title") static let gpcCheckboxTitle = NSLocalizedString("gpc.checkbox.title", value: "Enable Global Privacy Control", comment: "GPC settings checkbox title") static let gpcExplanation = NSLocalizedString("gpc.explanation", value: "Tells participating websites not to sell or share your data.", comment: "GPC explanation in settings") static let learnMore = NSLocalizedString("learnmore.link", value: "Learn More", comment: "Learn More link") @@ -426,7 +427,6 @@ struct UserText { static let addToFavorites = NSLocalizedString("add.to.favorites", value: "Add to Favorites", comment: "Button for adding bookmarks to favorites") static let addFavorite = NSLocalizedString("add.favorite", value: "Add Favorite", comment: "Button for adding a favorite bookmark") static let editFavorite = NSLocalizedString("edit.favorite", value: "Edit Favorite", comment: "Header of the view that edits a favorite bookmark") - static let editFolder = NSLocalizedString("edit.folder", value: "Edit Folder", comment: "Header of the view that edits a bookmark folder") static let removeFromFavorites = NSLocalizedString("remove.from.favorites", value: "Remove from Favorites", comment: "Button for removing bookmarks from favorites") static let bookmarkThisPage = NSLocalizedString("bookmark.this.page", value: "Bookmark This Page", comment: "Menu item for bookmarking current page") static let bookmarksShowToolbarPanel = NSLocalizedString("bookmarks.show-toolbar-panel", value: "Open Bookmarks Panel", comment: "Menu item for opening the bookmarks panel") @@ -459,7 +459,6 @@ struct UserText { static let newFolder = NSLocalizedString("folder.optionsMenu.newFolder", value: "New Folder", comment: "Option for creating a new folder") static let renameFolder = NSLocalizedString("folder.optionsMenu.renameFolder", value: "Rename Folder", comment: "Option for renaming a folder") static let deleteFolder = NSLocalizedString("folder.optionsMenu.deleteFolder", value: "Delete Folder", comment: "Option for deleting a folder") - static let newFolderDialogFolderNameTitle = NSLocalizedString("add.folder.name", value: "Name:", comment: "Add Folder popover: folder name text field title") static let newBookmarkDialogBookmarkNameTitle = NSLocalizedString("add.bookmark.name", value: "Name:", comment: "New bookmark folder dialog folder name field heading") static let updateBookmark = NSLocalizedString("bookmark.update", value: "Update Bookmark", comment: "Option for updating a bookmark") @@ -518,8 +517,8 @@ struct UserText { static let settings = NSLocalizedString("settings", value: "Settings", comment: "Menu item for opening settings") - static let general = NSLocalizedString("preferences.general", value: "General", comment: "Show general preferences") - static let sync = NSLocalizedString("preferences.sync", value: "Sync & Backup", comment: "Show sync preferences") + static let general = NSLocalizedString("preferences.general", value: "General", comment: "Title of the option to show the General preferences") + static let sync = NSLocalizedString("preferences.sync", value: "Sync & Backup", comment: "Title of the option to show the Sync preferences") static let syncAutoLockPrompt = NSLocalizedString("preferences.sync.auto-lock-prompt", value:"Unlock device to setup Sync & Backup", comment: "Reason for auth when setting up Sync") static let syncBookmarkPausedAlertTitle = NSLocalizedString("alert.sync-bookmarks-paused-title", value: "Bookmarks Sync is Paused", comment: "Title for alert shown when sync bookmarks paused for too many items") static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You have exceeded the bookmarks sync limit. Try deleting some bookmarks. Until this is resolved your bookmarks will not be backed up.", comment: "Description for alert shown when sync bookmarks paused for too many items") @@ -528,14 +527,29 @@ struct UserText { static let syncPausedTitle = NSLocalizedString("alert.sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") static let syncUnavailableMessage = NSLocalizedString("alert.sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("alert.sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") - static let defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Show default browser preferences") - static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Show appearance preferences") - static let privacy = NSLocalizedString("preferences.privacy", value: "Privacy", comment: "Show privacy browser preferences") - static let vpn = NSLocalizedString("preferences.vpn", value: "VPN", comment: "Show VPN preferences") - static let duckPlayer = NSLocalizedString("preferences.duck-player", value: "Duck Player", comment: "Show Duck Player browser preferences") - static let about = NSLocalizedString("preferences.about", value: "About", comment: "Show about screen") - - static let downloads = NSLocalizedString("preferences.downloads", value: "Downloads", comment: "Show downloads browser preferences") + static let privacyProtections = NSLocalizedString("preferences.privacy-protections", value: "Privacy Protections", comment: "The section header in Preferences representing browser features related to privacy protection") + static let mainSettings = NSLocalizedString("preferences.main-settings", value: "Main Settings", comment: "Section header in Preferences for main settings") + static let preferencesOn = NSLocalizedString("preferences.on", value: "On", comment: "Status indicator of a browser privacy protection feature.") + static let preferencesOff = NSLocalizedString("preferences.off", value: "Off", comment: "Status indicator of a browser privacy protection feature.") + static let preferencesAlwaysOn = NSLocalizedString("preferences.always-on", value: "Always On", comment: "Status indicator of a browser privacy protection feature.") + static let duckduckgoOnOtherPlatforms = NSLocalizedString("preferences.duckduckgo-on-other-platforms", value: "DuckDuckGo on Other Platforms", comment: "Button presented to users to navigate them to our product page which presents all other products for other platforms") + static let defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Title of the option to show the Default Browser Preferences") + static let privateSearch = NSLocalizedString("preferences.private-search", value: "Private Search", comment: "Title of the option to show the Private Search preferences") + static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Title of the option to show the Appearance preferences") + static let dataClearing = NSLocalizedString("preferences.data-clearing", value: "Data Clearing", comment: "Title of the option to show the Data Clearing preferences") + static let webTrackingProtection = NSLocalizedString("preferences.web-tracking-protection", value: "Web Tracking Protection", comment: "Title of the option to show the Web Tracking Protection preferences") + static let emailProtectionPreferences = NSLocalizedString("preferences.email-protection", value: "Email Protection", comment: "Title of the option to show the Email Protection preferences") + static let autofillEnabledFor = NSLocalizedString("preferences.autofill-enabled-for", value: "Autofill enabled in this browser for:", comment: "Label presented before the email account in email protection preferences") + + static let vpn = NSLocalizedString("preferences.vpn", value: "VPN", comment: "Title of the option to show the VPN preferences") + static let duckPlayer = NSLocalizedString("preferences.duck-player", value: "Duck Player", comment: "Title of the option to show the Duck Player browser preferences") + static let about = NSLocalizedString("preferences.about", value: "About", comment: "Title of the option to show the About screen") + + static let accessibility = NSLocalizedString("preferences.accessibility", value: "Accessibility", comment: "Title of the option to show the Accessibility browser preferences") + static let cookiePopUpProtection = NSLocalizedString("preferences.cookie-pop-up-protection", value: "Cookie Pop-Up Protection", comment: "Title of the option to show the Cookie Pop-Up Protection preferences") + static let downloads = NSLocalizedString("preferences.downloads", value: "Downloads", comment: "Title of the downloads browser preferences") + static let support = NSLocalizedString("preferences.support", value: "Support", comment: "Open support page") + static let isDefaultBrowser = NSLocalizedString("preferences.default-browser.active", value: "DuckDuckGo is your default browser", comment: "Indicate that the browser is the default") static let isNotDefaultBrowser = NSLocalizedString("preferences.default-browser.inactive", value: "DuckDuckGo is not your default browser.", comment: "Indicate that the browser is not the default") static let makeDefaultBrowser = NSLocalizedString("preferences.default-browser.button.make-default", value: "Make DuckDuckGo Default…", comment: "represents a prompt message asking the user to make DuckDuckGo their default browser.") @@ -570,10 +584,9 @@ struct UserText { static let addressBar = NSLocalizedString("preferences.appearance.address-bar", value: "Address Bar", comment: "Theme preferences") static let showFullWebsiteAddress = NSLocalizedString("preferences.appearance.show-full-url", value: "Full website address", comment: "Option to show full URL in the address bar") static let showAutocompleteSuggestions = NSLocalizedString("preferences.appearance.show-autocomplete-suggestions", value: "Autocomplete suggestions", comment: "Option to show autocomplete suggestions in the address bar") - static let zoomSettingTitle = NSLocalizedString("preferences.appearance.zoom", value: "Zoom", comment: "Zoom settings section title") static let zoomPickerTitle = NSLocalizedString("preferences.appearance.zoom-picker", value: "Default page zoom", comment: "Default page zoom picker title") static let defaultZoomPageMoreOptionsItem = NSLocalizedString("more-options.zoom.default-zoom-page", value: "Change Default Page Zoom…", comment: "Default page zoom picker title") - static let autofill = NSLocalizedString("preferences.autofill", value: "Autofill", comment: "Show Autofill preferences") + static let autofill = NSLocalizedString("preferences.autofill", value: "Passwords", comment: "Show Autofill preferences") static let aboutDuckDuckGo = NSLocalizedString("preferences.about.about-duckduckgo", value: "About DuckDuckGo", comment: "About screen") static let privacySimplified = NSLocalizedString("preferences.about.privacy-simplified", value: "Privacy, simplified.", comment: "About screen") @@ -974,23 +987,23 @@ struct UserText { static let newTabSetUpImportCardTitle = NSLocalizedString("newTab.setup.import.title", value: "Bring Your Stuff", comment: "Title of the Import card of the Set Up section in the home page") static let newTabSetUpDuckPlayerCardTitle = NSLocalizedString("newTab.setup.duck.player.title", value: "Clean Up YouTube", comment: "Title of the Duck Player card of the Set Up section in the home page") static let newTabSetUpEmailProtectionCardTitle = NSLocalizedString("newTab.setup.email.protection.title", value: "Protect Your Inbox", comment: "Title of the Email Protection card of the Set Up section in the home page") - static let newTabSetUpSurveyDay0CardTitle = NSLocalizedString("newTab.setup.survey.day.0.title", value: "Tell Us What Brought You Here", comment: "Title of the Day 0 durvey of the Set Up section in the home page") - static let newTabSetUpSurveyDay7CardTitle = NSLocalizedString("newTab.setup.survey.day.7.title", value: "Help Us Improve", comment: "Title of the Day 7 durvey of the Set Up section in the home page") + static let newTabSetUpSurveyDay0CardTitle = NSLocalizedString("newTab.setup.survey.day.0.title", value: "Share Your Thoughts With Us", comment: "Title of the Day 0 durvey of the Set Up section in the home page") + static let newTabSetUpSurveyDay14CardTitle = NSLocalizedString("newTab.setup.survey.day.14.title", value: "Share Your Thoughts With Us", comment: "Title of the Day 14 durvey of the Set Up section in the home page") static let newTabSetUpDefaultBrowserAction = NSLocalizedString("newTab.setup.default.browser.action", value: "Make Default Browser", comment: "Action title on the action menu of the Default Browser card") static let newTabSetUpImportAction = NSLocalizedString("newTab.setup.Import.action", value: "Import Now", comment: "Action title on the action menu of the Import card of the Set Up section in the home page") static let newTabSetUpDuckPlayerAction = NSLocalizedString("newTab.setup.duck.player.action", value: "Try Duck Player", comment: "Action title on the action menu of the Duck Player card of the Set Up section in the home page") static let newTabSetUpEmailProtectionAction = NSLocalizedString("newTab.setup.email.protection.action", value: "Get a Duck Address", comment: "Action title on the action menu of the Email Protection card of the Set Up section in the home page") static let newTabSetUpRemoveItemAction = NSLocalizedString("newTab.setup.remove.item", value: "Dismiss", comment: "Action title on the action menu of the set up cards card of the SetUp section in the home page to remove the item") - static let newTabSetUpSurveyDay0Action = NSLocalizedString("newTab.setup.survey.day.0.action", value: "Share Your Thoughts", comment: "Action title of the Day 0 durvey of the Set Up section in the home page") - static let newTabSetUpSurveyDay7Action = NSLocalizedString("newTab.setup.survey.day.7.action", value: "Share Your Thoughts", comment: "Action title of the Day 7 durvey of the Set Up section in the home page") + static let newTabSetUpSurveyDay0Action = NSLocalizedString("newTab.setup.survey.day.0.action", value: "Sign Up To Participate", comment: "Action title of the Day 0 survey of the Set Up section in the home page") + static let newTabSetUpSurveyDay14Action = NSLocalizedString("newTab.setup.survey.day.14.action", value: "Sign Up To Participate", comment: "Action title of the Day 14 survey of the Set Up section in the home page") static let newTabSetUpDefaultBrowserSummary = NSLocalizedString("newTab.setup.default.browser.summary", value: "We automatically block trackers as you browse. It's privacy, simplified.", comment: "Summary of the Default Browser card") static let newTabSetUpImportSummary = NSLocalizedString("newTab.setup.import.summary", value: "Import bookmarks, favorites, and passwords from your old browser.", comment: "Summary of the Import card of the Set Up section in the home page") static let newTabSetUpDuckPlayerSummary = NSLocalizedString("newTab.setup.duck.player.summary", value: "Enjoy a clean viewing experience without personalized ads.", comment: "Summary of the Duck Player card of the Set Up section in the home page") static let newTabSetUpEmailProtectionSummary = NSLocalizedString("newTab.setup.email.protection.summary", value: "Generate custom @duck.com addresses that clean trackers from incoming email.", comment: "Summary of the Email Protection card of the Set Up section in the home page") - static let newTabSetUpSurveyDay0Summary = NSLocalizedString("newTab.setup.survey.day.0.summary", value: "Take our short survey and help us build the best browser.", comment: "Summary of the Day 0 durvey of the Set Up section in the home page") - static let newTabSetUpSurveyDay7Summary = NSLocalizedString("newTab.setup.survey.day.7.summary", value: "Take our short survey and help us build the best browser.", comment: "Summary of the Day 7 durvey of the Set Up section in the home page") + static let newTabSetUpSurveyDay0Summary = NSLocalizedString("newTab.setup.survey.day.0.summary", value: "Join an interview with a member of our research team to help us build the best browser.", comment: "Summary of the card on the new tab page that invites users to partecipate to a survey") + static let newTabSetUpSurveyDay14Summary = NSLocalizedString("newTab.setup.survey.day.14.summary", value: "Join an interview with a member of our research team to help us build the best browser.", comment: "Summary of the card on the new tab page that invites users to partecipate to a survey") // Recent Activity static let newTabRecentActivitySectionTitle = NSLocalizedString("newTab.recent.activity.section.title", value: "Recent Activity", comment: "Title of the RecentActivity section in the home page") @@ -1008,7 +1021,6 @@ struct UserText { // "data-broker-protection.optionsMenu" - Menu item data broker protection feature static let dataBrokerProtectionOptionsMenuItem = "Personal Information Removal" - static let dataBrokerProtectionScanOptionsMenuItem = "Personal Information Removal Scan" // "tab.dbp.title" - Tab data broker protection title static let tabDataBrokerProtectionTitle = "Personal Information Removal" @@ -1049,9 +1061,9 @@ struct UserText { enum Bookmarks { enum Dialog { enum Title { - static let addBookmark = NSLocalizedString("bookmarks.dialog.title.add", value: "Add bookmark", comment: "Bookmark creation dialog title") + static let addBookmark = NSLocalizedString("bookmarks.dialog.title.add", value: "Add Bookmark", comment: "Bookmark creation dialog title") static let addedBookmark = NSLocalizedString("bookmarks.dialog.title.added", value: "Bookmark Added", comment: "Bookmark added popover title") - static let editBookmark = NSLocalizedString("bookmarks.dialog.title.edit", value: "Edit bookmark", comment: "Bookmark edit dialog title") + static let editBookmark = NSLocalizedString("bookmarks.dialog.title.edit", value: "Edit Bookmark", comment: "Bookmark edit dialog title") static let addFolder = NSLocalizedString("bookmarks.dialog.folder.title.add", value: "Add Folder", comment: "Bookmark folder creation dialog title") static let editFolder = NSLocalizedString("bookmarks.dialog.folder.title.edit", value: "Edit Folder", comment: "Bookmark folder edit dialog title") } diff --git a/DuckDuckGo/Common/Logging/Logging.swift b/DuckDuckGo/Common/Logging/Logging.swift index e6c9d18b36..70b37f76b7 100644 --- a/DuckDuckGo/Common/Logging/Logging.swift +++ b/DuckDuckGo/Common/Logging/Logging.swift @@ -41,7 +41,7 @@ extension OSLog { case duckPlayer = "Duck Player" case tabSnapshots = "Tab Snapshots" case sync = "Sync" - case networkProtection = "Network Protection" + case networkProtection = "VPN" case dbp = "dbp" } diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 272d63cca0..2d7d25f85e 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -23,6 +23,7 @@ extension UserDefaults { /// The app group's shared UserDefaults static let netP = UserDefaults(suiteName: Bundle.main.appGroup(bundle: .netP))! static let dbp = UserDefaults(suiteName: Bundle.main.appGroup(bundle: .dbp))! + static let subs = UserDefaults(suiteName: Bundle.main.appGroup(bundle: .subs))! } @propertyWrapper @@ -111,8 +112,10 @@ public struct UserDefaultsWrapper { case homePageShowDuckPlayer = "home.page.show.duck.player" case homePageShowEmailProtection = "home.page.show.email.protection" case homePageShowSurveyDay0 = "home.page.show.survey.0" + case homePageShowSurveyDay0in10Percent = "home.page.show.survey.0.in.10.pervent" + case homePageShowSurveyDay14in10Percent = "home.page.show.survey.0.in.14.pervent" case homePageUserInteractedWithSurveyDay0 = "home.page.user.interacted.with.survey.0" - case homePageShowSurveyDay7 = "home.page.show.survey.7" + case homePageShowSurveyDay14 = "home.page.show.survey.14" case homePageShowPageTitles = "home.page.show.page.titles" case homePageShowRecentlyVisited = "home.page.show.recently.visited" case homePageContinueSetUpImport = "home.page.continue.set.up.import" @@ -146,13 +149,13 @@ public struct UserDefaultsWrapper { case dataBrokerProtectionTermsAndConditionsAccepted = "data-broker-protection.waitlist-terms-and-conditions.accepted" case shouldShowDBPWaitlistInvitedCardUI = "shouldShowDBPWaitlistInvitedCardUI" - // Network Protection + // VPN case networkProtectionExcludedRoutes = "netp.excluded-routes" case networkProtectionTermsAndConditionsAccepted = "network-protection.waitlist-terms-and-conditions.accepted" case networkProtectionWaitlistSignUpPromptDismissed = "network-protection.waitlist.sign-up-prompt-dismissed" - // Network Protection: Shared Defaults + // VPN: Shared Defaults // --- // Please note that shared defaults MUST have a name that matches exactly their value, // or else KVO will just not work as of 2023-08-07 diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index 427b2983c2..f73cdd744c 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"b9487380b1490e67513c650e9497a28c\"" - public static let embeddedDataSHA = "79590d4f2a9713b20eb127a29f862130fdc964145917004031022beaecf80fd0" + public static let embeddedDataETag = "\"3e4949b5cf69c1851158e419dd4f175e\"" + public static let embeddedDataSHA = "8bedb31a111891d59112279e19bfbc2ba585dbf768f2f622e9c76ed5235a8e59" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/ContentBlocking.swift b/DuckDuckGo/ContentBlocker/ContentBlocking.swift index dee72a7514..795f5ec581 100644 --- a/DuckDuckGo/ContentBlocker/ContentBlocking.swift +++ b/DuckDuckGo/ContentBlocker/ContentBlocking.swift @@ -86,7 +86,7 @@ final class AppContentBlocking { privacyConfigurationManager: privacyConfigurationManager, trackerDataManager: trackerDataManager, configStorage: configStorage, - privacySecurityPreferences: PrivacySecurityPreferences.shared, + webTrackingProtectionPreferences: WebTrackingProtectionPreferences.shared, tld: tld) adClickAttributionRulesProvider = AdClickAttributionRulesProvider(config: adClickAttribution, diff --git a/DuckDuckGo/ContentBlocker/ScriptSourceProviding.swift b/DuckDuckGo/ContentBlocker/ScriptSourceProviding.swift index 4013498fff..979ecaf469 100644 --- a/DuckDuckGo/ContentBlocker/ScriptSourceProviding.swift +++ b/DuckDuckGo/ContentBlocker/ScriptSourceProviding.swift @@ -37,7 +37,7 @@ protocol ScriptSourceProviding { // refactor: ScriptSourceProvider to be passed to init methods as `some ScriptSourceProviding`, DefaultScriptSourceProvider to be killed // swiftlint:disable:next identifier_name func DefaultScriptSourceProvider() -> ScriptSourceProviding { - ScriptSourceProvider(configStorage: ConfigurationStore.shared, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, privacySettings: PrivacySecurityPreferences.shared, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, trackerDataManager: ContentBlocking.shared.trackerDataManager, tld: ContentBlocking.shared.tld) + ScriptSourceProvider(configStorage: ConfigurationStore.shared, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, webTrackingProtectionPreferences: WebTrackingProtectionPreferences.shared, contentBlockingManager: ContentBlocking.shared.contentBlockingManager, trackerDataManager: ContentBlocking.shared.trackerDataManager, tld: ContentBlocking.shared.tld) } struct ScriptSourceProvider: ScriptSourceProviding { @@ -52,19 +52,19 @@ struct ScriptSourceProvider: ScriptSourceProviding { let privacyConfigurationManager: PrivacyConfigurationManaging let contentBlockingManager: ContentBlockerRulesManagerProtocol let trackerDataManager: TrackerDataManager - let privacySettings: PrivacySecurityPreferences + let webTrakcingProtectionPreferences: WebTrackingProtectionPreferences let tld: TLD init(configStorage: ConfigurationStoring, privacyConfigurationManager: PrivacyConfigurationManaging, - privacySettings: PrivacySecurityPreferences, + webTrackingProtectionPreferences: WebTrackingProtectionPreferences, contentBlockingManager: ContentBlockerRulesManagerProtocol, trackerDataManager: TrackerDataManager, tld: TLD) { self.configStorage = configStorage self.privacyConfigurationManager = privacyConfigurationManager - self.privacySettings = privacySettings + self.webTrakcingProtectionPreferences = webTrackingProtectionPreferences self.contentBlockingManager = contentBlockingManager self.trackerDataManager = trackerDataManager self.tld = tld @@ -83,7 +83,7 @@ struct ScriptSourceProvider: ScriptSourceProviding { public func buildAutofillSource() -> AutofillUserScriptSourceProvider { let privacyConfig = self.privacyConfigurationManager.privacyConfig return DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: privacyConfigurationManager, - properties: ContentScopeProperties(gpcEnabled: privacySettings.gpcEnabled, + properties: ContentScopeProperties(gpcEnabled: webTrakcingProtectionPreferences.isGPCEnabled, sessionKey: self.sessionKey ?? "", featureToggles: ContentScopeFeatureToggles.supportedFeaturesOnMacOS(privacyConfig)), isDebug: AutofillPreferences().debugScriptEnabled) diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index 57da7cd02e..ca3abe4d41 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1710170291349, + "version": 1711567148287, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -77,9 +77,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -114,7 +111,7 @@ ] }, "state": "enabled", - "hash": "a92fae2cdccf479cc1bbe840cd32627b" + "hash": "51b76aa7b92d78ad52106b04ac809843" }, "androidBrowserConfig": { "exceptions": [], @@ -255,9 +252,6 @@ { "domain": "metro.co.uk" }, - { - "domain": "youtube.com" - }, { "domain": "newsmax.com" }, @@ -265,10 +259,19 @@ "domain": "meneame.net" }, { - "domain": "earth.google.com" + "domain": "usaa.com" }, { - "domain": "instructure.com" + "domain": "publico.es" + }, + { + "domain": "leboncoin.fr" + }, + { + "domain": "www.ffbb.com" + }, + { + "domain": "earth.google.com" }, { "domain": "iscorp.com" @@ -283,11 +286,12 @@ "settings": { "disabledCMPs": [ "generic-cosmetic", - "termsfeed3" + "termsfeed3", + "strato.de" ] }, "state": "enabled", - "hash": "8458a9cc012a9613e1497204003be946" + "hash": "d5b59105cdef6fcc34e8697940e42762" }, "autofill": { "exceptions": [ @@ -906,9 +910,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -934,16 +935,13 @@ } }, "state": "disabled", - "hash": "9c70121360bcdfeb63770d8d9aeee770" + "hash": "36e8971fa9bb204b78a5929a14a108dd" }, "clickToPlay": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -964,12 +962,15 @@ } }, "state": "enabled", - "hash": "fac8ee1d06f89e1c93ca9287c095b055" + "hash": "f1b7de266435cd2e414f50deb2c9234a" }, "clientBrandHint": { "exceptions": [], + "settings": { + "domains": [] + }, "state": "disabled", - "hash": "728493ef7a1488e4781656d3f9db84aa" + "hash": "d35dd75140cdfe166762013e59eb076d" }, "contentBlocking": { "state": "enabled", @@ -995,9 +996,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1008,7 +1006,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "910e25ffe4d683b3c708a1578d097a16" + "hash": "e37447d42ee8194f185e35e40f577f41" }, "cookie": { "settings": { @@ -1053,9 +1051,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1067,7 +1062,7 @@ } ], "state": "disabled", - "hash": "7c7ceca9eeb664059750ea96938669b0" + "hash": "37a27966915571085613911b47e6e2eb" }, "customUserAgent": { "settings": { @@ -1104,6 +1099,10 @@ { "domain": "spectrum.net", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1876" + }, + { + "domain": "web.de", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1931" } ], "webViewDefault": [ @@ -1127,7 +1126,7 @@ }, "exceptions": [], "state": "enabled", - "hash": "150400f9bcd5a8d7eb6fa7589047fe45" + "hash": "7edfa8344fd2577b31426130696d8b23" }, "dbp": { "state": "enabled", @@ -1227,9 +1226,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1677,6 +1673,14 @@ { "selector": "#ez-content-blocker-container", "type": "hide" + }, + { + "selector": ".m-balloon-header--ad", + "type": "hide-empty" + }, + { + "selector": ".m-in-content-ad-row", + "type": "hide-empty" } ], "styleTagExceptions": [ @@ -1719,6 +1723,7 @@ "advertisementcontinue reading the main story", "advertisement\ncontinue reading the main story", "advertisement\n\ncontinue reading the main story", + "advertisement - continue reading below", "advertisement\n\nhide ad", "advertisementhide ad", "advertisement - scroll to continue", @@ -1732,6 +1737,7 @@ "anzeige", "close ad", "close this ad", + "content continues below", "x", "_", "sponsored", @@ -1811,6 +1817,33 @@ } ] }, + { + "domain": "accuweather.com", + "rules": [ + { + "selector": ".glacier-ad", + "type": "hide-empty" + }, + { + "selector": "#connatix", + "type": "hide-empty" + }, + { + "selector": ".adhesion-header", + "type": "hide-empty" + }, + { + "selector": ".header-placeholder.has-alerts.has-adhesion", + "type": "modify-style", + "values": [ + { + "property": "height", + "value": "76px" + } + ] + } + ] + }, { "domain": "acidadeon.com", "rules": [ @@ -2048,6 +2081,31 @@ } ] }, + { + "domain": "clevelandclinic.org", + "rules": [ + { + "selector": "[data-identity='adhesive-ad']", + "type": "closest-empty" + }, + { + "selector": "[data-identity='billboard-ad']", + "type": "hide-empty" + }, + { + "selector": "[data-identity='leaderboard-ad']", + "type": "hide" + }, + { + "selector": "[data-identity='sticky-leaderboard-ad']", + "type": "hide" + }, + { + "selector": "[data-identity='leaderboard-ad-page-header-placeholder']", + "type": "hide" + } + ] + }, { "domain": "cnbc.com", "rules": [ @@ -2066,6 +2124,31 @@ } ] }, + { + "domain": "comicbook.com", + "rules": [ + { + "selector": "body:not(.skybox-loaded)>header", + "type": "modify-style", + "values": [ + { + "property": "top", + "value": "0px" + } + ] + }, + { + "selector": "body.pcm-public:not(.skybox-loaded)", + "type": "modify-style", + "values": [ + { + "property": "margin-top", + "value": "90px" + } + ] + } + ] + }, { "domain": "corriere.it", "rules": [ @@ -2932,6 +3015,23 @@ } ] }, + { + "domain": "n4g.com", + "rules": [ + { + "selector": ".top-ads-container-outer", + "type": "closest-empty" + }, + { + "selector": ".f-item-ad", + "type": "closest-empty" + }, + { + "selector": ".f-item-ad-inhouse", + "type": "closest-empty" + } + ] + }, { "domain": "nasdaq.com", "rules": [ @@ -3241,6 +3341,14 @@ } ] }, + { + "domain": "prajwaldesai.com", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "primagames.com", "rules": [ @@ -3282,6 +3390,26 @@ { "selector": "#marquee-ad", "type": "closest-empty" + }, + { + "selector": ".js_sticky-top-ad", + "type": "hide-empty" + }, + { + "selector": ".js_sticky-footer", + "type": "hide-empty" + }, + { + "selector": "#leftrail_dynamic_ad_wrapper", + "type": "hide-empty" + }, + { + "selector": "#splashy-ad-container-top", + "type": "hide-empty" + }, + { + "selector": ".ad-mobile", + "type": "closest-empty" } ] }, @@ -3358,6 +3486,15 @@ } ] }, + { + "domain": "runnersworld.com", + "rules": [ + { + "selector": ".ad-disclaimer", + "type": "closest-empty" + } + ] + }, { "domain": "scmp.com", "rules": [ @@ -3583,6 +3720,18 @@ } ] }, + { + "domain": "thetvdb.com", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": "[data-aa-adunit]", + "type": "hide" + } + ] + }, { "domain": "thewindowsclub.com", "rules": [ @@ -3704,6 +3853,14 @@ } ] }, + { + "domain": "tumblr.com", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "tvtropes.org", "rules": [ @@ -3858,6 +4015,22 @@ { "selector": "#YDC-Lead-Stack", "type": "hide-empty" + }, + { + "selector": "#topAd", + "type": "hide-empty" + }, + { + "selector": "#neoLeadAdMobile", + "type": "hide-empty" + }, + { + "selector": ".caas-da", + "type": "hide-empty" + }, + { + "selector": ".gam-placeholder", + "type": "closest-empty" } ] }, @@ -3904,6 +4077,23 @@ } ] }, + { + "domain": "uzone.id", + "rules": [ + { + "selector": "[class^='box-ads']", + "type": "hide-empty" + }, + { + "selector": "[class^='section-ads']", + "type": "hide-empty" + }, + { + "selector": ".parallax-container", + "type": "hide-empty" + } + ] + }, { "domain": "washingtontimes.com", "rules": [ @@ -3984,16 +4174,13 @@ ] }, "state": "enabled", - "hash": "3dc2d5b9a38827f46503a3b5882c7e33" + "hash": "f5eedb77e30d600d3cd81f92ceebee36" }, "exceptionHandler": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4005,7 +4192,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "fingerprintingAudio": { "state": "disabled", @@ -4016,9 +4203,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4029,7 +4213,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "40b13d6ca36cd3de287345ab9e5839fb" + "hash": "f25a8f2709e865c2bd743828c7ee2f77" }, "fingerprintingBattery": { "exceptions": [ @@ -4039,9 +4223,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4053,7 +4234,7 @@ } ], "state": "disabled", - "hash": "651cfc12aec9ff9a1caac0f817b58c3c" + "hash": "4085f1593faff2feac2093533b819a41" }, "fingerprintingCanvas": { "settings": { @@ -4147,9 +4328,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4161,7 +4339,7 @@ } ], "state": "disabled", - "hash": "98b5e91ff539dfb6c81699e32b76f70c" + "hash": "ea4c565bae27996f0d651300d757594c" }, "fingerprintingHardware": { "settings": { @@ -4216,9 +4394,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4263,7 +4438,7 @@ } ], "state": "enabled", - "hash": "d58f56954556ee2a4a32855edbf6e224" + "hash": "37e16df501e3e68416a13f991b4e4147" }, "fingerprintingScreenSize": { "settings": { @@ -4306,9 +4481,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4320,7 +4492,7 @@ } ], "state": "disabled", - "hash": "84897737f8a85968b246fdb80a84e671" + "hash": "466b85680f138657de9bfd222c440887" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -4336,9 +4508,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4350,16 +4519,13 @@ } ], "state": "disabled", - "hash": "dd1b1fb96c2f932048f1767f949e4453" + "hash": "f858697949c90842c450daee64a1dc30" }, "googleRejected": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4371,7 +4537,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "gpc": { "state": "enabled", @@ -4409,9 +4575,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4431,7 +4594,7 @@ "privacy-test-pages.site" ] }, - "hash": "99ec2ffa33009050831feba194452790" + "hash": "1a1373bcf16647d63220659fce650a83" }, "harmfulApis": { "settings": { @@ -4536,9 +4699,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4550,7 +4710,7 @@ } ], "state": "disabled", - "hash": "9d0f5f4f8c02e79246e2d809cada2fdb" + "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" }, "history": { "state": "disabled", @@ -4572,9 +4732,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4585,7 +4742,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "ea6d5ad048e35c75c451bff6fe58cb11" + "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" }, "incontextSignup": { "exceptions": [], @@ -4625,9 +4782,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4646,7 +4800,7 @@ ] }, "state": "enabled", - "hash": "f8dc40f1f5687f403f381452d66eb0d0" + "hash": "698de7b963d7d7942c5c5d1e986bb1b1" }, "networkProtection": { "state": "enabled", @@ -4691,10 +4845,11 @@ "exceptions": [], "state": "enabled", "settings": { - "surveyCardDay0": "disabled", - "surveyCardDay7": "disabled" + "surveyCardDay0": "enabled", + "surveyCardDay7": "disabled", + "surveyCardDay14": "enabled" }, - "hash": "2904635973a1cc6e9521cec3c29b93ea" + "hash": "eb826d9079211f30d624211f44aed184" }, "nonTracking3pCookies": { "settings": { @@ -4705,7 +4860,23 @@ "domain": "earth.google.com" }, { - "domain": "instructure.com" + "domain": "iscorp.com" + }, + { + "domain": "marvel.com" + }, + { + "domain": "sundancecatalog.com" + } + ], + "state": "disabled", + "hash": "841fa92b9728c9754f050662678f82c7" + }, + "performanceMetrics": { + "state": "enabled", + "exceptions": [ + { + "domain": "earth.google.com" }, { "domain": "iscorp.com" @@ -4717,8 +4888,7 @@ "domain": "sundancecatalog.com" } ], - "state": "disabled", - "hash": "d07b5bf740e4d648c94e1ac65c4305d9" + "hash": "38558d5e7b231d4b27e7dd76814387a7" }, "privacyDashboard": { "exceptions": [], @@ -4730,7 +4900,7 @@ } }, "toggleReports": { - "state": "internal", + "state": "enabled", "rollout": { "steps": [ { @@ -4741,7 +4911,29 @@ } }, "state": "enabled", - "hash": "0d76cb4a367fc6738f7c4aa6a66f0a04" + "hash": "f7cce63c16c142db4ff5764b542a6c52" + }, + "privacyPro": { + "state": "enabled", + "exceptions": [], + "features": { + "isLaunched": { + "state": "disabled" + }, + "isLaunchedOverride": { + "state": "disabled" + }, + "allowPurchase": { + "state": "enabled" + }, + "isLaunchedStripe": { + "state": "disabled" + }, + "allowPurchaseStripe": { + "state": "enabled" + } + }, + "hash": "c9153c5cc3b6b7eba024c6c597e15edb" }, "privacyProtectionsPopup": { "state": "disabled", @@ -4771,9 +4963,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4785,7 +4974,7 @@ } ], "state": "disabled", - "hash": "1679be76968fe50858b3cc664b8fcbad" + "hash": "0d3df0f7c24ebde89d2dced4e2d34322" }, "requestFilterer": { "state": "disabled", @@ -4793,9 +4982,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4809,7 +4995,7 @@ "settings": { "windowInMs": 0 }, - "hash": "219a51a9aafbc9c1bae4bad55d7ce437" + "hash": "0fff8017d8ea4b5609b8f5c110be1401" }, "runtimeChecks": { "state": "disabled", @@ -4817,9 +5003,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4831,16 +5014,13 @@ } ], "settings": {}, - "hash": "e2246d7c78df2167134e1428b04d51ca" + "hash": "800a19533c728bbec7e31e466f898268" }, "serviceworkerInitiatedRequests": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4852,7 +5032,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "sync": { "state": "enabled", @@ -4878,6 +5058,16 @@ "state": "enabled", "settings": { "allowlistedTrackers": { + "2mdn.net": { + "rules": [ + { + "rule": "2mdn.net", + "domains": [ + "crunchyroll.com" + ] + } + ] + }, "3lift.com": { "rules": [ { @@ -5060,9 +5250,10 @@ "advertising.com": { "rules": [ { - "rule": "adserver.adtech.advertising.com/pubapi/3.0/1/54669.7/0/0/ADTECH;v=2;cmd=bid;cors=yes", + "rule": "adserver.adtech.advertising.com/pubapi/3.0/1/", "domains": [ - "collider.com" + "collider.com", + "si.com" ] } ] @@ -5124,6 +5315,7 @@ "rule": "c.amazon-adsystem.com/aax2/apstag.js", "domains": [ "applesfera.com", + "fattoincasadabenedetta.it", "inquirer.com", "thesurfersview.com", "wildrivers.lostcoastoutpost.com" @@ -5853,6 +6045,12 @@ "" ] }, + { + "rule": "go.ezodn.com", + "domains": [ + "airplaneacademy.com" + ] + }, { "rule": "ezodn.com", "domains": [ @@ -6013,22 +6211,11 @@ "fwmrm.net": { "rules": [ { - "rule": "2a7e9.v.fwmrm.net/ad/g/1", + "rule": "v.fwmrm.net/ad", "domains": [ + "6play.fr", "channel4.com" ] - }, - { - "rule": "2a7e9.v.fwmrm.net/ad/l/1", - "domains": [ - "channel4.com" - ] - }, - { - "rule": "7cbf2.v.fwmrm.net/ad/g/1", - "domains": [ - "6play.fr" - ] } ] }, @@ -6197,6 +6384,7 @@ "domains": [ "arkadium.com", "bloomberg.com", + "crunchyroll.com", "gamak.tv", "games.washingtonpost.com", "metro.co.uk", @@ -6234,6 +6422,7 @@ "hscprojects.com", "kits4beats.com", "magicgameworld.com", + "ncaa.com", "rocketnews24.com", "youmath.it", "zefoy.com" @@ -6990,6 +7179,12 @@ "domains": [ "" ] + }, + { + "rule": "cdn.optimizely.com/js/24096340716.js", + "domains": [ + "hgtv.com" + ] } ] }, @@ -7213,6 +7408,12 @@ "domains": [ "newser.com" ] + }, + { + "rule": "a.pub.network/core/prebid-universal-creative.js", + "domains": [ + "titantv.com" + ] } ] }, @@ -7365,6 +7566,22 @@ } ] }, + "scorecardresearch.com": { + "rules": [ + { + "rule": "sb.scorecardresearch.com/c2/plugins/streamingtag_plugin_jwplayer.js", + "domains": [ + "" + ] + }, + { + "rule": "sb.scorecardresearch.com/internal-c2/default/streamingtag_plugin_jwplayer.js", + "domains": [ + "" + ] + } + ] + }, "searchspring.io": { "rules": [ { @@ -7933,9 +8150,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7946,7 +8160,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "4325ed151d126936fbeb9a608d77da86" + "hash": "653d70e261a24982bd23a0b107608d13" }, "trackingCookies1p": { "settings": { @@ -7959,9 +8173,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7973,7 +8184,7 @@ } ], "state": "disabled", - "hash": "bfd8b32efe8d633fe670bf6ab1b00240" + "hash": "4dddf681372a2aea9788090b13db6e6f" }, "trackingCookies3p": { "settings": { @@ -7983,9 +8194,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7997,7 +8205,7 @@ } ], "state": "disabled", - "hash": "d07b5bf740e4d648c94e1ac65c4305d9" + "hash": "841fa92b9728c9754f050662678f82c7" }, "trackingParameters": { "exceptions": [ @@ -8007,9 +8215,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8051,7 +8256,7 @@ }, "state": "enabled", "minSupportedVersion": "0.22.3", - "hash": "12e223e9f9327015be71e620e0ab4ccb" + "hash": "9a0282376084874f0245c421d6943841" }, "userAgentRotation": { "settings": { @@ -8061,9 +8266,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8075,7 +8277,7 @@ } ], "state": "disabled", - "hash": "4498ff835bed7ce27ff2a568db599155" + "hash": "f65d10dfdf6739feab99a08d42734747" }, "voiceSearch": { "exceptions": [], @@ -8087,9 +8289,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8361,7 +8560,7 @@ } ] }, - "hash": "f85196ca319a430ae50cd654b3230148" + "hash": "151d7ee40451c4aac4badfcc829ea0b5" }, "windowsPermissionUsage": { "exceptions": [], @@ -8373,9 +8572,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8387,7 +8583,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "windowsWaitlist": { "exceptions": [], diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index af30a4d7ce..641b81f587 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -33,6 +33,7 @@ public extension Notification.Name { final class DBPHomeViewController: NSViewController { private var presentedWindowController: NSWindowController? private let dataBrokerProtectionManager: DataBrokerProtectionManager + private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { let privacyConfigurationManager = PrivacyFeatures.contentBlocking.privacyConfigurationManager @@ -46,9 +47,9 @@ final class DBPHomeViewController: NSViewController { inlineIconCredentials: false, thirdPartyCredentialsProvider: false) - let privacySettings = PrivacySecurityPreferences.shared + let isGPCEnabled = WebTrackingProtectionPreferences.shared.isGPCEnabled let sessionKey = UUID().uuidString - let prefs = ContentScopeProperties(gpcEnabled: privacySettings.gpcEnabled, + let prefs = ContentScopeProperties(gpcEnabled: isGPCEnabled, sessionKey: sessionKey, featureToggles: features) @@ -90,6 +91,7 @@ final class DBPHomeViewController: NSViewController { } } catch { os_log("DBPHomeViewController error: viewDidLoad, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DBPHomeViewController.viewDidLoad")) } } @@ -150,6 +152,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Bool func disableAndDeleteForAllUsers() func disableAndDeleteForWaitlistUsers() + func isPrivacyProEnabled() -> Bool + func isEligibleForThankYouMessage() -> Bool } struct DefaultDataBrokerProtectionFeatureVisibility: DataBrokerProtectionFeatureVisibility { private let privacyConfigurationManager: PrivacyConfigurationManaging private let featureDisabler: DataBrokerProtectionFeatureDisabling private let pixelHandler: EventMapping + private let userDefaults: UserDefaults + private let waitlistStorage: WaitlistStorage + private let subscriptionAvailability: SubscriptionFeatureAvailability + + private let dataBrokerProtectionKey = "data-broker-protection.cleaned-up-from-waitlist-to-privacy-pro" + private var dataBrokerProtectionCleanedUpFromWaitlistToPrivacyPro: Bool { + get { + return userDefaults.bool(forKey: dataBrokerProtectionKey) + } + nonmutating set { + userDefaults.set(newValue, forKey: dataBrokerProtectionKey) + } + } /// Temporary code to use while we have both redeem flow for diary study users. Should be removed later static var bypassWaitlist = false init(privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler(), - pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler()) { + pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler(), + userDefaults: UserDefaults = .standard, + waitlistStorage: WaitlistStorage = DataBrokerProtectionWaitlist().waitlistStorage, + subscriptionAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability()) { self.privacyConfigurationManager = privacyConfigurationManager self.featureDisabler = featureDisabler self.pixelHandler = pixelHandler + self.userDefaults = userDefaults + self.waitlistStorage = waitlistStorage + self.subscriptionAvailability = subscriptionAvailability } var waitlistIsOngoing: Bool { @@ -61,10 +83,9 @@ struct DefaultDataBrokerProtectionFeatureVisibility: DataBrokerProtectionFeature regionCode = "US" } - #if DEBUG // Always assume US for debug builds +#if DEBUG // Always assume US for debug builds regionCode = "US" - #endif - +#endif return (regionCode ?? "US") == "US" } @@ -81,7 +102,19 @@ struct DefaultDataBrokerProtectionFeatureVisibility: DataBrokerProtectionFeature } private var isWaitlistUser: Bool { - DataBrokerProtectionWaitlist().waitlistStorage.isWaitlistUser + waitlistStorage.isWaitlistUser + } + + private var wasWaitlistUser: Bool { + waitlistStorage.getWaitlistInviteCode() != nil + } + + func isPrivacyProEnabled() -> Bool { + return subscriptionAvailability.isFeatureAvailable + } + + func isEligibleForThankYouMessage() -> Bool { + return wasWaitlistUser && isPrivacyProEnabled() } func disableAndDeleteForAllUsers() { @@ -99,6 +132,17 @@ struct DefaultDataBrokerProtectionFeatureVisibility: DataBrokerProtectionFeature featureDisabler.disableAndDelete() } + /// Returns true if a cleanup was performed, false otherwise + func cleanUpDBPForPrivacyProIfNecessary() -> Bool { + if isPrivacyProEnabled() && wasWaitlistUser && !dataBrokerProtectionCleanedUpFromWaitlistToPrivacyPro { + disableAndDeleteForWaitlistUsers() + dataBrokerProtectionCleanedUpFromWaitlistToPrivacyPro = true + return true + } else { + return false + } + } + /// If we want to prevent new users from joining the waitlist while still allowing waitlist users to continue using it, /// we should set isWaitlistEnabled to false and isWaitlistBetaActive to true. /// To remove it from everyone, isWaitlistBetaActive should be set to false @@ -116,5 +160,4 @@ struct DefaultDataBrokerProtectionFeatureVisibility: DataBrokerProtectionFeature } } } - #endif diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift index feb254562a..34dafab63f 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift @@ -55,7 +55,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.statusPublisher } - func scanAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + func scanAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { enableLoginItem() ipcScheduler.scanAllBrokers(showWebView: showWebView, completion: completion) } @@ -69,7 +70,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.stopScheduler() } - func optOutAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + func optOutAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { ipcScheduler.optOutAllBrokers(showWebView: showWebView, completion: completion) } @@ -77,7 +79,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.runAllOperations(showWebView: showWebView) } - func runQueuedOperations(showWebView: Bool, completion: ((Error?) -> Void)?) { + func runQueuedOperations(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { ipcScheduler.runQueuedOperations(showWebView: showWebView, completion: completion) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift index 29ce721418..adb20e4aee 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift @@ -36,7 +36,7 @@ public final class DataBrokerProtectionManager { private let dataBrokerProtectionWaitlistDataSource: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp) lazy var dataManager: DataBrokerProtectionDataManager = { - let dataManager = DataBrokerProtectionDataManager(fakeBrokerFlag: fakeBrokerFlag) + let dataManager = DataBrokerProtectionDataManager(pixelHandler: pixelHandler, fakeBrokerFlag: fakeBrokerFlag) dataManager.delegate = self return dataManager }() diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift index b57b77e846..08ec829fb1 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -27,7 +27,7 @@ final class DataBrokerProtectionSubscriptionEventHandler { private let authRepository: AuthenticationRepository private let featureDisabler: DataBrokerProtectionFeatureDisabling - init(accountManager: AccountManaging = AccountManager(), + init(accountManager: AccountManaging = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)), authRepository: AuthenticationRepository = KeychainAuthenticationData(), featureDisabler: DataBrokerProtectionFeatureDisabling = DataBrokerProtectionFeatureDisabler()) { self.accountManager = accountManager diff --git a/DuckDuckGo/DataImport/Logins/Chromium/ChromiumKeychainPrompt.swift b/DuckDuckGo/DataImport/Logins/Chromium/ChromiumKeychainPrompt.swift index 3c30a50f90..4e473eada8 100644 --- a/DuckDuckGo/DataImport/Logins/Chromium/ChromiumKeychainPrompt.swift +++ b/DuckDuckGo/DataImport/Logins/Chromium/ChromiumKeychainPrompt.swift @@ -43,6 +43,10 @@ final class ChromiumKeychainPrompt: ChromiumKeychainPrompting { kSecMatchLimit as String: kSecMatchLimitOne] as [String: Any] var dataFromKeychain: AnyObject? + + // Fire Pixel to help measure rate of password prompt denied actions + Pixel.fire(.passwordImportKeychainPrompt) + let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataFromKeychain) if status == noErr, let passwordData = dataFromKeychain as? Data { diff --git a/DuckDuckGo/DataImport/Logins/Chromium/ChromiumLoginReader.swift b/DuckDuckGo/DataImport/Logins/Chromium/ChromiumLoginReader.swift index 2c5b36ba8e..c423ec9025 100644 --- a/DuckDuckGo/DataImport/Logins/Chromium/ChromiumLoginReader.swift +++ b/DuckDuckGo/DataImport/Logins/Chromium/ChromiumLoginReader.swift @@ -65,9 +65,9 @@ final class ChromiumLoginReader { private let decryptionKey: String? private let decryptionKeyPrompt: ChromiumKeychainPrompting - private static let sqlSelectWithPasswordTimestamp = "SELECT signon_realm, username_value, password_value, date_password_modified FROM logins;" - private static let sqlSelectWithCreatedTimestamp = "SELECT signon_realm, username_value, password_value, date_created FROM logins;" - private static let sqlSelectWithoutTimestamp = "SELECT signon_realm, username_value, password_value FROM logins;" + private static let sqlSelectWithPasswordTimestamp = "SELECT signon_realm, username_value, password_value, date_password_modified, blacklisted_by_user FROM logins WHERE blacklisted_by_user != 1;" + private static let sqlSelectWithCreatedTimestamp = "SELECT signon_realm, username_value, password_value, date_created, blacklisted_by_user FROM logins WHERE blacklisted_by_user != 1;" + private static let sqlSelectWithoutTimestamp = "SELECT signon_realm, username_value, password_value, blacklisted_by_user FROM logins WHERE blacklisted_by_user != 1;" private let source: DataImport.Source diff --git a/DuckDuckGo/DataImport/Model/DataImportViewModel.swift b/DuckDuckGo/DataImport/Model/DataImportViewModel.swift index c0c7f1343e..1a2d1eef7b 100644 --- a/DuckDuckGo/DataImport/Model/DataImportViewModel.swift +++ b/DuckDuckGo/DataImport/Model/DataImportViewModel.swift @@ -286,7 +286,7 @@ struct DataImportViewModel { switch error { // chromium user denied keychain prompt error case let error as ChromiumLoginReader.ImportError where error.type == .userDeniedKeychainPrompt: - Pixel.fire(.dataImportFailed(source: importSource, sourceVersion: importSource.installedAppsMajorVersionDescription(selectedProfile: selectedProfile), error: error)) + Pixel.fire(.passwordImportKeychainPromptDenied) // stay on the same screen return true diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements index 98e0dcda6d..23abe2e6ab 100644 --- a/DuckDuckGo/DuckDuckGo.entitlements +++ b/DuckDuckGo/DuckDuckGo.entitlements @@ -13,8 +13,9 @@ com.apple.security.application-groups - $(NETP_APP_GROUP) $(DBP_APP_GROUP) + $(NETP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) com.apple.security.device.audio-input diff --git a/DuckDuckGo/DuckDuckGoAppStore.entitlements b/DuckDuckGo/DuckDuckGoAppStore.entitlements index b4153637dd..7b07bc92d8 100644 --- a/DuckDuckGo/DuckDuckGoAppStore.entitlements +++ b/DuckDuckGo/DuckDuckGoAppStore.entitlements @@ -2,12 +2,18 @@ + com.apple.developer.networking.networkextension + + packet-tunnel-provider + app-proxy-provider + com.apple.security.app-sandbox com.apple.security.application-groups $(DBP_APP_GROUP) $(NETP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) com.apple.security.device.audio-input @@ -19,11 +25,6 @@ com.apple.security.files.user-selected.read-write - com.apple.developer.networking.networkextension - - packet-tunnel-provider - app-proxy-provider - com.apple.security.network.client com.apple.security.personal-information.location diff --git a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements index 13ea43d233..8779a3f306 100644 --- a/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements +++ b/DuckDuckGo/DuckDuckGoAppStoreCI.entitlements @@ -7,6 +7,8 @@ com.apple.security.application-groups $(NETP_APP_GROUP) + $(DBP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) com.apple.security.device.audio-input diff --git a/DuckDuckGo/DuckDuckGoDebug.entitlements b/DuckDuckGo/DuckDuckGoDebug.entitlements index 688dc7b6d3..e5efc9872c 100644 --- a/DuckDuckGo/DuckDuckGoDebug.entitlements +++ b/DuckDuckGo/DuckDuckGoDebug.entitlements @@ -13,8 +13,9 @@ com.apple.security.application-groups - $(NETP_APP_GROUP) $(DBP_APP_GROUP) + $(NETP_APP_GROUP) + $(SUBSCRIPTION_APP_GROUP) com.apple.security.device.audio-input diff --git a/DuckDuckGo/Email/EmailUrlExtensions.swift b/DuckDuckGo/Email/EmailUrlExtensions.swift index 0055748b94..6b86f032c6 100644 --- a/DuckDuckGo/Email/EmailUrlExtensions.swift +++ b/DuckDuckGo/Email/EmailUrlExtensions.swift @@ -25,6 +25,7 @@ extension EmailUrls { static let emailProtectionLink = "https://duckduckgo.com/email" static let emailProtectionInContextSignupLink = "https://duckduckgo.com/email/start-incontext" static let emailProtectionAccountLink = "https://duckduckgo.com/email/settings/account" + static let emailProtectionSupportLink = "https://duckduckgo.com/email/settings/support" } var emailProtectionLink: URL { @@ -39,6 +40,10 @@ extension EmailUrls { return URL(string: Url.emailProtectionAccountLink)! } + var emailProtectionSupportLink: URL { + return URL(string: Url.emailProtectionSupportLink)! + } + func isDuckDuckGoEmailProtection(url: URL) -> Bool { return url.absoluteString.starts(with: Url.emailProtectionLink) } diff --git a/DuckDuckGo/FindInPage/FindInPageViewController.swift b/DuckDuckGo/FindInPage/FindInPageViewController.swift index 985c634777..142d9c1f18 100644 --- a/DuckDuckGo/FindInPage/FindInPageViewController.swift +++ b/DuckDuckGo/FindInPage/FindInPageViewController.swift @@ -59,6 +59,14 @@ final class FindInPageViewController: NSViewController { closeButton.toolTip = UserText.findInPageCloseTooltip nextButton.toolTip = UserText.findInPageNextTooltip previousButton.toolTip = UserText.findInPagePreviousTooltip + + nextButton.setAccessibilityIdentifier("FindInPageController.nextButton") + closeButton.setAccessibilityIdentifier("FindInPageController.closeButton") + previousButton.setAccessibilityIdentifier("FindInPageController.previousButton") + textField.setAccessibilityIdentifier("FindInPageController.textField") + textField.setAccessibilityRole(.textField) + statusField.setAccessibilityIdentifier("FindInPageController.statusField") + statusField.setAccessibilityRole(.textField) } @IBAction func findInPageNext(_ sender: Any?) { diff --git a/DuckDuckGo/Fire/View/FireViewController.swift b/DuckDuckGo/Fire/View/FireViewController.swift index a107578fef..1529734b5b 100644 --- a/DuckDuckGo/Fire/View/FireViewController.swift +++ b/DuckDuckGo/Fire/View/FireViewController.swift @@ -27,7 +27,7 @@ final class FireViewController: NSViewController { static let animationName = "01_Fire_really_small" } - private var fireViewModel: FireViewModel + private(set) var fireViewModel: FireViewModel private let tabCollectionViewModel: TabCollectionViewModel private var cancellables = Set() @@ -119,6 +119,7 @@ final class FireViewController: NSViewController { fakeFireButton.wantsLayer = true fakeFireButton.layer?.backgroundColor = NSColor.buttonMouseDown.cgColor + fakeFireButton.setAccessibilityIdentifier("FireViewController.fakeFireButton") subscribeToIsBurning() } diff --git a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift index af5658727d..6fb1869064 100644 --- a/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageContinueSetUpModel.swift @@ -51,10 +51,10 @@ extension HomePage.Models { } return false } - var isDay7SurveyEnabled: Bool { + var isDay14SurveyEnabled: Bool { let newTabContinueSetUpSettings = privacyConfigurationManager.privacyConfig.settings(for: .newTabContinueSetUp) - if let day7SurveyString = newTabContinueSetUpSettings["surveyCardDay7"] as? String { - if day7SurveyString == "enabled" { + if let day14SurveyString = newTabContinueSetUpSettings["surveyCardDay14"] as? String { + if day14SurveyString == "enabled" { return true } } @@ -64,15 +64,15 @@ extension HomePage.Models { let duckPlayerSettings = privacyConfigurationManager.privacyConfig.settings(for: .duckPlayer) return duckPlayerSettings["tryDuckPlayerLink"] as? String ?? "https://www.youtube.com/watch?v=yKWIA-Pys4c" } - var day0SurveyURL: String = "https://selfserve.decipherinc.com/survey/selfserve/32ab/230701?list=1" - var day7SurveyURL: String = "https://selfserve.decipherinc.com/survey/selfserve/32ab/230702?list=1" + var day0SurveyURL: String = "https://selfserve.decipherinc.com/survey/selfserve/32ab/240300?list=1" + var day14SurveyURL: String = "https://selfserve.decipherinc.com/survey/selfserve/32ab/240300?list=2" private let defaultBrowserProvider: DefaultBrowserProvider private let dataImportProvider: DataImportStatusProviding private let tabCollectionViewModel: TabCollectionViewModel private let emailManager: EmailManager - private let privacyPreferences: PrivacySecurityPreferences private let duckPlayerPreferences: DuckPlayerPreferencesPersistor + private let randomNumberGenerator: RandomNumberGenerating @UserDefaultsWrapper(key: .homePageShowAllFeatures, defaultValue: false) var shouldShowAllFeatures: Bool { @@ -102,12 +102,18 @@ extension HomePage.Models { @UserDefaultsWrapper(key: .shouldShowDBPWaitlistInvitedCardUI, defaultValue: false) private var shouldShowDBPWaitlistInvitedCardUI: Bool - @UserDefaultsWrapper(key: .homePageShowSurveyDay7, defaultValue: true) - private var shouldShowSurveyDay7: Bool + @UserDefaultsWrapper(key: .homePageShowSurveyDay14, defaultValue: true) + private var shouldShowSurveyDay14: Bool @UserDefaultsWrapper(key: .homePageIsFirstSession, defaultValue: true) private var isFirstSession: Bool + @UserDefaultsWrapper(key: .homePageShowSurveyDay0in10Percent, defaultValue: nil) + private var isPartOfSurveyDay0On10Percent: Bool? + + @UserDefaultsWrapper(key: .homePageShowSurveyDay14in10Percent, defaultValue: nil) + private var isPartOfSurveyDay14On10Percent: Bool? + @UserDefaultsWrapper(key: .firstLaunchDate, defaultValue: Calendar.current.date(byAdding: .month, value: -1, to: Date())!) private var firstLaunchDate: Date @@ -121,6 +127,8 @@ extension HomePage.Models { lazy var statisticsStore: StatisticsStore = LocalStatisticsStore() + lazy var waitlistBetaThankYouPresenter = WaitlistThankYouPromptPresenter() + lazy var listOfFeatures = isFirstSession ? firstRunFeatures : randomisedFeatures private var featuresMatrix: [[FeatureType]] = [[]] { @@ -135,18 +143,18 @@ extension HomePage.Models { dataImportProvider: DataImportStatusProviding, tabCollectionViewModel: TabCollectionViewModel, emailManager: EmailManager = EmailManager(), - privacyPreferences: PrivacySecurityPreferences = PrivacySecurityPreferences.shared, duckPlayerPreferences: DuckPlayerPreferencesPersistor, homePageRemoteMessaging: HomePageRemoteMessaging, - privacyConfigurationManager: PrivacyConfigurationManaging = AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager) { + privacyConfigurationManager: PrivacyConfigurationManaging = AppPrivacyFeatures.shared.contentBlocking.privacyConfigurationManager, + randomNumberGenerator: RandomNumberGenerating = RandomNumberGenerator()) { self.defaultBrowserProvider = defaultBrowserProvider self.dataImportProvider = dataImportProvider self.tabCollectionViewModel = tabCollectionViewModel self.emailManager = emailManager - self.privacyPreferences = privacyPreferences self.duckPlayerPreferences = duckPlayerPreferences self.homePageRemoteMessaging = homePageRemoteMessaging self.privacyConfigurationManager = privacyConfigurationManager + self.randomNumberGenerator = randomNumberGenerator refreshFeaturesMatrix() @@ -159,6 +167,7 @@ extension HomePage.Models { switch featureType { case .defaultBrowser: do { + Pixel.fire(.defaultRequestedFromHomepageSetupView) try defaultBrowserProvider.presentDefaultBrowserPrompt() } catch { defaultBrowserProvider.openSystemPreferences() @@ -175,8 +184,8 @@ extension HomePage.Models { tabCollectionViewModel.append(tab: tab) case .surveyDay0: visitSurvey(day: .day0) - case .surveyDay7: - visitSurvey(day: .day7) + case .surveyDay14: + visitSurvey(day: .day14) case .networkProtectionRemoteMessage(let message): handle(remoteMessage: message) case .dataBrokerProtectionRemoteMessage(let message): @@ -185,9 +194,16 @@ extension HomePage.Models { #if DBP DataBrokerProtectionAppEvents().handleWaitlistInvitedNotification(source: .cardUI) #endif + case .vpnThankYou: + guard let window = NSApp.keyWindow else { return } + waitlistBetaThankYouPresenter.presentVPNThankYouPrompt(in: window) + case .pirThankYou: + guard let window = NSApp.keyWindow else { return } + waitlistBetaThankYouPresenter.presentPIRThankYouPrompt(in: window) } } + // swiftlint:disable:next cyclomatic_complexity func removeItem(for featureType: FeatureType) { switch featureType { case .defaultBrowser: @@ -200,8 +216,8 @@ extension HomePage.Models { shouldShowEmailProtectionSetting = false case .surveyDay0: shouldShowSurveyDay0 = false - case .surveyDay7: - shouldShowSurveyDay7 = false + case .surveyDay14: + shouldShowSurveyDay14 = false case .networkProtectionRemoteMessage(let message): #if NETWORK_PROTECTION homePageRemoteMessaging.networkProtectionRemoteMessaging.dismiss(message: message) @@ -214,6 +230,10 @@ extension HomePage.Models { #endif case .dataBrokerProtectionWaitlistInvited: shouldShowDBPWaitlistInvitedCardUI = false + case .vpnThankYou: + waitlistBetaThankYouPresenter.didDismissVPNThankYouCard() + case .pirThankYou: + waitlistBetaThankYouPresenter.didDismissPIRThankYouCard() } refreshFeaturesMatrix() } @@ -245,6 +265,14 @@ extension HomePage.Models { } #endif + if waitlistBetaThankYouPresenter.canShowVPNCard { + features.append(.vpnThankYou) + } + + if waitlistBetaThankYouPresenter.canShowPIRCard { + features.append(.pirThankYou) + } + for feature in listOfFeatures { switch feature { case .defaultBrowser: @@ -266,12 +294,17 @@ extension HomePage.Models { case .surveyDay0: if shouldSurveyDay0BeVisible { features.append(feature) + userInteractedWithSurveyDay0 = true } - case .surveyDay7: - if shouldSurveyDay7BeVisible { + case .surveyDay14: + if shouldSurveyDay14BeVisible { features.append(feature) } - case .networkProtectionRemoteMessage, .dataBrokerProtectionRemoteMessage, .dataBrokerProtectionWaitlistInvited: + case .networkProtectionRemoteMessage, + .dataBrokerProtectionRemoteMessage, + .dataBrokerProtectionWaitlistInvited, + .vpnThankYou, + .pirThankYou: break // Do nothing, these messages get appended first } } @@ -353,22 +386,39 @@ extension HomePage.Models { let oneDayAgo = Calendar.current.date(byAdding: .weekday, value: -1, to: Date())! return isDay0SurveyEnabled && shouldShowSurveyDay0 && - !userInteractedWithSurveyDay0 && - firstLaunchDate > oneDayAgo + firstLaunchDate >= oneDayAgo && + Bundle.main.preferredLocalizations.first == "en" && + isPartOfSurveyDay0On10Percent ?? calculateIfIn10percent(day: .day0) } - private var shouldSurveyDay7BeVisible: Bool { - let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())! - return isDay7SurveyEnabled && + private var shouldSurveyDay14BeVisible: Bool { + let fourteenDaysAgo = Calendar.current.date(byAdding: .weekday, value: -14, to: Date())! + let fifteenDaysAgo = Calendar.current.date(byAdding: .weekday, value: -15, to: Date())! + return isDay14SurveyEnabled && shouldShowSurveyDay0 && - shouldShowSurveyDay7 && + shouldShowSurveyDay14 && !userInteractedWithSurveyDay0 && - firstLaunchDate <= oneWeekAgo + firstLaunchDate >= fifteenDaysAgo && + firstLaunchDate <= fourteenDaysAgo && + Bundle.main.preferredLocalizations.first == "en" && + isPartOfSurveyDay14On10Percent ?? calculateIfIn10percent(day: .day14) + } + + private func calculateIfIn10percent(day: SurveyDay) -> Bool { + let randomNumber0To99 = randomNumberGenerator.random(in: 0..<100) + let isInSurvey10Percent = randomNumber0To99 < 10 + switch day { + case .day0: + isPartOfSurveyDay0On10Percent = isInSurvey10Percent + case .day14: + isPartOfSurveyDay14On10Percent = isInSurvey10Percent + } + return isInSurvey10Percent } private enum SurveyDay { case day0 - case day7 + case day14 } @MainActor private func visitSurvey(day: SurveyDay) { @@ -376,11 +426,8 @@ extension HomePage.Models { switch day { case .day0: surveyURLString = day0SurveyURL - case .day7: - surveyURLString = day7SurveyURL - } - if let atb = statisticsStore.atb { - surveyURLString += "&atb=\(atb)" + case .day14: + surveyURLString = day14SurveyURL } if let url = URL(string: surveyURLString) { @@ -388,9 +435,9 @@ extension HomePage.Models { tabCollectionViewModel.append(tab: tab) switch day { case .day0: - userInteractedWithSurveyDay0 = true - case .day7: - shouldShowSurveyDay7 = false + shouldShowSurveyDay0 = false + case .day14: + shouldShowSurveyDay14 = false } } } @@ -455,7 +502,7 @@ extension HomePage.Models { // We ignore the `networkProtectionRemoteMessage` case here to avoid it getting accidentally included - it has special handling and will get // included elsewhere. static var allCases: [HomePage.Models.FeatureType] { - [.duckplayer, .emailProtection, .defaultBrowser, .importBookmarksAndPasswords, .surveyDay0, .surveyDay7] + [.duckplayer, .emailProtection, .defaultBrowser, .importBookmarksAndPasswords, .surveyDay0, .surveyDay14] } case duckplayer @@ -463,10 +510,12 @@ extension HomePage.Models { case defaultBrowser case importBookmarksAndPasswords case surveyDay0 - case surveyDay7 + case surveyDay14 case networkProtectionRemoteMessage(NetworkProtectionRemoteMessage) case dataBrokerProtectionRemoteMessage(DataBrokerProtectionRemoteMessage) case dataBrokerProtectionWaitlistInvited + case vpnThankYou + case pirThankYou var title: String { switch self { @@ -480,14 +529,18 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionCardTitle case .surveyDay0: return UserText.newTabSetUpSurveyDay0CardTitle - case .surveyDay7: - return UserText.newTabSetUpSurveyDay7CardTitle + case .surveyDay14: + return UserText.newTabSetUpSurveyDay14CardTitle case .networkProtectionRemoteMessage(let message): return message.cardTitle case .dataBrokerProtectionRemoteMessage(let message): return message.cardTitle case .dataBrokerProtectionWaitlistInvited: return "Personal Information Removal" + case .vpnThankYou: + return "Thanks for testing DuckDuckGo VPN!" + case .pirThankYou: + return "Thanks for testing Personal Information Removal!" } } @@ -503,14 +556,18 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionSummary case .surveyDay0: return UserText.newTabSetUpSurveyDay0Summary - case .surveyDay7: - return UserText.newTabSetUpSurveyDay7Summary + case .surveyDay14: + return UserText.newTabSetUpSurveyDay14Summary case .networkProtectionRemoteMessage(let message): return message.cardDescription case .dataBrokerProtectionRemoteMessage(let message): return message.cardDescription case .dataBrokerProtectionWaitlistInvited: return "You're invited to try Personal Information Removal beta!" + case .vpnThankYou: + return "To keep using it, subscribe to DuckDuckGo Privacy Pro." + case .pirThankYou: + return "To keep using it, subscribe to DuckDuckGo Privacy Pro." } } @@ -526,14 +583,18 @@ extension HomePage.Models { return UserText.newTabSetUpEmailProtectionAction case .surveyDay0: return UserText.newTabSetUpSurveyDay0Action - case .surveyDay7: - return UserText.newTabSetUpSurveyDay7Action + case .surveyDay14: + return UserText.newTabSetUpSurveyDay14Action case .networkProtectionRemoteMessage(let message): return message.action.actionTitle case .dataBrokerProtectionRemoteMessage(let message): return message.action.actionTitle case .dataBrokerProtectionWaitlistInvited: return "Get Started" + case .vpnThankYou: + return "See Special Offer For Testers" + case .pirThankYou: + return "See Special Offer For Testers" } } @@ -550,15 +611,19 @@ extension HomePage.Models { case .emailProtection: return .inbox128.resized(to: iconSize)! case .surveyDay0: - return .survey128.resized(to: iconSize)! - case .surveyDay7: - return .survey128.resized(to: iconSize)! + return .qandA128.resized(to: iconSize)! + case .surveyDay14: + return .qandA128.resized(to: iconSize)! case .networkProtectionRemoteMessage: return .vpnEnded.resized(to: iconSize)! case .dataBrokerProtectionRemoteMessage: return .dbpInformationRemover.resized(to: iconSize)! case .dataBrokerProtectionWaitlistInvited: return .dbpInformationRemover.resized(to: iconSize)! + case .vpnThankYou: + return .vpnEnded.resized(to: iconSize)! + case .pirThankYou: + return .dbpInformationRemover.resized(to: iconSize)! } } } @@ -615,3 +680,13 @@ struct HomePageRemoteMessaging { #endif } + +public protocol RandomNumberGenerating { + func random(in range: Range) -> Int +} + +struct RandomNumberGenerator: RandomNumberGenerating { + func random(in range: Range) -> Int { + return Int.random(in: range) + } +} diff --git a/DuckDuckGo/HomePage/Model/HomePageFavoritesModel.swift b/DuckDuckGo/HomePage/Model/HomePageFavoritesModel.swift index 0860f02cfc..e88a48b1b0 100644 --- a/DuckDuckGo/HomePage/Model/HomePageFavoritesModel.swift +++ b/DuckDuckGo/HomePage/Model/HomePageFavoritesModel.swift @@ -86,14 +86,16 @@ extension HomePage.Models { let open: (Bookmark, OpenTarget) -> Void let removeFavorite: (Bookmark) -> Void let deleteBookmark: (Bookmark) -> Void - let addEdit: (Bookmark?) -> Void + let add: () -> Void + let edit: (Bookmark) -> Void let moveFavorite: (Bookmark, Int) -> Void let onFaviconMissing: () -> Void init(open: @escaping (Bookmark, OpenTarget) -> Void, removeFavorite: @escaping (Bookmark) -> Void, deleteBookmark: @escaping (Bookmark) -> Void, - addEdit: @escaping (Bookmark?) -> Void, + add: @escaping () -> Void, + edit: @escaping (Bookmark) -> Void, moveFavorite: @escaping (Bookmark, Int) -> Void, onFaviconMissing: @escaping () -> Void ) { @@ -102,7 +104,8 @@ extension HomePage.Models { self.open = open self.removeFavorite = removeFavorite self.deleteBookmark = deleteBookmark - self.addEdit = addEdit + self.add = add + self.edit = edit self.moveFavorite = moveFavorite self.onFaviconMissing = onFaviconMissing } @@ -119,12 +122,12 @@ extension HomePage.Models { open(bookmark, .current) } - func edit(_ bookmark: Bookmark) { - addEdit(bookmark) + func editBookmark(_ bookmark: Bookmark) { + edit(bookmark) } func addNew() { - addEdit(nil) + add() } private func updateVisibleModels() { diff --git a/DuckDuckGo/HomePage/View/FavoritesView.swift b/DuckDuckGo/HomePage/View/FavoritesView.swift index 90956a4a66..52e7f585d1 100644 --- a/DuckDuckGo/HomePage/View/FavoritesView.swift +++ b/DuckDuckGo/HomePage/View/FavoritesView.swift @@ -305,14 +305,17 @@ struct Favorite: View { let bookmark: Bookmark // Maintain separate copies of bookmark metadata required by the view, in order to ensure that SwiftUI re-renders correctly. + // Do not remove these properties even if some are not used in the `FavoriteTemplate` view as the view will not re-render correctly. private let bookmarkTitle: String private let bookmarkURL: URL + private let bookmarkParentFolder: String? init?(bookmark: Bookmark) { guard let urlObject = bookmark.urlObject else { return nil } self.bookmark = bookmark self.bookmarkTitle = bookmark.title self.bookmarkURL = urlObject + self.bookmarkParentFolder = bookmark.parentFolderUUID } var body: some View { diff --git a/DuckDuckGo/HomePage/View/HomePageViewController.swift b/DuckDuckGo/HomePage/View/HomePageViewController.swift index ae40aff5df..96fb2aca47 100644 --- a/DuckDuckGo/HomePage/View/HomePageViewController.swift +++ b/DuckDuckGo/HomePage/View/HomePageViewController.swift @@ -42,7 +42,9 @@ final class HomePageViewController: NSViewController { var defaultBrowserModel: HomePage.Models.DefaultBrowserModel! var recentlyVisitedModel: HomePage.Models.RecentlyVisitedModel! var featuresModel: HomePage.Models.ContinueSetUpModel! - var appearancePreferences: AppearancePreferences! + let accessibilityPreferences: AccessibilityPreferences + let appearancePreferences: AppearancePreferences + let defaultBrowserPreferences: DefaultBrowserPreferences var cancellables = Set() @UserDefaultsWrapper(key: .defaultBrowserDismissed, defaultValue: false) @@ -56,13 +58,19 @@ final class HomePageViewController: NSViewController { bookmarkManager: BookmarkManager, historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared, fireViewModel: FireViewModel? = nil, - onboardingViewModel: OnboardingViewModel = OnboardingViewModel()) { + onboardingViewModel: OnboardingViewModel = OnboardingViewModel(), + accessibilityPreferences: AccessibilityPreferences = AccessibilityPreferences.shared, + appearancePreferences: AppearancePreferences = AppearancePreferences.shared, + defaultBrowserPreferences: DefaultBrowserPreferences = DefaultBrowserPreferences.shared) { self.tabCollectionViewModel = tabCollectionViewModel self.bookmarkManager = bookmarkManager self.historyCoordinating = historyCoordinating self.fireViewModel = fireViewModel ?? FireCoordinator.fireViewModel self.onboardingViewModel = onboardingViewModel + self.accessibilityPreferences = accessibilityPreferences + self.appearancePreferences = appearancePreferences + self.defaultBrowserPreferences = defaultBrowserPreferences super.init(nibName: nil, bundle: nil) } @@ -72,7 +80,6 @@ final class HomePageViewController: NSViewController { defaultBrowserModel = createDefaultBrowserModel() recentlyVisitedModel = createRecentlyVisitedModel() featuresModel = createFeatureModel() - appearancePreferences = AppearancePreferences.shared refreshModels() @@ -81,6 +88,7 @@ final class HomePageViewController: NSViewController { .environmentObject(defaultBrowserModel) .environmentObject(recentlyVisitedModel) .environmentObject(featuresModel) + .environmentObject(accessibilityPreferences) .environmentObject(appearancePreferences) .onTapGesture { [weak self] in // Remove focus from the address bar if interacting with this view. @@ -149,8 +157,9 @@ final class HomePageViewController: NSViewController { } func createDefaultBrowserModel() -> HomePage.Models.DefaultBrowserModel { - return .init(isDefault: DefaultBrowserPreferences().isDefault, wasClosed: defaultBrowserDismissed, requestSetDefault: { [weak self] in - let defaultBrowserPreferencesModel = DefaultBrowserPreferences() + return .init(isDefault: DefaultBrowserPreferences.shared.isDefault, wasClosed: defaultBrowserDismissed, requestSetDefault: { [weak self] in + Pixel.fire(.defaultRequestedFromHomepage) + let defaultBrowserPreferencesModel = DefaultBrowserPreferences.shared defaultBrowserPreferencesModel.becomeDefault { [weak self] isDefault in _ = defaultBrowserPreferencesModel self?.defaultBrowserModel.isDefault = isDefault @@ -172,8 +181,10 @@ final class HomePageViewController: NSViewController { self?.bookmarkManager.update(bookmark: bookmark) }, deleteBookmark: { [weak self] bookmark in self?.bookmarkManager.remove(bookmark: bookmark) - }, addEdit: { [weak self] bookmark in - self?.showAddEditController(for: bookmark) + }, add: { [weak self] in + self?.showAddController() + }, edit: { [weak self] bookmark in + self?.showEditController(for: bookmark) }, moveFavorite: { [weak self] (bookmark, index) in self?.bookmarkManager.moveFavorites(with: [bookmark.id], toIndex: index) { _ in } }, onFaviconMissing: { [weak self] in @@ -194,7 +205,7 @@ final class HomePageViewController: NSViewController { } func refreshDefaultBrowserModel() { - let prefs = DefaultBrowserPreferences() + let prefs = DefaultBrowserPreferences.shared if prefs.isDefault { defaultBrowserDismissed = false } @@ -204,7 +215,7 @@ final class HomePageViewController: NSViewController { } func subscribeToBookmarks() { - bookmarkManager.listPublisher.receive(on: RunLoop.main).sink { [weak self] _ in + bookmarkManager.listPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in withAnimation { self?.refreshFavoritesModel() } @@ -230,9 +241,14 @@ final class HomePageViewController: NSViewController { tabCollectionViewModel.selectedTabViewModel?.tab.setContent(.contentFromURL(url, source: .bookmark)) } - private func showAddEditController(for bookmark: Bookmark? = nil) { - AddBookmarkModalView(model: AddBookmarkModalViewModel(originalBookmark: bookmark, isFavorite: true)) - .show(in: self.view.window) + private func showAddController() { + BookmarksDialogViewFactory.makeAddFavoriteView() + .show(in: view.window) + } + + private func showEditController(for bookmark: Bookmark) { + BookmarksDialogViewFactory.makeEditBookmarkView(bookmark: bookmark) + .show(in: view.window) } private var burningDataCancellable: AnyCancellable? diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 8c7dca0e52..bdb809d70e 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -60,7 +60,7 @@ CFBundleTypeRole Viewer CFBundleURLName - Network Protection URLs + VPN URLs CFBundleURLSchemes networkprotection diff --git a/DuckDuckGo/InfoPlist.xcstrings b/DuckDuckGo/InfoPlist.xcstrings index ee95494024..e4f7972ce7 100644 --- a/DuckDuckGo/InfoPlist.xcstrings +++ b/DuckDuckGo/InfoPlist.xcstrings @@ -7,7 +7,7 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, @@ -19,43 +19,43 @@ }, "es" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "fr" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "it" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "nl" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "pl" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "pt" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } }, "ru" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "DuckDuckGo" } } @@ -363,4 +363,4 @@ } }, "version" : "1.0" -} \ No newline at end of file +} diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 9347c6977d..ce218cd1b2 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -53,8 +53,57 @@ } } }, - "-1 -> 0 ... 1" : { - + " %@" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : " %@" + } + } + } }, "**%lld** tracking attempts blocked" : { "comment" : "The number of tracking attempts blocked in the last 7 days, shown on a new tab, translate as: Tracking attempts blocked: %@", @@ -430,24 +479,6 @@ } } } - }, - "0->1->nil" : { - - }, - "10%" : { - - }, - "20%" : { - - }, - "50%" : { - - }, - "80%" : { - - }, - "100%" : { - }, "about.app_name" : { "comment" : "Application name to be displayed in the About dialog", @@ -908,66 +939,6 @@ } } }, - "add.folder.name" : { - "comment" : "Add Folder popover: folder name text field title", - "extractionState" : "extracted_with_value", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Name:" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Name:" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nombre:" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nom :" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nome:" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Naam:" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nazwa:" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nome:" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Имя:" - } - } - } - }, "add.link.to.bookmarks" : { "comment" : "Context menu item", "extractionState" : "extracted_with_value", @@ -2026,9 +1997,6 @@ } } } - }, - "Animate" : { - }, "auth.alert.login.button" : { "comment" : "Authentication Alert Sign In Button", @@ -2932,7 +2900,7 @@ }, "autoconsent.title" : { "comment" : "Autoconsent settings section title", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -8959,7 +8927,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Add bookmark" + "value" : "Add Bookmark" } }, "es" : { @@ -9079,7 +9047,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Edit bookmark" + "value" : "Edit Bookmark" } }, "es" : { @@ -15626,66 +15594,6 @@ } } }, - "edit.folder" : { - "comment" : "Header of the view that edits a bookmark folder", - "extractionState" : "extracted_with_value", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ordner bearbeiten" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Edit Folder" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Editar carpeta" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Modifier le dossier" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Modifica cartella" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Map bewerken" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Edytuj folder" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Editar pasta" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Изменить папку" - } - } - } - }, "email.copied" : { "comment" : "Notification that the Private email address was copied to clipboard after the user generated a new address", "extractionState" : "extracted_with_value", @@ -16046,6 +15954,66 @@ } } }, + "email.protection.explanation" : { + "comment" : "Email protection feature explanation in settings. The feature blocks email trackers and hides original email address.", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "E-Mail-Tracker blockieren und deine Adresse verbergen, ohne den E-Mail-Anbieter zu wechseln." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Block email trackers and hide your address without switching your email provider." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bloquea los rastreadores de correo electrónico y oculta tu dirección sin cambiar de proveedor de correo electrónico." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bloquez les traqueurs d'e-mails et masquez votre adresse sans changer de fournisseur de messagerie." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blocca i sistemi di tracciamento delle e-mail e nascondi il tuo indirizzo, senza cambiare il provider di posta elettronica." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Blokkeer e-mailtrackers en verberg je adres zonder van e-mailprovider te wisselen." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zablokuj mechanizmy śledzące pocztę e-mail i ukryj swój adres bez zmiany dostawcy poczty e-mail." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bloqueia os rastreadores de e-mail e oculta o teu endereço sem alterares o teu fornecedor de e-mail." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Чтобы блокировать трекеры и скрывать свой адрес, вовсе не нужно менять почтовый сервис." + } + } + } + }, "Enter Full Screen" : { "comment" : "Main Menu View item", "localizations" : { @@ -19687,7 +19655,7 @@ }, "gpc.title" : { "comment" : "GPC settings title", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -24294,9 +24262,6 @@ } } } - }, - "Indeterminate (-1)" : { - }, "invite.dialog.get.started.button" : { "comment" : "Get Started button on an invite dialog", @@ -24584,59 +24549,6 @@ } } }, - "Location:" : { - "comment" : "Add Folder popover: parent folder picker title", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Standort:" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ubicación:" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Emplacement :" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Posizione:" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Locatie:" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lokalizacja:" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Localização:" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Папка:" - } - } - } - }, "looking.for.bitwarden" : { "comment" : "Setup of the integration with Bitwarden app", "extractionState" : "extracted_with_value", @@ -29509,120 +29421,120 @@ } }, "newTab.setup.survey.day.0.action" : { - "comment" : "Action title of the Day 0 durvey of the Set Up section in the home page", + "comment" : "Action title of the Day 0 survey of the Set Up section in the home page", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Teile deine Gedanken" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Share Your Thoughts" + "value" : "Sign Up To Participate" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Comparte tus ideas" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Partagez votre avis" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Comunicaci la tua opinione" } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Deel je gedachten" } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Podziel się przemyśleniami" } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Partilha as tuas opiniões" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Поделиться соображениями" } } } }, "newTab.setup.survey.day.0.summary" : { - "comment" : "Summary of the Day 0 durvey of the Set Up section in the home page", + "comment" : "Summary of the card on the new tab page that invites users to partecipate to a survey", "extractionState" : "extracted_with_value", "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Nimm an unserer kurzen Umfrage teil und hilf uns, den besten Browser zu entwickeln." } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Take our short survey and help us build the best browser." + "value" : "Join an interview with a member of our research team to help us build the best browser." } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Responde a nuestra breve encuesta y ayúdanos a crear el mejor navegador." } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Répondez à notre courte enquête et aidez-nous à créer le meilleur navigateur." } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Rispondi al nostro breve sondaggio e aiutaci a creare il browser migliore." } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Vul onze korte enquête in en help ons de beste browser te bouwen." } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Weź udział w krótkiej ankiecie i pomóż nam opracować najlepszą przeglądarkę." } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Responde ao nosso curto inquérito e ajuda-nos a criar o melhor navegador." } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Пройдите короткий опрос и помогите DuckDuckGo стать лучшим из браузеров." } } @@ -29634,55 +29546,55 @@ "localizations" : { "de" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Sag uns, was dich hierher gebracht hat" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Tell Us What Brought You Here" + "value" : "Share Your Thoughts With Us" } }, "es" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Cuéntanos qué te ha traído hasta aquí" } }, "fr" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Dites-nous ce qui vous amène ici" } }, "it" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Raccontaci cosa ti ha portato qui" } }, "nl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Vertel ons wat je hier heeft gebracht" } }, "pl" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Powiedz nam, co Cię tu sprowadziło" } }, "pt" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Diz-nos o que te trouxe aqui" } }, "ru" : { "stringUnit" : { - "state" : "translated", + "state" : "needs_review", "value" : "Что привело вас к нам" } } @@ -29690,7 +29602,7 @@ }, "newTab.setup.survey.day.7.action" : { "comment" : "Action title of the Day 7 durvey of the Set Up section in the home page", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -29750,7 +29662,7 @@ }, "newTab.setup.survey.day.7.summary" : { "comment" : "Summary of the Day 7 durvey of the Set Up section in the home page", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -29810,7 +29722,7 @@ }, "newTab.setup.survey.day.7.title" : { "comment" : "Title of the Day 7 durvey of the Set Up section in the home page", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -29868,6 +29780,42 @@ } } }, + "newTab.setup.survey.day.14.action" : { + "comment" : "Action title of the Day 14 survey of the Set Up section in the home page", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Sign Up To Participate" + } + } + } + }, + "newTab.setup.survey.day.14.summary" : { + "comment" : "Summary of the card on the new tab page that invites users to partecipate to a survey", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Join an interview with a member of our research team to help us build the best browser." + } + } + } + }, + "newTab.setup.survey.day.14.title" : { + "comment" : "Title of the Day 14 durvey of the Set Up section in the home page", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Share Your Thoughts With Us" + } + } + } + }, "next" : { "comment" : "Next button", "extractionState" : "extracted_with_value", @@ -29927,9 +29875,6 @@ } } } - }, - "nil->0->nil" : { - }, "no.access.to.downloads.folder.header" : { "comment" : "Header of the alert dialog warning the user they need to give the browser permission to access the Downloads folder", @@ -31815,7 +31760,7 @@ "it" : { "stringUnit" : { "state" : "translated", - "value" : "Apri collegamento in una nuova scheda" + "value" : "Apri link in una nuova scheda" } }, "nl" : { @@ -41830,7 +41775,7 @@ } }, "preferences.about" : { - "comment" : "Show about screen", + "comment" : "Title of the option to show the About screen", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -42309,8 +42254,128 @@ } } }, + "preferences.accessibility" : { + "comment" : "Title of the option to show the Accessibility browser preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Barrierefreiheit" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Accessibility" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Accesibilidad" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Accessibilité" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Accessibilità" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toegankelijkheid" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dostępność" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acessibilidade" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Универсальный доступ" + } + } + } + }, + "preferences.always-on" : { + "comment" : "Status indicator of a browser privacy protection feature.", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Immer aktiviert" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Always On" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Siempre activado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Toujours activé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sempre attiva" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Altijd aan" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Zawsze włączone" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sempre ligada" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Всегда включено" + } + } + } + }, "preferences.appearance" : { - "comment" : "Show appearance preferences", + "comment" : "Title of the option to show the Appearance preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -42791,7 +42856,7 @@ }, "preferences.appearance.zoom" : { "comment" : "Zoom settings section title", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -42916,61 +42981,241 @@ "de" : { "stringUnit" : { "state" : "translated", - "value" : "Autovervollständigen" + "value" : "Passwörter" } }, "en" : { "stringUnit" : { "state" : "new", - "value" : "Autofill" + "value" : "Passwords" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Autocompletar" + "value" : "Contraseñas" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Saisie automatique" + "value" : "Mots de passe" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Compilazione automatica" + "value" : "Password" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Automatisch invullen" + "value" : "Wachtwoorden" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Autouzupełnianie" + "value" : "Hasła" } }, "pt" : { "stringUnit" : { "state" : "translated", - "value" : "Preenchimento automático" + "value" : "Palavras-passe" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Автозаполнение" + "value" : "Пароли" + } + } + } + }, + "preferences.autofill-enabled-for" : { + "comment" : "Label presented before the email account in email protection preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autovervollständigen in diesem Browser aktiviert für:" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Autofill enabled in this browser for:" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autocompletar habilitado en este navegador para:" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "La saisie automatique est activée dans ce navigateur pour :" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Compilazione automatica abilitata in questo browser per:" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "'Automatisch aanvullen' is ingeschakeld in deze browser voor:" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Autouzupełnianie włączone w tej przeglądarce dla:" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Preenchimento automático ativado neste navegador para:" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "В этом браузере включено автозаполнение для:" + } + } + } + }, + "preferences.cookie-pop-up-protection" : { + "comment" : "Title of the option to show the Cookie Pop-Up Protection preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cookie-Pop-up-Schutz" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Cookie Pop-Up Protection" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protección contra ventanas emergentes de cookies" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protection contre les fenêtres contextuelles des cookies" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protezione pop-up dei cookie" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bescherming tegen cookiepop-ups" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ochrona przed wyskakującymi okienkami dotyczącymi plików cookie" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteção contra pop-ups de cookies" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита от всплывающих окон куки" + } + } + } + }, + "preferences.data-clearing" : { + "comment" : "Title of the option to show the Data Clearing preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datenlöschung" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Data Clearing" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Eliminación de datos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Effacement des données" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancellazione dati" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gegevens wissen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Czyszczenie danych" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Limpeza de Dados" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Очистка данных" } } } }, "preferences.default-browser" : { - "comment" : "Show default browser preferences", + "comment" : "Title of the option to show the Default Browser Preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43210,7 +43455,7 @@ } }, "preferences.downloads" : { - "comment" : "Show downloads browser preferences", + "comment" : "Title of the downloads browser preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43270,7 +43515,7 @@ } }, "preferences.duck-player" : { - "comment" : "Show Duck Player browser preferences", + "comment" : "Title of the option to show the Duck Player browser preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43329,8 +43574,128 @@ } } }, + "preferences.duckduckgo-on-other-platforms" : { + "comment" : "Button presented to users to navigate them to our product page which presents all other products for other platforms", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo auf anderen Plattformen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "DuckDuckGo on Other Platforms" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo en otras plataformas" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo sur d'autres plateformes" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo su altre piattaforme" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo op andere platforms" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo na innych platformach" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Noutras Plataformas" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo для других платформ" + } + } + } + }, + "preferences.email-protection" : { + "comment" : "Title of the option to show the Email Protection preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "E-Mail-Schutz" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Email Protection" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protección del correo electrónico" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protection des e-mails" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protezione email" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "E-mailbescherming" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ochrona poczty e-mail" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteção de e-mail" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита электронной почты" + } + } + } + }, "preferences.general" : { - "comment" : "Show general preferences", + "comment" : "Title of the option to show the General preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43389,6 +43754,186 @@ } } }, + "preferences.main-settings" : { + "comment" : "Section header in Preferences for main settings", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Haupteinstellungen" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Main Settings" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajustes principales" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Réglages principaux" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impostazioni principali" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hoofdinstellingen" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ustawienia główne" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Definições Principais" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Основные настройки" + } + } + } + }, + "preferences.off" : { + "comment" : "Status indicator of a browser privacy protection feature.", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aus" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Off" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desactivado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Désactivé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disattivato" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Uit" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wył." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desligado" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Выкл." + } + } + } + }, + "preferences.on" : { + "comment" : "Status indicator of a browser privacy protection feature.", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "An" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "On" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Activé" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "On" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aan" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wł." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ligado" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вкл." + } + } + } + }, "preferences.on-startup" : { "comment" : "Name of the preferences section related to app startup", "extractionState" : "extracted_with_value", @@ -43451,7 +43996,7 @@ }, "preferences.privacy" : { "comment" : "Show privacy browser preferences", - "extractionState" : "extracted_with_value", + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { @@ -43509,6 +44054,126 @@ } } }, + "preferences.privacy-protections" : { + "comment" : "The section header in Preferences representing browser features related to privacy protection", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Datenschutz" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Privacy Protections" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protecciones de privacidad" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protections de la confidentialité" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protezioni della Privacy" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privacybescherming" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mechanizmy Ochrony Prywatności" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteções de Privacidade" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита конфиденциальности" + } + } + } + }, + "preferences.private-search" : { + "comment" : "Title of the option to show the Private Search preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Private Suche" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Private Search" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Búsqueda privada" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recherche privée" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ricerca Privata" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Privézoekopdracht" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Prywatne wyszukiwanie" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pesquisa Privada" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Частный поиск" + } + } + } + }, "preferences.reopen-windows" : { "comment" : "Option to control session restoration", "extractionState" : "extracted_with_value", @@ -43629,8 +44294,68 @@ } } }, + "preferences.support" : { + "comment" : "Open support page", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterstützung" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Support" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ayuda" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aide" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supporto" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Support" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wsparcie" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assistência" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поддержка" + } + } + } + }, "preferences.sync" : { - "comment" : "Show sync preferences", + "comment" : "Title of the option to show the Sync preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43750,7 +44475,7 @@ } }, "preferences.vpn" : { - "comment" : "Show VPN preferences", + "comment" : "Title of the option to show the VPN preferences", "extractionState" : "extracted_with_value", "localizations" : { "de" : { @@ -43809,6 +44534,66 @@ } } }, + "preferences.web-tracking-protection" : { + "comment" : "Title of the option to show the Web Tracking Protection preferences", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Web Tracking Protection" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Web Tracking Protection" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protección de rastreo en la web" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protection contre le pistage sur le Web" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Protezione dal tracciamento web" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bescherming tegen webtracking" + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ochrona przed śledzeniem w sieci" + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "Proteção contra rastreamento na internet" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Защита от отслеживания онлайн" + } + } + } + }, "print.menu.item" : { "comment" : "Menu item title", "extractionState" : "extracted_with_value", @@ -43869,6 +44654,66 @@ } } }, + "private.search.explenation" : { + "comment" : "feature explanation in settings", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search ist deine Standardsuchmaschine – du kannst also das Web durchsuchen, ohne getrackt zu werden." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "DuckDuckGo Private Search is your default search engine, so you can search the web without being tracked." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La búsqueda privada de DuckDuckGo es tu motor de búsqueda predeterminado para que puedas buscar en la web sin que te rastreen." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search est votre moteur de recherche par défaut, qui vous permet d'effectuer des recherches sur le Web sans être suivi(e)." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search è il tuo motore di ricerca predefinito, quindi puoi effettuare ricerche sul Web senza essere tracciato." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search is je standaardzoekmachine, zodat je op het web kunt zoeken zonder gevolgd te worden." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search to domyślna wyszukiwarka, dzięki której możesz wyszukiwać treści w sieci, unikając śledzenia." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "O DuckDuckGo Private Search é o teu motor de pesquisa predefinido, para que possas pesquisar a web sem seres rastreado." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo Private Search — ваша поисковая система по умолчанию. Вы можете пользоваться поиском в интернете, не опасаясь слежки." + } + } + } + }, "quit" : { "comment" : "Quit button", "extractionState" : "extracted_with_value", @@ -44500,9 +45345,6 @@ } } } - }, - "Reset (nil)" : { - }, "restart.bitwarden" : { "comment" : "Button to restart Bitwarden application", @@ -46610,9 +47452,6 @@ } } } - }, - "Slow animations" : { - }, "Smart Copy/Paste" : { "comment" : "Main Menu Edit-Substitutions item", @@ -47670,59 +48509,6 @@ } } }, - "Title:" : { - "comment" : "Add Bookmark dialog bookmark title field heading", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel:" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Título:" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titre :" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titolo:" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Titel:" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tytuł:" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Título:" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Название:" - } - } - } - }, "tooltip.addToFavorites" : { "comment" : "Tooltip for add to favorites button", "extractionState" : "extracted_with_value", @@ -50127,7 +50913,7 @@ } } }, - "web.tracking.protection.explenation" : { + "web.tracking.protection.explanation" : { "comment" : "feature explanation in settings", "extractionState" : "extracted_with_value", "localizations" : { @@ -50187,6 +50973,66 @@ } } }, + "web.tracking.protection.explenation" : { + "comment" : "Privacy feature explanation in the browser settings", + "extractionState" : "extracted_with_value", + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo blockiert beim Durchsuchen des Internets automatisch ausgeblendete Tracker." + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "DuckDuckGo automatically blocks hidden trackers as you browse the web." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo bloquea automáticamente los rastreadores ocultos mientras navegas por la web." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo bloque automatiquement les traqueurs cachés lorsque vous naviguez sur le Web." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo blocca automaticamente i sistemi di tracciamento nascosti mentre navighi sul web." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo blokkeert automatisch verborgen trackers terwijl je op het web surft." + } + }, + "pl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Podczas przeglądania stron internetowych DuckDuckGo automatycznie blokuje ukryte mechanizmy śledzące." + } + }, + "pt" : { + "stringUnit" : { + "state" : "translated", + "value" : "O DuckDuckGo bloqueia automaticamente os rastreadores ocultos enquanto navegas na Internet." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "DuckDuckGo автоматически блокирует скрытые трекеры, пока вы посещаете сайты." + } + } + } + }, "web.tracking.protection.title" : { "comment" : "Web tracking protection settings section title", "extractionState" : "extracted_with_value", @@ -50352,9 +51198,6 @@ } } } - }, - "Zero" : { - }, "zoom" : { "comment" : "Menu with Zooming commands", @@ -50524,4 +51367,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/DuckDuckGo/LoginItems/LoginItemsManager.swift b/DuckDuckGo/LoginItems/LoginItemsManager.swift index d8ab2356db..e0a44a4fb4 100644 --- a/DuckDuckGo/LoginItems/LoginItemsManager.swift +++ b/DuckDuckGo/LoginItems/LoginItemsManager.swift @@ -20,8 +20,8 @@ import Common import Foundation import LoginItems -/// Class to manage the login items for Network Protection and DBP -/// +/// Class to manage the login items for the VPN and DBP +/// final class LoginItemsManager { private enum Action: String { case enable @@ -59,6 +59,12 @@ final class LoginItemsManager { } } + func isAnyEnabled(_ items: Set) -> Bool { + return items.contains(where: { item in + item.status == .enabled + }) + } + private func handleError(for item: LoginItem, action: Action, error: NSError) { let event = Pixel.Event.Debug.loginItemUpdateError( loginItemBundleID: item.agentBundleID, diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 5741f2f55e..17c166e932 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -23,6 +23,7 @@ import Common #if NETWORK_PROTECTION import NetworkProtection +import NetworkProtectionIPC #endif final class MainViewController: NSViewController { @@ -56,14 +57,53 @@ final class MainViewController: NSViewController { fatalError("MainViewController: Bad initializer") } - init(tabCollectionViewModel: TabCollectionViewModel? = nil, - bookmarkManager: BookmarkManager = LocalBookmarkManager.shared) { + init(tabCollectionViewModel: TabCollectionViewModel? = nil, bookmarkManager: BookmarkManager = LocalBookmarkManager.shared, autofillPopoverPresenter: AutofillPopoverPresenter) { let tabCollectionViewModel = tabCollectionViewModel ?? TabCollectionViewModel() self.tabCollectionViewModel = tabCollectionViewModel self.isBurner = tabCollectionViewModel.isBurner tabBarViewController = TabBarViewController.create(tabCollectionViewModel: tabCollectionViewModel) - navigationBarViewController = NavigationBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner) + +#if NETWORK_PROTECTION + let networkProtectionPopoverManager: NetPPopoverManager = { +#if DEBUG + guard case .normal = NSApp.runType else { + return NetPPopoverManagerMock() + } +#endif + + let ipcClient = TunnelControllerIPCClient() + ipcClient.register() + + return NetworkProtectionNavBarPopoverManager(ipcClient: ipcClient, networkProtectionFeatureDisabler: NetworkProtectionFeatureDisabler()) + }() + let networkProtectionStatusReporter: NetworkProtectionStatusReporter = { + var connectivityIssuesObserver: ConnectivityIssueObserver! + var controllerErrorMessageObserver: ControllerErrorMesssageObserver! +#if DEBUG + if ![.normal, .integrationTests].contains(NSApp.runType) { + connectivityIssuesObserver = ConnectivityIssueObserverMock() + controllerErrorMessageObserver = ControllerErrorMesssageObserverMock() + } +#endif + connectivityIssuesObserver = connectivityIssuesObserver ?? DisabledConnectivityIssueObserver() + controllerErrorMessageObserver = controllerErrorMessageObserver ?? ControllerErrorMesssageObserverThroughDistributedNotifications() + + let ipcClient = networkProtectionPopoverManager.ipcClient + return DefaultNetworkProtectionStatusReporter( + statusObserver: ipcClient.ipcStatusObserver, + serverInfoObserver: ipcClient.ipcServerInfoObserver, + connectionErrorObserver: ipcClient.ipcConnectionErrorObserver, + connectivityIssuesObserver: connectivityIssuesObserver, + controllerErrorMessageObserver: controllerErrorMessageObserver + ) + }() + + navigationBarViewController = NavigationBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner, networkProtectionPopoverManager: networkProtectionPopoverManager, networkProtectionStatusReporter: networkProtectionStatusReporter, autofillPopoverPresenter: autofillPopoverPresenter) +#else + navigationBarViewController = NavigationBarViewController.create(tabCollectionViewModel: tabCollectionViewModel, isBurner: isBurner, autofillPopoverPresenter: AutofillPopoverPresenter) +#endif + browserTabViewController = BrowserTabViewController(tabCollectionViewModel: tabCollectionViewModel, bookmarkManager: bookmarkManager) findInPageViewController = FindInPageViewController.create() fireViewController = FireViewController.create(tabCollectionViewModel: tabCollectionViewModel) @@ -156,6 +196,7 @@ final class MainViewController: NSViewController { updateReloadMenuItem() updateStopMenuItem() browserTabViewController.windowDidBecomeKey() + presentWaitlistThankYouPromptIfNecessary() #if NETWORK_PROTECTION sendActiveNetworkProtectionWaitlistUserPixel() @@ -417,6 +458,16 @@ final class MainViewController: NSViewController { } #endif + func presentWaitlistThankYouPromptIfNecessary() { + guard let window = self.view.window else { + assertionFailure("Couldn't get main view controller's window") + return + } + + let presenter = WaitlistThankYouPromptPresenter() + presenter.presentThankYouPromptIfNecessary(in: window) + } + // MARK: - First responder func adjustFirstResponder(selectedTabViewModel: TabViewModel? = nil, tabContent: Tab.TabContent? = nil, force: Bool = false) { @@ -560,7 +611,7 @@ extension MainViewController { ])) bkman.loadBookmarks() - let vc = MainViewController(bookmarkManager: bkman) + let vc = MainViewController(bookmarkManager: bkman, autofillPopoverPresenter: DefaultAutofillPopoverPresenter()) var c: AnyCancellable! c = vc.publisher(for: \.view.window).sink { window in window?.titlebarAppearsTransparent = true diff --git a/DuckDuckGo/Menus/HistoryMenu.swift b/DuckDuckGo/Menus/HistoryMenu.swift index aca38c3e10..b99c7a867f 100644 --- a/DuckDuckGo/Menus/HistoryMenu.swift +++ b/DuckDuckGo/Menus/HistoryMenu.swift @@ -34,6 +34,7 @@ final class HistoryMenu: NSMenu { private let clearAllHistoryMenuItem = NSMenuItem(title: UserText.mainMenuHistoryClearAllHistory, action: #selector(MainViewController.clearAllHistory), keyEquivalent: [.command, .shift, .backspace]) + .withAccessibilityIdentifier("HistoryMenu.clearAllHistory") private let clearAllHistorySeparator = NSMenuItem.separator() private let historyCoordinator: HistoryCoordinating @@ -56,6 +57,7 @@ final class HistoryMenu: NSMenu { } reopenMenuItemKeyEquivalentManager.reopenLastClosedMenuItem = reopenLastClosedMenuItem + reopenAllWindowsFromLastSessionMenuItem.setAccessibilityIdentifier("HistoryMenu.reopenAllWindowsFromLastSessionMenuItem") reopenMenuItemKeyEquivalentManager.lastSessionMenuItem = reopenAllWindowsFromLastSessionMenuItem } @@ -89,8 +91,10 @@ final class HistoryMenu: NSMenu { switch RecentlyClosedCoordinator.shared.cache.last { case is RecentlyClosedWindow: reopenLastClosedMenuItem.title = UserText.reopenLastClosedWindow + reopenLastClosedMenuItem.setAccessibilityIdentifier("HistoryMenu.reopenLastClosedWindow") default: reopenLastClosedMenuItem.title = UserText.reopenLastClosedTab + reopenLastClosedMenuItem.setAccessibilityIdentifier("HistoryMenu.reopenLastClosedTab") } } @@ -106,6 +110,7 @@ final class HistoryMenu: NSMenu { var recentlyVisitedHeaderMenuItem: NSMenuItem { let item = NSMenuItem(title: UserText.recentlyVisitedMenuSection) item.isEnabled = false + item.setAccessibilityIdentifier("HistoryMenu.recentlyVisitedHeaderMenuItem") return item } @@ -113,13 +118,16 @@ final class HistoryMenu: NSMenu { private func addRecentlyVisited() { recentlyVisitedMenuItems = [recentlyVisitedHeaderMenuItem] - recentlyVisitedMenuItems.append(contentsOf: historyCoordinator.getRecentVisits(maxCount: 14) - .map { - VisitMenuItem(visitViewModel: VisitViewModel(visit: $0)) - } - ) - recentlyVisitedMenuItems.forEach { - addItem($0) + let recentVisits = historyCoordinator.getRecentVisits(maxCount: 14) + for (index, visit) in zip( + recentVisits.indices, recentVisits + ) { + let visitMenuItem = VisitMenuItem(visitViewModel: VisitViewModel(visit: visit)) + visitMenuItem.setAccessibilityIdentifier("HistoryMenu.recentlyVisitedMenuItem.\(index)") + recentlyVisitedMenuItems.append(visitMenuItem) + } + for recentlyVisitedMenuItem in recentlyVisitedMenuItems { + addItem(recentlyVisitedMenuItem) } } @@ -189,9 +197,18 @@ final class HistoryMenu: NSMenu { } private func makeMenuItems(from grouping: HistoryGrouping) -> [NSMenuItem] { - return grouping.visits.map { visit in - VisitMenuItem(visitViewModel: VisitViewModel(visit: visit)) + let date = grouping.date + let isToday = NSCalendar.current.isDateInToday(date) + let visits = grouping.visits + var menuItems = [NSMenuItem]() + for (index, visit) in zip( + visits.indices, visits + ) { + let menuItem = VisitMenuItem(visitViewModel: VisitViewModel(visit: visit)) + menuItem.setAccessibilityIdentifier("HistoryMenu.historyMenuItem.\(isToday ? "Today" : "\(date)").\(index)") + menuItems.append(menuItem) } + return menuItems } private func makeTitle(for grouping: HistoryGrouping) -> (String, String) { diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index da7c0d9984..15e6e5b723 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -43,7 +43,7 @@ import SubscriptionUI // MARK: DuckDuckGo let servicesMenu = NSMenu(title: UserText.mainMenuAppServices) - let preferencesMenuItem = NSMenuItem(title: UserText.mainMenuAppPreferences, action: #selector(AppDelegate.openPreferences), keyEquivalent: ",") + let preferencesMenuItem = NSMenuItem(title: UserText.mainMenuAppPreferences, action: #selector(AppDelegate.openPreferences), keyEquivalent: ",").withAccessibilityIdentifier("MainMenu.preferencesMenuItem") // MARK: File let newWindowMenuItem = NSMenuItem(title: UserText.newWindowMenuItem, action: #selector(AppDelegate.newWindow), keyEquivalent: "n") @@ -207,12 +207,12 @@ import SubscriptionUI NSMenuItem.separator() NSMenuItem(title: UserText.mainMenuEditFind) { - NSMenuItem(title: UserText.findInPageMenuItem, action: #selector(MainViewController.findInPage), keyEquivalent: "f") - NSMenuItem(title: UserText.mainMenuEditFindFindNext, action: #selector(MainViewController.findInPageNext), keyEquivalent: "g") - NSMenuItem(title: UserText.mainMenuEditFindFindPrevious, action: #selector(MainViewController.findInPagePrevious), keyEquivalent: "G") + NSMenuItem(title: UserText.findInPageMenuItem, action: #selector(MainViewController.findInPage), keyEquivalent: "f").withAccessibilityIdentifier("MainMenu.findInPage") + NSMenuItem(title: UserText.mainMenuEditFindFindNext, action: #selector(MainViewController.findInPageNext), keyEquivalent: "g").withAccessibilityIdentifier("MainMenu.findNext") + NSMenuItem(title: UserText.mainMenuEditFindFindPrevious, action: #selector(MainViewController.findInPagePrevious), keyEquivalent: "G").withAccessibilityIdentifier("MainMenu.findPrevious") NSMenuItem.separator() - NSMenuItem(title: UserText.mainMenuEditFindHideFind, action: #selector(MainViewController.findInPageDone), keyEquivalent: "F") + NSMenuItem(title: UserText.mainMenuEditFindHideFind, action: #selector(MainViewController.findInPageDone), keyEquivalent: "F").withAccessibilityIdentifier("MainMenu.findInPageDone") } NSMenuItem(title: UserText.mainMenuEditSpellingandGrammar) { @@ -544,18 +544,20 @@ import SubscriptionUI } private func updateShortcutMenuItems() { - toggleAutofillShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .autofill) - toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .bookmarks) - toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .downloads) + Task { @MainActor in + toggleAutofillShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .autofill) + toggleBookmarksShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .bookmarks) + toggleDownloadsShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .downloads) #if NETWORK_PROTECTION - if NetworkProtectionKeychainTokenStore().isFeatureActivated { - toggleNetworkProtectionShortcutMenuItem.isHidden = false - toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) - } else { - toggleNetworkProtectionShortcutMenuItem.isHidden = true - } + if await DefaultNetworkProtectionVisibility().isVPNVisible() { + toggleNetworkProtectionShortcutMenuItem.isHidden = false + toggleNetworkProtectionShortcutMenuItem.title = LocalPinningManager.shared.shortcutTitle(for: .networkProtection) + } else { + toggleNetworkProtectionShortcutMenuItem.isHidden = true + } #endif + } } // MARK: - Debug @@ -570,24 +572,30 @@ import SubscriptionUI NSMenuItem(title: "Reset Data") { NSMenuItem(title: "Reset Default Browser Prompt", action: #selector(MainViewController.resetDefaultBrowserPrompt)) NSMenuItem(title: "Reset Default Grammar Checks", action: #selector(MainViewController.resetDefaultGrammarChecks)) - NSMenuItem(title: "Reset Autofill Data", action: #selector(MainViewController.resetSecureVaultData)) - NSMenuItem(title: "Reset Bookmarks", action: #selector(MainViewController.resetBookmarks)) + NSMenuItem(title: "Reset Autofill Data", action: #selector(MainViewController.resetSecureVaultData)).withAccessibilityIdentifier("MainMenu.resetSecureVaultData") + NSMenuItem(title: "Reset Bookmarks", action: #selector(MainViewController.resetBookmarks)).withAccessibilityIdentifier("MainMenu.resetBookmarks") NSMenuItem(title: "Reset Pinned Tabs", action: #selector(MainViewController.resetPinnedTabs)) NSMenuItem(title: "Reset YouTube Overlay Interactions", action: #selector(MainViewController.resetDuckPlayerOverlayInteractions)) NSMenuItem(title: "Reset MakeDuckDuckYours user settings", action: #selector(MainViewController.resetMakeDuckDuckGoYoursUserSettings)) + NSMenuItem(title: "Survey 10% on", action: #selector(MainViewController.in10PercentSurveyOn)) + NSMenuItem(title: "Survey 10% off", action: #selector(MainViewController.in10PercentSurveyOff)) NSMenuItem(title: "Change Activation Date") { NSMenuItem(title: "Today", action: #selector(MainViewController.changeInstallDateToToday), keyEquivalent: "N") - NSMenuItem(title: "Less Than a 21 days Ago", action: #selector(MainViewController.changeInstallDateToLessThan21DaysAgo)) - NSMenuItem(title: "More Than 21 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan21DaysAgoButLessThan27)) - NSMenuItem(title: "More Than 27 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan27DaysAgo)) + NSMenuItem(title: "Less Than a 1 days Ago", action: #selector(MainViewController.changeInstallDateToLessThan1DayAgo(_:))) + NSMenuItem(title: "More Than 1 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan1DayAgoButLessThan14(_:))) + NSMenuItem(title: "More Than 14 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan14DaysAgoButLessThan15(_:))) + NSMenuItem(title: "More Than 15 Days Ago", action: #selector(MainViewController.changeInstallDateToMoreThan15DaysAgo(_:))) } NSMenuItem(title: "Reset Email Protection InContext Signup Prompt", action: #selector(MainViewController.resetEmailProtectionInContextPrompt)) NSMenuItem(title: "Reset Daily Pixels", action: #selector(MainViewController.resetDailyPixels)) - } + }.withAccessibilityIdentifier("MainMenu.resetData") NSMenuItem(title: "UI Triggers") { NSMenuItem(title: "Show Save Credentials Popover", action: #selector(MainViewController.showSaveCredentialsPopover)) NSMenuItem(title: "Show Credentials Saved Popover", action: #selector(MainViewController.showCredentialsSavedPopover)) NSMenuItem(title: "Show Pop Up Window", action: #selector(MainViewController.showPopUpWindow)) + NSMenuItem(title: "Show VPN Thank You Modal", action: #selector(MainViewController.showVPNThankYouModal)) + NSMenuItem(title: "Show PIR Thank You Modal", action: #selector(MainViewController.showPIRThankYouModal)) + NSMenuItem(title: "Reset Thank You Modal Checks", action: #selector(MainViewController.resetThankYouModalChecks)) } NSMenuItem(title: "Remote Configuration") { customConfigurationUrlMenuItem @@ -609,8 +617,10 @@ import SubscriptionUI #endif #if NETWORK_PROTECTION - NSMenuItem(title: "Network Protection") - .submenu(NetworkProtectionDebugMenu()) + if case .normal = NSApp.runType { + NSMenuItem(title: "VPN") + .submenu(NetworkProtectionDebugMenu()) + } #endif NSMenuItem(title: "Trigger Fatal Error", action: #selector(MainViewController.triggerFatalError)) @@ -619,16 +629,21 @@ import SubscriptionUI let currentEnvironmentWrapper = UserDefaultsWrapper(key: .subscriptionEnvironment, defaultValue: SubscriptionPurchaseEnvironment.ServiceEnvironment.default) let isInternalTestingWrapper = UserDefaultsWrapper(key: .subscriptionInternalTesting, defaultValue: false) - SubscriptionDebugMenu(currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, - updateEnvironment: { - guard let newEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: $0) else { return } - currentEnvironmentWrapper.wrappedValue = newEnvironment - SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment }, - isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, - updateInternalTestingFlag: { isInternalTestingWrapper.wrappedValue = $0 }, - currentViewController: { - WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController - }, subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + SubscriptionDebugMenu( + currentEnvironment: { currentEnvironmentWrapper.wrappedValue.rawValue }, + updateEnvironment: { + guard let newEnvironment = SubscriptionPurchaseEnvironment.ServiceEnvironment(rawValue: $0) else { return } + currentEnvironmentWrapper.wrappedValue = newEnvironment + SubscriptionPurchaseEnvironment.currentServiceEnvironment = newEnvironment + VPNSettings(defaults: .netP).selectedEnvironment = newEnvironment == .staging ? .staging : .production + }, + isInternalTestingEnabled: { isInternalTestingWrapper.wrappedValue }, + updateInternalTestingFlag: { isInternalTestingWrapper.wrappedValue = $0 }, + currentViewController: { + WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController + }, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs) + ) #endif NSMenuItem(title: "Logging").submenu(setupLoggingMenu()) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 2f4c286472..fc0b5c9dc2 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -22,6 +22,8 @@ import Common import WebKit import Configuration import History +import PixelKit +import Subscription // Actions are sent to objects of responder chain @@ -708,7 +710,7 @@ extension MainViewController { UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowDuckPlayer.rawValue) UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowEmailProtection.rawValue) UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay0.rawValue) - UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay7.rawValue) + UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay14.rawValue) UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageUserInteractedWithSurveyDay0.rawValue) } @@ -716,28 +718,64 @@ extension MainViewController { guard let internalUserDecider = NSApp.delegateTyped.internalUserDecider as? DefaultInternalUserDecider else { return } let state = internalUserDecider.isInternalUser internalUserDecider.debugSetInternalUserState(!state) + + if !DefaultSubscriptionFeatureAvailability().isFeatureAvailable { + // We only clear PPro state when it's not available, as otherwise + // there should be no state to clear. Clearing PPro state can + // trigger notifications which we want to avoid unless + // necessary. + clearPrivacyProState() + } + } + + /// Clears the PrivacyPro state to make testing easier. + /// + private func clearPrivacyProState() { + AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).signOut() + resetThankYouModalChecks(nil) + UserDefaults.netP.networkProtectionEntitlementsExpired = false + + // Clear pixel data + DailyPixel.clearLastFireDate(pixel: .privacyProFeatureEnabled) + Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouDBP) + Pixel.shared?.clearRepetitions(for: .privacyProBetaUserThankYouVPN) } @objc func resetDailyPixels(_ sender: Any?) { UserDefaults.standard.removePersistentDomain(forName: DailyPixel.Constant.dailyPixelStorageIdentifier) } + @objc func in10PercentSurveyOn(_ sender: Any?) { + UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay14in10Percent.rawValue) + UserDefaults.standard.set(true, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay0in10Percent.rawValue) + } + + @objc func in10PercentSurveyOff(_ sender: Any?) { + UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay14in10Percent.rawValue) + UserDefaults.standard.set(false, forKey: UserDefaultsWrapper.Key.homePageShowSurveyDay0in10Percent.rawValue) + } + @objc func changeInstallDateToToday(_ sender: Any?) { UserDefaults.standard.set(Date(), forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @objc func changeInstallDateToLessThan21DaysAgo(_ sender: Any?) { - let lessThanTwentyOneDaysAgo = Calendar.current.date(byAdding: .day, value: -20, to: Date()) - UserDefaults.standard.set(lessThanTwentyOneDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) + @objc func changeInstallDateToLessThan1DayAgo(_ sender: Any?) { + let lessThanOneDaysAgo = Calendar.current.date(byAdding: .hour, value: -23, to: Date()) + UserDefaults.standard.set(lessThanOneDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) + } + + @objc func changeInstallDateToMoreThan1DayAgoButLessThan14(_ sender: Any?) { + let between1And4DaysAgo = Calendar.current.date(byAdding: .day, value: -13, to: Date()) + UserDefaults.standard.set(between1And4DaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @objc func changeInstallDateToMoreThan21DaysAgoButLessThan27(_ sender: Any?) { - let twentyOneDaysAgo = Calendar.current.date(byAdding: .day, value: -21, to: Date()) - UserDefaults.standard.set(twentyOneDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) + @objc func changeInstallDateToMoreThan14DaysAgoButLessThan15(_ sender: Any?) { + let twentyEightDaysAgo = Calendar.current.date(byAdding: .day, value: -14, to: Date()) + UserDefaults.standard.set(twentyEightDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } - @objc func changeInstallDateToMoreThan27DaysAgo(_ sender: Any?) { - let twentyEightDaysAgo = Calendar.current.date(byAdding: .day, value: -28, to: Date()) + @objc func changeInstallDateToMoreThan15DaysAgo(_ sender: Any?) { + let twentyEightDaysAgo = Calendar.current.date(byAdding: .day, value: -16, to: Date()) UserDefaults.standard.set(twentyEightDaysAgo, forKey: UserDefaultsWrapper.Key.firstLaunchDate.rawValue) } @@ -764,6 +802,28 @@ extension MainViewController { WindowsManager.openPopUpWindow(with: tab, origin: nil, contentSize: nil) } + @objc func resetThankYouModalChecks(_ sender: Any?) { + let presenter = WaitlistThankYouPromptPresenter() + presenter.resetPromptCheck() + UserDefaults.netP.removeObject(forKey: UserDefaults.vpnLegacyUserAccessDisabledOnceKey) + } + + @objc func showVPNThankYouModal(_ sender: Any?) { + let thankYouModalView = WaitlistBetaThankYouDialogViewController(copy: .vpn) + let thankYouWindowController = thankYouModalView.wrappedInWindowController() + if let thankYouWindow = thankYouWindowController.window { + WindowsManager.windows.first?.beginSheet(thankYouWindow) + } + } + + @objc func showPIRThankYouModal(_ sender: Any?) { + let thankYouModalView = WaitlistBetaThankYouDialogViewController(copy: .dbp) + let thankYouWindowController = thankYouModalView.wrappedInWindowController() + if let thankYouWindow = thankYouWindowController.window { + WindowsManager.windows.first?.beginSheet(thankYouWindow) + } + } + @objc func resetEmailProtectionInContextPrompt(_ sender: Any?) { EmailManager().resetEmailProtectionInContextPrompt() } @@ -844,6 +904,9 @@ extension MainViewController: NSMenuItemValidation { // swiftlint:disable cyclomatic_complexity // swiftlint:disable function_body_length func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { + guard fireViewController.fireViewModel.fire.burningData == nil else { + return true + } switch menuItem.action { // Back/Forward case #selector(MainViewController.back(_:)): diff --git a/DuckDuckGo/NavigationBar/PinningManager.swift b/DuckDuckGo/NavigationBar/PinningManager.swift index 64d29c104e..d2cc6ef6bb 100644 --- a/DuckDuckGo/NavigationBar/PinningManager.swift +++ b/DuckDuckGo/NavigationBar/PinningManager.swift @@ -104,6 +104,7 @@ final class LocalPinningManager: PinningManager { return } + manuallyToggledPinnedViewsStrings.removeAll(where: { $0 == view.rawValue }) pinnedViewStrings.removeAll(where: { $0 == view.rawValue }) NotificationCenter.default.post(name: .PinnedViewsChanged, object: nil, userInfo: [ @@ -133,7 +134,7 @@ final class LocalPinningManager: PinningManager { /// This method is useful for knowing if the view was manually toggled. /// It's particularly useful for initializing a pin to a certain value at a certain point during the execution of code, /// only if the user hasn't explicitly specified a desired state. - /// As an example: this is used in Network Protection for pinning the icon to the navigation bar the first time the + /// As an example: this is used in the VPN for pinning the icon to the navigation bar the first time the /// feature is enabled. /// func wasManuallyToggled(_ view: PinnableView) -> Bool { diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index c834cdd58e..84fbb30576 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -20,7 +20,8 @@ import AppKit import Carbon.HIToolbox import Combine import Common -import BrowserServicesKit +import Suggestions +import Subscription final class AddressBarTextField: NSTextField { @@ -57,6 +58,8 @@ final class AddressBarTextField: NSTextField { private var addressBarStringCancellable: AnyCancellable? private var contentTypeCancellable: AnyCancellable? + private let searchPreferences: SearchPreferences = SearchPreferences.shared + private enum TextDidChangeEventType { case none case userAppendingTextToTheEnd @@ -345,7 +348,7 @@ final class AddressBarTextField: NSTextField { #endif #if SUBSCRIPTION - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable() { + if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { if providedUrl.isChild(of: URL.subscriptionBaseURL) || providedUrl.isChild(of: URL.identityTheftRestoration) { self.updateValue(selectedTabViewModel: nil, addressBarString: nil) // reset self.window?.makeFirstResponder(nil) @@ -624,9 +627,9 @@ final class AddressBarTextField: NSTextField { } @objc func toggleAutocomplete(_ menuItem: NSMenuItem) { - AppearancePreferences.shared.showAutocompleteSuggestions.toggle() + searchPreferences.showAutocompleteSuggestions.toggle() - let shouldShowAutocomplete = AppearancePreferences.shared.showAutocompleteSuggestions + let shouldShowAutocomplete = searchPreferences.showAutocompleteSuggestions menuItem.state = shouldShowAutocomplete ? .on : .off @@ -1084,7 +1087,7 @@ private extension NSMenuItem { action: #selector(AddressBarTextField.toggleAutocomplete(_:)), keyEquivalent: "" ) - menuItem.state = AppearancePreferences.shared.showAutocompleteSuggestions ? .on : .off + menuItem.state = SearchPreferences.shared.showAutocompleteSuggestions ? .on : .off return menuItem } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift index 4caad386b5..256138f56e 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarViewController.swift @@ -99,6 +99,7 @@ final class AddressBarViewController: NSViewController { view.layer?.masksToBounds = false addressBarTextField.placeholderString = UserText.addressBarPlaceholder + addressBarTextField.setAccessibilityIdentifier("AddressBarViewController.addressBarTextField") updateView() // only activate active text field leading constraint on its appearance to avoid constraint conflicts diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index 28c9122766..4598e190e4 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -62,6 +62,7 @@ final class MoreOptionsMenu: NSMenu { private let passwordManagerCoordinator: PasswordManagerCoordinating private let internalUserDecider: InternalUserDecider private lazy var sharingMenu: NSMenu = SharingMenu(title: UserText.shareMenuItem) + private lazy var accountManager = AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) #if NETWORK_PROTECTION private let networkProtectionFeatureVisibility: NetworkProtectionFeatureVisibility @@ -164,15 +165,11 @@ final class MoreOptionsMenu: NSMenu { #if DBP @objc func openDataBrokerProtection(_ sender: NSMenuItem) { - #if SUBSCRIPTION - actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) - #else if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist && DataBrokerProtectionWaitlistViewControllerPresenter.shouldPresentWaitlist() { DataBrokerProtectionWaitlistViewControllerPresenter.show() } else { actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) } - #endif } #endif // DBP @@ -255,7 +252,6 @@ final class MoreOptionsMenu: NSMenu { #if SUBSCRIPTION @objc func openSubscriptionPurchasePage(_ sender: NSMenuItem) { - Pixel.fire(.privacyProOfferScreenImpression) actionDelegate?.optionsButtonMenuRequestedSubscriptionPurchasePage(self) } @@ -322,7 +318,7 @@ final class MoreOptionsMenu: NSMenu { var items: [NSMenuItem] = [] #if SUBSCRIPTION - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable() && !AccountManager().isUserAuthenticated { + if DefaultSubscriptionFeatureAvailability().isFeatureAvailable && !accountManager.isUserAuthenticated { items.append(contentsOf: makeInactiveSubscriptionItems()) } else { items.append(contentsOf: makeActiveSubscriptionItems()) // this adds NETP and DBP only if conditionally enabled @@ -341,29 +337,23 @@ final class MoreOptionsMenu: NSMenu { private func makeActiveSubscriptionItems() -> [NSMenuItem] { var items: [NSMenuItem] = [] -#if NETWORK_PROTECTION - if networkProtectionFeatureVisibility.isNetworkProtectionVisible() { - let isWaitlistUser = NetworkProtectionWaitlist().waitlistStorage.isWaitlistUser - let hasAuthToken = NetworkProtectionKeychainTokenStore().isFeatureActivated +#if SUBSCRIPTION + let subscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability() +#endif +#if NETWORK_PROTECTION + if networkProtectionFeatureVisibility.isNetworkProtectionBetaVisible() { let networkProtectionItem: NSMenuItem - // If the user can see the Network Protection option but they haven't joined the waitlist or don't have an auth token, show the "New" - // badge to bring it to their attention. - if !isWaitlistUser && !hasAuthToken { - networkProtectionItem = makeNetworkProtectionItem(showNewLabel: true) - } else { - networkProtectionItem = makeNetworkProtectionItem(showNewLabel: false) - } + networkProtectionItem = makeNetworkProtectionItem() items.append(networkProtectionItem) - #if SUBSCRIPTION - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable() && AccountManager().isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { Task { let isMenuItemEnabled: Bool - switch await AccountManager().hasEntitlement(for: .networkProtection) { + switch await accountManager.hasEntitlement(for: .networkProtection) { case let .success(result): isMenuItemEnabled = result case .failure: @@ -382,7 +372,8 @@ final class MoreOptionsMenu: NSMenu { #endif // NETWORK_PROTECTION #if DBP - if DefaultDataBrokerProtectionFeatureVisibility().isFeatureVisible() { + let dbpVisibility = DefaultDataBrokerProtectionFeatureVisibility() + if dbpVisibility.isFeatureVisible() || dbpVisibility.isPrivacyProEnabled() { let dataBrokerProtectionItem = NSMenuItem(title: UserText.dataBrokerProtectionOptionsMenuItem, action: #selector(openDataBrokerProtection), keyEquivalent: "") @@ -391,11 +382,11 @@ final class MoreOptionsMenu: NSMenu { items.append(dataBrokerProtectionItem) #if SUBSCRIPTION - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable() && AccountManager().isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { Task { let isMenuItemEnabled: Bool - switch await AccountManager().hasEntitlement(for: .dataBrokerProtection) { + switch await accountManager.hasEntitlement(for: .dataBrokerProtection) { case let .success(result): isMenuItemEnabled = result case .failure: @@ -415,7 +406,7 @@ final class MoreOptionsMenu: NSMenu { #endif // DBP #if SUBSCRIPTION - if AccountManager().isUserAuthenticated { + if accountManager.isUserAuthenticated { let identityTheftRestorationItem = NSMenuItem(title: UserText.identityTheftRestorationOptionsMenuItem, action: #selector(openIdentityTheftRestoration), keyEquivalent: "") @@ -423,11 +414,11 @@ final class MoreOptionsMenu: NSMenu { .withImage(.itrIcon) items.append(identityTheftRestorationItem) - if DefaultSubscriptionFeatureAvailability().isFeatureAvailable() && AccountManager().isUserAuthenticated { + if subscriptionFeatureAvailability.isFeatureAvailable && accountManager.isUserAuthenticated { Task { let isMenuItemEnabled: Bool - switch await AccountManager().hasEntitlement(for: .identityTheftRestoration) { + switch await accountManager.hasEntitlement(for: .identityTheftRestoration) { case let .success(result): isMenuItemEnabled = result case .failure: @@ -445,11 +436,10 @@ final class MoreOptionsMenu: NSMenu { #if SUBSCRIPTION private func makeInactiveSubscriptionItems() -> [NSMenuItem] { - let dataBrokerProtectionItem = NSMenuItem(title: UserText.dataBrokerProtectionScanOptionsMenuItem, - action: #selector(openSubscriptionPurchasePage(_:)), - keyEquivalent: "") - .targetting(self) - .withImage(.dbpIcon) + let shouldHidePrivacyProDueToNoProducts = SubscriptionPurchaseEnvironment.current == .appStore && SubscriptionPurchaseEnvironment.canPurchase == false + if shouldHidePrivacyProDueToNoProducts { + return [] + } let privacyProItem = NSMenuItem(title: UserText.subscriptionOptionsMenuItem, action: #selector(openSubscriptionPurchasePage(_:)), @@ -457,7 +447,7 @@ final class MoreOptionsMenu: NSMenu { .targetting(self) .withImage(.subscriptionIcon) - return [dataBrokerProtectionItem, privacyProItem] + return [privacyProItem] } #endif @@ -479,6 +469,7 @@ final class MoreOptionsMenu: NSMenu { addItem(withTitle: UserText.findInPageMenuItem, action: #selector(findInPage(_:)), keyEquivalent: "f") .targetting(self) .withImage(.findSearch) + .withAccessibilityIdentifier("MoreOptionsMenu.findInPage") addItem(withTitle: UserText.shareMenuItem, action: nil, keyEquivalent: "") .targetting(self) @@ -494,25 +485,12 @@ final class MoreOptionsMenu: NSMenu { } #if NETWORK_PROTECTION - private func makeNetworkProtectionItem(showNewLabel: Bool) -> NSMenuItem { + private func makeNetworkProtectionItem() -> NSMenuItem { let networkProtectionItem = NSMenuItem(title: "", action: #selector(showNetworkProtectionStatus(_:)), keyEquivalent: "") .targetting(self) .withImage(.image(for: .vpnIcon)) - if showNewLabel { - let attributedText = NSMutableAttributedString(string: UserText.networkProtection) - attributedText.append(NSAttributedString(string: " ")) - - let imageAttachment = NSTextAttachment() - imageAttachment.image = .newLabel - imageAttachment.setImageHeight(height: 16, offset: .init(x: 0, y: -4)) - - attributedText.append(NSAttributedString(attachment: imageAttachment)) - - networkProtectionItem.attributedTitle = attributedText - } else { - networkProtectionItem.title = UserText.networkProtection - } + networkProtectionItem.title = UserText.networkProtection return networkProtectionItem } diff --git a/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard b/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard index fe6b3f27ca..357dbe0d25 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard +++ b/DuckDuckGo/NavigationBar/View/NavigationBar.storyboard @@ -252,7 +252,7 @@ -